PGPainless aims to make using OpenPGP with Bouncycastle fun again by abstracting away most of the complexity and overhead that normally comes with it. At the same time PGPainless remains configurable by making heavy use of the builder pattern for almost everything.
Lets take a look at how to create a fresh OpenPGP key:
PGPKeyRing keyRing = PGPainless.generateKeyRing() .simpleEcKeyRing("firstname.lastname@example.org", "password123");
That is all it takes to generate an OpenPGP keypair that uses ECDH+ECDSA keys for encryption and signatures! You can of course also configure a more complex key pair with different algorithms and attributes:
PGPainless.generateKeyRing() .withSubKey(KeySpec.getBuilder(RSA_ENCRYPT.withLength(RsaLength._4096)) .withKeyFlags(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE) .withDefaultAlgorithms()) .withSubKey(KeySpec.getBuilder(ECDH.fromCurve(EllipticCurve._P256)) .withKeyFlags(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE) .withDefaultAlgorithms()) .withSubKey(KeySpec.getBuilder(RSA_SIGN.withLength(RsaLength._4096)) .withKeyFlags(KeyFlag.SIGN_DATA) .withDefaultAlgorithms()) .withMasterKey(KeySpec.getBuilder(RSA_SIGN.withLength(RsaLength._8192)) .withKeyFlags(KeyFlag.CERTIFY_OTHER) .withDetailedConfiguration() .withPreferredSymmetricAlgorithms(SymmetricKeyAlgorithm.AES_256) .withPreferredHashAlgorithms(HashAlgorithm.SHA512) .withPreferredCompressionAlgorithms(CompressionAlgorithm.BZIP2) .withFeature(Feature.MODIFICATION_DETECTION) .done()) .withPrimaryUserId("email@example.com") .withPassphrase(new Passphrase("password123".toCharArray())) .build();
The API is designed in a way so that the user can very hardly make mistakes. Inputs are typed, so that as an example the user cannot input a wrong key length for an RSA key. The “shortcut” methods (eg.
withDefaultAlgorithms()) uses sane, secure defaults.
Now that we have a key, lets encrypt some data!
byte secretMessage = message.getBytes(UTF8); ByteArrayOutputStream envelope = new ByteArrayOutputStream(); EncryptionStream encryptor = PGPainless.createEncryptor() .onOutputStream(envelope) .toRecipients(recipientPublicKey) .usingSecureAlgorithms() .signWith(keyDecryptor, senderSecretKey) .asciiArmor(); Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor); encryptor.close(); byte encryptedSecretMessage = envelope.toByteArray();
As you can see there is almost no boilerplate code! At the same time, above code will create a stream that will encrypt and sign all the data that is passed through. In the end the envelope stream will contain an ASCII armored message that can only be decrypted by the intended recipients and that is signed using the senders secret key.
Decrypting data and/or verifying signatures works very similar:
ByteArrayInputStream envelopeIn = new ByteArrayInputStream(encryptedSecretMessage); DecryptionStream decryptor = PGPainless.createDecryptor() .onInputStream(envelopeIn) .decryptWith(keyDecryptor, recipientSecretKey) .verifyWith(senderPublicKey) .ignoreMissingPublicKeys() .build(); ByteArrayOutputStream decryptedSecretMessage = new ByteArrayOutputStream(); Streams.pipeAll(decryptor, decryptedSecretMessage); decryptor.close(); OpenPgpMetadata metadata = decryptor.getResult();
The decryptedSecretMessage stream now contains the decrypted message. The metadata object can be used to get information about the message, eg. which keys and algorithms were used to encrypt/sign the data and if those signatures were valid.
In summary, PGPainless is now able to create different types of keys, read encrypted and unencrypted keys, encrypt and/or sign data streams as well as decrypt and/or verify signatures. The latest additions to the API contain support for creating and verifying detached signatures.
PGPainless is already in use in Smacks OpenPGP module which implements XEP-0373: OpenPGP for XMPP and it has been designed primarily with the instant messaging use case in mind. So if you want to add OpenPGP support to your application, feel free to give PGPainless a try!