3

I tried to implement the solution here https://stackoverflow.com/a/33216302/4727842. My code looks like this

private fun readPublicKey(input: InputStream): PGPPublicKey {
    val publicKey = PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input), BcKeyFingerprintCalculator())
    var key: PGPPublicKey? = null

    publicKey.keyRings.forEach { ring: PGPPublicKeyRing ->
        ring.publicKeys.forEach { k: PGPPublicKey ->
            if (k.isEncryptionKey) {
                key = k
            }
        }
    }

    if (key == null) {
        throw IllegalArgumentException("Can't find encryption key input key ring.")
    } else {
        return key!!
    }
}

private fun encrypt(file: File): String {
    val input = IOUtils.toInputStream("pub.asc", "UTF-8")
    val key = readPublicKey(input)

    Security.addProvider(BouncyCastleProvider())

    val out = ByteArrayOutputStream()
    val compressor = PGPCompressedDataGenerator(PGPCompressedData.ZIP)

    PGPUtil.writeFileToLiteralData(compressor.open(out), PGPLiteralData.BINARY, file)
    compressor.close()

    val builder = BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES)
    val generator = PGPEncryptedDataGenerator(builder.setSecureRandom(SecureRandom()))

    generator.addMethod(BcPublicKeyKeyEncryptionMethodGenerator(key))

    return String(out.toByteArray())
}

Using the gpg cli (v2.2.15), after generating a key, I output the asc file.

gpg -ab -o ./pub.asc

its contents look like this

-----BEGIN PGP SIGNATURE-----

iQEzBAABCAAdFiEEpjyVE0VY1bLTdOo7wzVyPJ8SXFIFAlzbRfQACgkQwzVyPJ8S
XFKtjwgAp1ad9jFxKtsbzR4XT4HqypTPxpwY8raoIeXNg2PMDAFXGqmcRmP4NEBO
BYqalHAxrzXKVPcmKHtYnm7Jb91VLcYycsF+9RM53mwhg2YJhv49xROx8IsJuhVG
8X52nhPc+qQtzE/79FPDgiZNKSnrHUDvPU3rrZH44WPsGQJ9iGy0eoJPomuU29Cb
wWYxOHq8fRmL5h5Pi9mU1dJRZvHej8ewt0DpredY//7Er7xjCKHrFyzddSn1sGtv
QvoOP+1pLNCV/LKAgCz2N2vSOToLqYTuQlrO/kNApnza0+lO0GW4RMf0OJntbSIa
MHDa+/uc9YnyABkptxD2a9DsbvHwvg==
=QeFf
-----END PGP SIGNATURE-----

Then with the line

val input = IOUtils.toInputStream("pub.asc", "UTF-8")

I attempted to run. It output the message "Can't find encryption key input key ring." after failing to extract a key. After debugging I saw that publicKey.keyRings size was 0.

I didn't understand the situation since the linked guide seems to advocate using the .asc file for encrypting when I thought it was for signing. I don't know much about PGP but I thought those were different steps and that maybe signing was not necessary for my use case.

So, I tried exporting the public key

gpg --export -a "User" > public.key

containing

