Sunday, July 10, 2016

JPGPJ: A new Java GPG Library

The Bouncy Castle PGP implementation is the "standard" GPG/PGP library in Java, and it's quite solid — but it's cumbersome to use directly, since it pretty much forces you to learn and use the raw primitives of the OpenPGP spec (RFC 4880). Also, while there is some helpful example code in the Bouncy Castle examples package (and snippets from the same examples have been copied and pasted into a bunch of Stack Overflow answers), the example code is (appropriately?) cryptic, and covers only a limited subset of functionality in each example.

Encrypting with JPGPJ

So I wrote a small library, JPGPJ, to wrap the Bouncy Castle PGP implementation with a simple API for encrypting and decrypting files. It makes interoperating with the standard gpg command-line client (GnuPGP) a breeze. This is all you need to do to encrypt a file with Bob's public key, and sign it with Alice's private key:

new Encryptor(
    new Key(new File("path/to/my/keys/alice-sec.gpg"), "password123"),
    new Key(new File("path/to/my/keys/bob-pub.gpg"))
).encrypt(
    new File("path/to/plaintext.txt"),
    new File("path/to/ciphertext.txt.gpg")
);

The above Java code does the same thing as following gpg command (where Alice has an `alice` secret key and a `bob` public key on her keyring, and enters "password123" when prompted for her passphrase):

gpg --sign --encrypt --local-user alice --recipient alice --recipient bob \
    --output path/to/ciphertext.txt.gpg path/to/plaintext.txt

JPGPJ is set up to do the right thing by default — sign and encrypt — but if you just want to encrypt without signing, that's easy, too — just set the encryptor's signingAlgoritm property to Unsigned:

Encryptor encryptor = new Encryptor(
    new Key(new File("path/to/my/keys/bob-pub.gpg"))
);
encryptor.setSigningAlgorithm(HashingAlgorithm.Unsigned);
encryptor.encrypt(
    new File("path/to/plaintext.txt"),
    new File("path/to/ciphertext.txt.gpg")
);

To encode with ASCII Armor (ie produce Base64-encoded content, instead of binary content), just turn on the encryptor's asciiArmored flag:

Encryptor encryptor = new Encryptor(
    new Key(new File("path/to/my/keys/bob-pub.gpg"))
);
encryptor.setSigningAlgorithm(HashingAlgorithm.Unsigned);
encryptor.setAsciiArmored(true);
encryptor.encrypt(
    new File("path/to/plaintext.txt"),
    new File("path/to/ciphertext.txt.asc")
);

Decrypting with JPGPJ

Decrypting is just as easy. JPGPJ handles signed or unsigned, encrypted or unencrypted, compressed or uncompressed, ascii-armored or binary messages all the same way. Its default setting is to require messages to be signed by a known key; so for example, to decrypt a message signed by Alice's private key and encrypted with Bob's public key (requiring Alice's public key to verify and Bob's private key to decrypt), this is all the Java you need:

new Decryptor(
    new Key(new File("path/to/my/keys/alice-pub.gpg")),
    new Key(new File("path/to/my/keys/bob-sec.gpg"), "b0bru1z!")
).decrypt(
    new File("path/to/ciphertext.txt.gpg"),
    new File("path/back-to/plaintext.txt")
);

The above Java code does the same thing as the following gpg command (where Bob has a `bob` secret key and an `alice` public key on his keyring, and enters "b0bru1z!" when prompted for his passphrase):

gpg --decrypt --output path/back-to/plaintext.txt path/to/ciphertext.txt.gpg

If the message can't be verified by any known key (that is, any key with which the decryptor had been configured), JPGPJ will raise a VerificationException. If the message can't be decrypted by any known private key (that is, any private key with which the decryptor had been configured), JPGPJ will raise a DecryptionException.

To ignore signatures (in other words, decrypt a message successfully regardless of whether it was signed or not), simply turn off the decryptor's verificationRequired flag:

Decryptor decryptor = new Decryptor(
    new Key(new File("path/to/my/keys/bob-sec.gpg"), "b0bru1z!")
);
decryptor.setVerificationRequired(false);
decryptor.decrypt(
    new File("path/to/ciphertext.txt.gpg"),
    new File("path/back-to/plaintext.txt")
);

Keys in JPGPJ

The key data used by JPGPJ is simply what get when you export a key from GnuPG, like with the following gpg command for a public key:

gpg --export alice > path/to/my/keys/alice-pub.gpg

Or this gpg command to export a private key (which exports both the public and private parts of the key, encrypted with the same password that the key has on your GnuPG keyring):

gpg --export-secret-keys bob > path/to/my/keys/bob-sec.gpg

If you encode keys with ASCII Armor when you export them (via the GnuPG --armor flag), you can load them the same way in JPGPJ; and you can also embed ascii-armored keys as strings in your source code, if you find that more convenient than using external files (see the Key Rings wiki page for more details on loading and using keys in JPGPJ).