Looking for contributors: It's boring to work alone - If you are interested in contributing to an open source project please open an issue to discuss your ideas or create a PR
Make using Bouncy Castle with OpenPGP great fun again!
This project gives you the following super-powers
Bouncy GPG comes with several examples build in.
Bouncy GPG supports reading gpg
keyrings and parsing keys exported via gpg --export
and gpg --export-secret-key
.
The unit tests have some examples creating/reading keyrings.
The easiest way to manage keyrings is to use the pre-defined KeyringConfigs.
The following snippet encrypts a secret message to recipient@example.com
(and also self-encrypts it to sender@example.com
), and signs with sender@example.com
.
The encrypted message is then decrypted and the signature is verified. (This is from a documentation test).
final String original_message = "I love deadlines. I like the whooshing sound they make as they fly by. Douglas Adams";
// Most likely you will use one of the KeyringConfigs.... methods.
// These are wrappers for the test.
KeyringConfig keyringConfigOfSender = Configs
.keyringConfigFromResourceForSender();
ByteArrayOutputStream result = new ByteArrayOutputStream();
try (
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(result);
final OutputStream outputStream = BouncyGPG
.encryptToStream()
.withConfig(keyringConfigOfSender)
.withStrongAlgorithms()
.toRecipients("recipient@example.com", "sender@example.com")
.andSignWith("sender@example.com")
.binaryOutput()
.andWriteTo(bufferedOutputStream);
// Maybe read a file or a webservice?
final ByteArrayInputStream is = new ByteArrayInputStream(original_message.getBytes())
) {
Streams.pipeAll(is, outputStream);
// It is very important that outputStream is closed before the result stream is read.
// The reason is that GPG writes the signature at the end of the stream.
// This is triggered by closing the stream.
// In this example outputStream is closed via the try-with-resources mechanism of Java
}
result.close();
byte[] chipertext = result.toByteArray();
//////// Now decrypt the stream and check the signature
// Most likely you will use one of the KeyringConfigs.... methods.
// These are wrappers for the test.
KeyringConfig keyringConfigOfRecipient = Configs
.keyringConfigFromResourceForRecipient();
final OutputStream output = new ByteArrayOutputStream();
try (
final InputStream cipherTextStream = new ByteArrayInputStream(chipertext);
final BufferedOutputStream bufferedOut = new BufferedOutputStream(output);
final InputStream plaintextStream = BouncyGPG
.decryptAndVerifyStream()
.withConfig(keyringConfigOfRecipient)
.andRequireSignatureFromAllKeys("sender@example.com")
.fromEncryptedInputStream(cipherTextStream)
) {
Streams.pipeAll(plaintextStream, bufferedOut);
}
output.close();
final String decrypted_message = new String(((ByteArrayOutputStream) output).toByteArray());
assertEquals(original_message, decrypted_message);
Bouncy castle is often fast enough to not be the bottleneck. That said, here are some metrics to give you an indication of the performance:
Use Case | MBP 2,9 GHz Intel Core i5, Java 1.8.0_111 | (please add more via PR) |
---|---|---|
Encrypt & sign 1GB random | ~64s (16 MB/s) | |
Decrypt 1GB random | ~32s (32 MB/s) |
The directory examples contains several examples that show how easy some common use cases are implemented.
Decrypt a file and verify the signature.
decrypt.sh SOURCEFILE DESTFILE
Uses the testing keys to decrypt a file. Useful for performance measurements and gpg
interoperability.
Encrypt and sign a file.
encrypt.sh SOURCEFILE DESTFILE
Uses the testing keys to encrypt a file. Useful for performance measurements and gpg
interoperability.
A GPG encrypted ZIP file is decrypted on the fly. The structure of the ZIP is then written to disk. All files are re-encrypted before saving them.
demo_reencrypt.sh TARGET
-- decrypts an encrypted ZIP file containing three files (total size: 1.2 GB) AND
re-encrypts each of the files in the ZIP to the TARGET
dir.The sample shows how e.g. batch jobs can work with large files without leaving plaintext on disk (together with Transparent GPG decryption).
This scheme has some very appealing benefits:
ftp
,
comforting when you use https
and the next Heartbleed pops up.Consider the following batch job:
pre-processing
needs to split up the data for further processingpre-processing
stream-processes the GPG/ZIP archive
InputStream
processing
job transparently reads the files without writing plaintext to the disk.Have a look at the example classes to see how easy it is to use Bouncy Castle PGP.
Add bouncy castle as a dependency and then install the provider before in your application.
// build.gradle
// in build.gradle add a dependency to bouncy castle and bouncy-gpg
//...
repositories {
mavenCentral()
// jcenter() - optional.
}
//...
// ...
dependencies {
compile 'org.bouncycastle:bcprov-jdk15on:1.67'
compile 'org.bouncycastle:bcpg-jdk15on:1.67'
// ...
compile 'name.neuhalfen.projects.crypto.bouncycastle.openpgp:bouncy-gpg:2.+'
// ...
}
<dependency>
<groupId>name.neuhalfen.projects.crypto.bouncycastle.openpgp</groupId>
<artifactId>bouncy-gpg</artifactId>
<version>2.3.0</version>
</dependency>
// in one of you classed install the BC provider
BouncyGPG.registerProvider();
Class | Use when you want to |
---|---|
BouncyGPG |
Starting point for the convenient fluent en- and decryption API. |
KeyringConfigs |
Create default implementations for GPG keyring access. You can also create your own implementations by implementing KeyringConfig . |
KeyringConfigCallbacks |
Used by KeyringConfigs . Create default implementations to provide secret-key passwords. |
DefaultPGPAlgorithmSuites |
Select from predefined algorithms suites or create your won with PGPAlgorithmSuite . |
ReencryptExplodedZipSinglethread |
Work with encrypted ZIPs |
I strongly advise against using secring.gpg
or pubring.gpg
for production systems. These files are an undocumented API of gnupg: usable, but can change or show unexpected results. E.g. with GPG 2.1 the secring.gpg
file gets no longer updated and will provide you with stale data (emphasis by me):
To ease the migration to the no-secring method, gpg detects the presence of a secring.gpg and converts the keys on-the-fly to the the key store of gpg-agent (this is the private-keys-v1.d directory below the GnuPG home directory (~/.gnupg)). This is done only once and an existing secring.gpg is then not anymore touched by gpg. This allows co-existence of older GnuPG versions with GnuPG 2.1. However, any change to the private keys using the new gpg will not show up when using pre-2.1 versions of GnuPG and vice versa.
Most applications should manage their keys in an application specific database. Though this might seem more complex than just using the existing keyring files it has a some nice advantages:
gpg
executableTo use the InMemoryKeyring you first need to export the keys. Then you put them in your application data(base).
Export the keys with gpg
(gpg --export-secret-key -a user@example.com
and gpg --export -a user@example.com
):
gpg --export -a user@example.com > user@example.com.pub.gpg
cat user@example.com.pub.gpg
-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----
# You need to input keys passphrase here
gpg --export-secret-key -a user@example.com > user@example.com.secret.gpg
cat user@example.com.secret.gpg
-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----
A few examples for using the InMemoryKeyring can be found in the Test code.
Here is the short version:
public static KeyringConfig keyringConfigInMemoryForKeys(final String exportedPubKey, final String exportedPrivateKey, final String passphrase) throws IOException, PGPException {
final InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withPassword(passphrase);
keyring.addPublicKey(exportedPubKey.getBytes("US-ASCII"));
// you can add many more public keys
keyring.addSecretKey(exportedPrivateKey.getBytes("US-ASCII"));
// you can add many more privvate keys
return keyring;
}
The most straight forward way is to call BouncyGPG::createSimpleKeyring()
:
final KeyringConfig rsaKeyRing = BouncyGPG.createSimpleKeyring()
.simpleRsaKeyRing(UID_JULIET, RsaLength.RSA_3072_BIT);
Here is a more complex case with dedicated subkeys for signing, encryption, and authentication:
final KeySpec signingSubey = KeySpecBuilder
.newSpec(RSAForSigningKeyType.withLength(RsaLength.RSA_2048_BIT))
.allowKeyToBeUsedTo(KeyFlag.SIGN_DATA)
.withDefaultAlgorithms();
final KeySpec authenticationSubey = KeySpecBuilder
.newSpec(RSAForEncryptionKeyType.withLength(RsaLength.RSA_2048_BIT))
.allowKeyToBeUsedTo(KeyFlag.AUTHENTICATION)
.withDefaultAlgorithms();
final KeySpec encryptionSubey = KeySpecBuilder
.newSpec(RSAForEncryptionKeyType.withLength(RsaLength.RSA_2048_BIT))
.allowKeyToBeUsedTo(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)
.withDefaultAlgorithms();
final KeySpec masterKey = KeySpecBuilder.newSpec(
RSAForSigningKeyType.withLength(RsaLength.RSA_3072_BIT)
)
.allowKeyToBeUsedTo(KeyFlag.CERTIFY_OTHER)
.withDetailedConfiguration()
.withPreferredSymmetricAlgorithms(
PGPSymmetricEncryptionAlgorithms.recommendedAlgorithms()
)
.withPreferredHashAlgorithms(
PGPHashAlgorithms.recommendedAlgorithms()
)
.withPreferredCompressionAlgorithms(
PGPCompressionAlgorithms.recommendedAlgorithms()
)
.withFeature(Feature.MODIFICATION_DETECTION)
.done();
final KeyringConfig complexKeyRing = BouncyGPG
.createKeyring()
.withSubKey(signingSubey)
.withSubKey(authenticationSubey)
.withSubKey(encryptionSubey)
.withMasterKey(masterKey)
.withPrimaryUserId(uid)
.withPassphrase(Passphrase.fromString(passphrase))
.build();
return complexKeyRing;
The bouncy castle functions can be used to persist keys. ExportGeneratedKeysTest.java shows how to do that.
final InMemoryKeyring keyring = keyringConfigInMemoryForKeys(...);
final InputStream decryptedPlaintextStream = BouncyGPG
.decryptAndVerifyStream()
.withConfig(keyring)
....
The project is a basic gradle build. All the scripts use ./gradlew installDist
The coverage report (incl. running tests) is generated with ./gradlew check
.
./gradlew bintrayUpload
This code is placed under the Apache License 2.0. Don't forget to adhere to the BouncyCastle License (http://bouncycastle.org/license.html).