-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBFzaBaIBCACngN7Zu5ZKKJuwlp9/iYLleIbYqTm1mxIVEsCiiksLTwbKIo2M
d7YeFDjJRPoKS94bt0FaRUV7cKwXTvqdVg0OITkTLE446TrMhi6Yz9InxkO1yC2l
RfVIZJgOfFBFTwiqx5HpPMmgHnpyzYESiwkNXR7mVIbIX4/5r+NOjMH/ioBSAd80
FV8Yl7k72B6gpbD/CEV6iR+uWlL99Bv6aYtbubCUEieY0KZyk998TFlatrtHvhDQ
ftVHYS4EsrnvlwtVD8QLbnYA2WsX6xVqpz8KHZ5LzXLys4D64sHZ5BkD3aX8Jp5s
iCpOb6rU4wdlD7gDJb1a1ks613C8Qj5/KHN1ABEBAAG0J0dyYXlkZW4gSG9ybWVz
IDxncmF5ZGVuLmhvcm1lc0BzYXAuY29tPokBVAQTAQgAPhYhBKY8lRNFWNWy03Tq
O8M1cjyfElxSBQJc2gWiAhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA
AAoJEMM1cjyfElxS2H0H/0O37sJfcNC2UFeuwoGqPMV4+3dN6sejpWBvQCw8OuGv
lDQmPUpEbBHs3awI83XlpYBbAKaDcJlIHPugM6CKGNWOL8RhL+ziJoGX1/Cldc5M
NHToNwU78/LE9Eog6oiC/VELnLP2qMRGK9+6sSYy7kz4zVPRzGjjD3t2uSxpoclO
LnF7Iqm6W+j2OIHACgBeqjjv4hgH4bJjXuixuPSxRLNp0dQ6PU8fj+xRjJudJWS6
hx3PM8/NZvoDruqEWjoTJW96S7askitImtcSw90kMbTpw/knZhpNeCJMQPF8jLUo
ZSbuC+8EukzuJ1mNxS9S0M1IX4psKEUttBYya+SoX/C5AQ0EXNoFogEIALk9vHyO
sXeqHEXeRsCHDyfOtFJlUFQvtkf3dwyyg4hVQq2vYTVyO+couxpgmCCrCzSNdlwK
Salh7Wejf+U+Erx8RzGJ764uyIlw/1B2qlGFgdWPaRWHtufQiUz6RNtWEhsBuddI
lbWxMOvbA3wfkFAIl+NQ8Aqqd8N9ao7mrXcADF7hRMtM+TllFJjxpNJeCWxwfeDf
EMM6totFf9CyD+Q0Zq6zyVFhgLiMuLPX4LsRMBKHGrg0LyyRQgDEezI1WH25YeAT
El8YJriEV4PcXQGnrsfkKbvsnNkXFb3nRlANuKlnHgBrSLE9Tqm4XuwG+czdGJuU
p0eyoxv4vrCGCgMAEQEAAYkBPAQYAQgAJhYhBKY8lRNFWNWy03TqO8M1cjyfElxS
BQJc2gWiAhsMBQkDwmcAAAoJEMM1cjyfElxSRuwH/3RZ6Zs5K76GaakJh0H5uw3H
mqrQiVSh1PmcGO2qRZuDrGDOiJWnOCnl48t71DgqjyABPAYsYzIhZXltA6lYVCI5
68HmUyfOHyeoZa6t35YM3A6S3jsIHRSQgu98R/1VK5EksQnGTrYwsa3gUQy+7BbN
F88+jRhEKnBYOABQ+M29pkx8zvH9UdrHWMTu2h5tb70volDSQzsdN9KX4EHoAto3
jP3janjIzYohBXM+FFte9HIOndwJ0RsZ5UD8lx8EmKh4DCUw3JxHagHOvdXopAAM
j+bUVTlu72LmvcsiNAKg7UicIuprKyHxDkSeelgEQt7Iz+6w3WL9djvL3hG3cks=
=9i+0
-----END PGP PUBLIC KEY BLOCK-----

and ran with

val input = IOUtils.toInputStream("public.key", "UTF-8")

This yielded no change. bouncycastle found no keys in my public key file. I then saw that since I am using gpg2 there are differences in how key rings are handled. I found a solution to convert a .pkr to a .kbx here https://stackoverflow.com/a/34221494/4727842 but at this point this has become woefully complicated. I don't know the difference between a public key ring and a public key. I don't know why the first example used a .asc file as a suposedly valid keyring. I especially don't know why it is so hard to create a bouncycastle PGPPublicKey object from my public.key file.

Grayden Hormes
  • 855
  • 1
  • 15
  • 34

2 Answers2

1

If you just want to do proper en-/decryption use bouncy-gpg (shameless plug: I am the author).

Also do not use 3DES, it is dead Slow and not the most modern algorithm (though it is still considered secure, AFAIK).

You export the public key via gpg --export -a me@example.com.

You manage keys using the InMemoryKeyring (see here for details):

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

       // if you only want to encrypt you do not need a private key and can ignore this line
keyring.addSecretKey(exportedPrivateKey.getBytes("US-ASCII"));
// you can add many more private keys

return keyring;
}


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.
KeyringConfig keyringConfigOfSender = keyringConfigInMemoryForKeys(..); // TODO - here comes your code to get keys

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") // TODO - also your job to set the recipients 
        .andSignWith("sender@example.com") // TODO - same here
        .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();
Jens
  • 570
  • 3
  • 11
0

You can use PGPainless for that (another shameless plug - Author here as well :P):

        // Prepare keys
        // Required by the sender
        PGPSecretKeyRing keyAlice = PGPainless.readKeyRing().secretKeyRing(ALICE_KEY);
        PGPPublicKeyRing certificateBob = PGPainless.readKeyRing().publicKeyRing(BOB_CERT);
        SecretKeyRingProtector protectorAlice = SecretKeyRingProtector.unprotectedKeys();

        // Required by the recipient
        PGPSecretKeyRing keyBob = PGPainless.readKeyRing().secretKeyRing(BOB_KEY);
        PGPPublicKeyRing certificateAlice = PGPainless.readKeyRing().publicKeyRing(ALICE_CERT);
        SecretKeyRingProtector protectorBob = SecretKeyRingProtector.unprotectedKeys();

        // plaintext message to encrypt
        String message = "Hello, World!\n";
        ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
        // Encrypt and sign
        EncryptionStream encryptor = PGPainless.encryptAndOrSign()
                .onOutputStream(ciphertext)
                .withOptions(ProducerOptions.signAndEncrypt(
                        // we want to encrypt communication (affects key selection based on key flags)
                        EncryptionOptions.encryptCommunications()
                                .addRecipient(certificateBob)
                                .addRecipient(certificateAlice),
                        new SigningOptions()
                                .addInlineSignature(protectorAlice, keyAlice, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
                        ).setAsciiArmor(true)
                );

        // Pipe data trough and CLOSE the stream (important)
        Streams.pipeAll(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)), encryptor);
        encryptor.close();

        // Encrypted message
        String encryptedMessage = ciphertext.toString();

        // Decrypt and verify signatures
        DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
                .onInputStream(new ByteArrayInputStream(encryptedMessage.getBytes(StandardCharsets.UTF_8)))
                .withOptions(new ConsumerOptions()
                        .addDecryptionKey(keyBob, protectorBob)
                        .addVerificationCert(certificateAlice)
                );

        ByteArrayOutputStream plaintext = new ByteArrayOutputStream();

        Streams.pipeAll(decryptor, plaintext);
        decryptor.close();

        // Check the metadata to see how the message was encrypted/signed
        OpenPgpMetadata metadata = decryptor.getResult();
        assertTrue(metadata.isEncrypted());
        assertTrue(metadata.containsVerifiedSignatureFrom(certificateAlice));
        assertEquals(message, plaintext.toString());

And if you want it even simpler, PGPainless provides a SOP (Stateless OpenPGP Protocol) API via pgpainless-sop:

SOP sop = new SOPImpl();
        
// Generate an OpenPGP key - Alternatively just read existing (unencrypted) key from disk
byte[] key = sop.generateKey()
        .userId("Alice <alice@example.org>")
        .generate()
        .getBytes();

// Extract the certificate (public key) - alternatively just read certificate from disk
byte[] cert = sop.extractCert()
        .key(key)
        .getBytes();

// Encrypt a message
byte[] message = ...
byte[] encrypted = sop.encrypt()
        .withCert(cert)
        .signWith(key)
        .plaintext(message)
        .getBytes();

// Decrypt a message
ByteArrayAndResult<DecryptionResult> messageAndVerifications = sop.decrypt()
        .verifyWith(cert)
        .withKey(key)
        .ciphertext(encrypted)
        .toByteArrayAndResult();
byte[] decrypted = messageAndVerifications.getBytes();
// Signature Verifications
DecryptionResult messageInfo = messageAndVerifications.getResult();
List<Verification> signatureVerifications = messageInfo.getVerifications();
vanitasvitae
  • 185
  • 8