Search code examples
javaencryptionbouncycastleopenpgp

Java: PGP Encryption using Bouncy Castle


I am trying to achieve encryption using PGP and my encrypt method successfully encrypts the input string but when I try to decrypt it to verify if the encryption is properly done, the string doesn't get decrypted.

I tried 2 approaches:

1st approach uses FileOutputStream to write encrypted string & 2nd approach uses ByteArrayOutputStream. FileOutputStream creates a file and I am able to decrypt it using Kleopatra. However my requirement is to just get an encrypted string (not written in a file). So when I try to decrypt the encrypted string (received after using ByteArrayOutputStream) its not working. I tried copying the string and decrypting it through tools>>clipboard in Kleopatra, but the decrypt/verify option is disabled. I tried writing the string on a file manually & through FileWriter class, but decryption fails with the error that File contains certificate & cannot be decrypted or verified.

I assume only files created directly by OutputStream gets decrypted successfully. But I have to really check the encrypted string.

Any help would be highly appreciated.


Solution

  • The following full example is taken from the source code of the book "Java Cryptography: Tools and Techniques by David Hook & Jon Eaves". The complete source code with all examples is available here: https://www.bouncycastle.org/java-crypto-tools-src.zip

    The examples are showing a private-/public key creation with El Gamal or Elliptic Curves and encryption with AES-256. In the ecExample-method I added two lines to save the encrypted string to the file "pgp-encrypted-string.dat" and then reload the data to decypt the file and show the decrypted string.

    import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.openpgp.*;
    import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
    import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
    import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
    import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
    import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
    import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
    import org.bouncycastle.util.Strings;
    import org.bouncycastle.util.io.Streams;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.SecureRandom;
    import java.security.Security;
    import java.security.spec.ECGenParameterSpec;
    import java.util.Date;
    
    public class PGPEncryptionExampleForSO
    {
         /**
         * Create an encrypted data blob using an AES-256 session key and the
         * passed in public key.
         *
         * @param encryptionKey the public key to use.
         * @param data the data to be encrypted.
         * @return a PGP binary encoded version of the encrypted data.
         */
        public static byte[] createEncryptedData(
            PGPPublicKey encryptionKey,
            byte[] data)
            throws PGPException, IOException
        {
            PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256)
                    .setWithIntegrityPacket(true)
                    .setSecureRandom(new SecureRandom()).setProvider("BC"));
            encGen.addMethod(
                new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey)
                    .setProvider("BC"));
            ByteArrayOutputStream encOut = new ByteArrayOutputStream();
            // create an indefinite length encrypted stream
            OutputStream cOut = encGen.open(encOut, new byte[4096]);
            // write out the literal data
            PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
            OutputStream pOut = lData.open(
                cOut, PGPLiteralData.BINARY,
                PGPLiteralData.CONSOLE, data.length, new Date());
            pOut.write(data);
            pOut.close();
            // finish the encryption
            cOut.close();
            return encOut.toByteArray();
        }
    
        /**
         * Extract the plain text data from the passed in encoding of PGP
         * encrypted data. The routine assumes the passed in private key
         * is the one that matches the first encrypted data object in the
         * encoding.
         *
         * @param privateKey the private key to decrypt the session key with.
         * @param pgpEncryptedData the encoding of the PGP encrypted data.
         * @return a byte array containing the decrypted data.
         */
        public static byte[] extractPlainTextData(
            PGPPrivateKey privateKey,
            byte[] pgpEncryptedData)
            throws PGPException, IOException
        {
            PGPObjectFactory pgpFact = new JcaPGPObjectFactory(pgpEncryptedData);
            PGPEncryptedDataList encList = (PGPEncryptedDataList)pgpFact.nextObject();
            // find the matching public key encrypted data packet.
            PGPPublicKeyEncryptedData encData = null;
            for (PGPEncryptedData pgpEnc: encList)
            {
                PGPPublicKeyEncryptedData pkEnc
                    = (PGPPublicKeyEncryptedData)pgpEnc;
                if (pkEnc.getKeyID() == privateKey.getKeyID())
                {
                    encData = pkEnc;
                    break;
                }
            }
            if (encData == null)
            {
                throw new IllegalStateException("matching encrypted data not found");
            }
            // build decryptor factory
            PublicKeyDataDecryptorFactory dataDecryptorFactory =
                new JcePublicKeyDataDecryptorFactoryBuilder()
                    .setProvider("BC")
                    .build(privateKey);
            InputStream clear = encData.getDataStream(dataDecryptorFactory);
            byte[] literalData = Streams.readAll(clear);
            clear.close();
            // check data decrypts okay
            if (encData.verify())
            {
                // parse out literal data
                PGPObjectFactory litFact = new JcaPGPObjectFactory(literalData);
                PGPLiteralData litData = (PGPLiteralData)litFact.nextObject();
                byte[] data = Streams.readAll(litData.getInputStream());
                return data;
            }
            throw new IllegalStateException("modification check failed");
        }
    
        private static void elgamalExample()
            throws Exception
        {
            byte[] msg = Strings.toByteArray("Hello, world!");
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH", "BC");
            kpGen.initialize(2048);
            KeyPair kp = kpGen.generateKeyPair();
            PGPKeyPair elgKp = new JcaPGPKeyPair(
                PGPPublicKey.ELGAMAL_ENCRYPT, kp, new Date());
            byte[] encData = createEncryptedData(elgKp.getPublicKey(), msg);
            byte[] decData = extractPlainTextData(elgKp.getPrivateKey(), encData);
            System.out.println("elgamal encryption msg length: " + msg.length + " enc.length: " + encData.length + " dec.length: " + decData.length);
            System.out.println(Strings.fromByteArray(decData));
        }
    
        private static void ecExample()
            throws Exception
        {
            byte[] msg = Strings.toByteArray("Hello, world!");
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
            kpGen.initialize(new ECGenParameterSpec("P-256"));
            KeyPair kp = kpGen.generateKeyPair();
            PGPKeyPair ecdhKp = new JcaPGPKeyPair(PGPPublicKey.ECDH, kp, new Date());
            byte[] encData = createEncryptedData(ecdhKp.getPublicKey(), msg);
            // save encrypted string
            Files.write(Paths.get("pgp-encrypted-string.dat"), encData);
            // load encrypted string
            byte[] encDataLoad = Files.readAllBytes(Paths.get("pgp-encrypted-string.dat"));
            byte[] decData = extractPlainTextData(ecdhKp.getPrivateKey(), encDataLoad);
            System.out.println("ec encryption msg length: " + msg.length + " enc.length: " + encData.length + " dec.length: " + decData.length);
            System.out.println(Strings.fromByteArray(decData));
        }
    
        public static void main(String[] args)
            throws Exception
        {
            Security.addProvider(new BouncyCastleProvider());
            // you need the two files bcpg-jdk15on-165.jar and bcprov-jdk15to18-165.jar to run the example
            System.out.println("Example from Java Cryptography: Tools and Techniques by David Hook & Jon Eaves");
            System.out.println("get source files: https://www.bouncycastle.org/java-crypto-tools-src.zip");
            elgamalExample();
            ecExample();
        }
    }
    

    This is the short output:

    Example from Java Cryptography: Tools and Techniques by David Hook & Jon Eaves
    get source files: https://www.bouncycastle.org/java-crypto-tools-src.zip
    elgamal encryption msg length: 13 enc.length: 601 dec.length: 13
    Hello, world!
    ec encryption msg length: 13 enc.length: 200 dec.length: 13
    Hello, world!
    

    Added: As I now understand you're having a String like

    This string needs an encryption
    

    and you want to encrypt it with a rsa pgp public key:

    -----BEGIN PGP PUBLIC KEY BLOCK-----
    Version: BCPG v1.65
    
    mI0EXr/nDgEEAKhB6ufAB954aBIlNjPCsryzUVLu0qkC/1RtnFHf+J6IVegV8Wi7
    28V074inQcw6o6FTLtFTaLRP4+3eXNATdjGSjrvcP7k+nu50vydugHv43fPuCiZ7
    6gbbMTE9gPiLPA2pS+SmQJnr9hOrD5rzwYP1yNNIsRJ9qmU5NeZyu+szABEBAAG0
    DHRlc3RpZGVudGl0eYicBBABAgAGBQJev+cOAAoJEPBDuyqTbz/gY0YD/R+gDkfe
    qPgNuk6iI2wLSGEeZRXr6Ru1cyG73CRvz7BjCpwWx039AdQzP9gkeo6MEj8Z0c73
    obqEP8NtvvOcwC7+/QiGLTR2mgCsNhk54+iCGsvNbkpkr/rRoYZGyvb+rxui0A61
    DCB1w5hdnyMg2OglFNrkaPfpNjMsTebfF5eS
    =h1+m
    -----END PGP PUBLIC KEY BLOCK-----
    

    and get the encrypted string

    -----BEGIN PGP MESSAGE-----
    Version: BCPG v1.65
    
    hIwD8EO7KpNvP+ABA/9JkOE9PDyS/kr/lZ1Uz+NCSe1JiNcKCXjbsUbvP8CT7Tf1
    cKlgzIz1mQjdpkBtVpVhEnEjmUzFy2UCRKr4b4Wx7/1UL+370CICW5HgMoi5TgTg
    MYRy5I9Uba/+JxcusjWB1JJHP4ofULziXRKLWAoSPLlglZDzSmV88hNo19rl39JZ
    AbMhIS2edM9hHICefL/Yaiq90hGjKMRReVopu2tPUjNLGYP7QABAvWb3WQJMZoYT
    HEsyjHxeyYQylAdYB7pWQA0++Z803iclvM3skN8FBt64ebDkqfxgbhs=
    =je0r
    -----END PGP MESSAGE-----
    

    Now you'd like to decrypt this message with Kleoptatra, online (e.g. https://sela.io/pgp-en/) or in Java with the RSA pgp private key and the password 123456:

    -----BEGIN PGP PRIVATE KEY BLOCK-----
    Version: BCPG v1.65
    
    lQH+BF6/5w4BBACoQernwAfeeGgSJTYzwrK8s1FS7tKpAv9UbZxR3/ieiFXoFfFo
    u9vFdO+Ip0HMOqOhUy7RU2i0T+Pt3lzQE3Yxko673D+5Pp7udL8nboB7+N3z7gom
    e+oG2zExPYD4izwNqUvkpkCZ6/YTqw+a88GD9cjTSLESfaplOTXmcrvrMwARAQAB
    /gMDAhhcE1oF/u8YYExKGLgriK5JpUUSsMFU0AOHP9/zZQr09437V0f/F4J87+9s
    G30lDRikGwynEGRnAvIVwqq2F+iarKGGHCZCRgbyufXS7VK6wE/43lR0kSwA2VIM
    ll/KbQKP1cSZv0rqtJ1tGL7cDHFEwq10gM4Bn75HOKyBzE9oERRKz37noAECsAZn
    xuXGlEB5noqTT00RxsHjBA5Os04CtEz9N+OMrg47IR7AzSQUe90lG2F6W71dhJ6V
    jQaf7D6JFU3dOWPW1eBb5FQhgYF92CFRizJ42lDCiTfl2FQU49MlwLd2ofNneuPo
    aVuPoYUNKwbasyx4fo2vh6rrMyxmncCizMExvh6GIVgYd7EK9s6Gxq/duuOvly4O
    ZAyIY2MOon0bDXxAYR2q/wdQLamnP7rAR4uMu24m/iOuBj6wwTR8v8hhsFFTp/4u
    tebwWzLnPyyBYStnTF5IZ9ZJeVl5S3zdzNcrP9g8yXtItAx0ZXN0aWRlbnRpdHmI
    nAQQAQIABgUCXr/nDgAKCRDwQ7sqk28/4GNGA/0foA5H3qj4DbpOoiNsC0hhHmUV
    6+kbtXMhu9wkb8+wYwqcFsdN/QHUMz/YJHqOjBI/GdHO96G6hD/Dbb7znMAu/v0I
    hi00dpoArDYZOePoghrLzW5KZK/60aGGRsr2/q8botAOtQwgdcOYXZ8jINjoJRTa
    5Gj36TYzLE3m3xeXkg==
    =y/tQ
    -----END PGP PRIVATE KEY BLOCK-----
    

    and get the decrypted string:

    This string needs an encryption
    

    To encrypt/decrypt in Java fortunately there are sample files available in the BouncyCastle Github-Repo: https://github.com/bcgit/bc-java/blob/master/pg/src/main/java/org/bouncycastle/openpgp/examples/. You may need to create a new PGP-keypair using RSA (RSAKeyPairGenerator.java) or ElGamal (DSAElGamalKeyRingGenerator.java). With the generated keys you can encrypt or decrypt using KeyBasedFileProcessor.java and neccessary PGPExampleUtil.java.

    I created the RSA key files with "-a testidentity 123456" as arguments, the encryption is done with "-e -ai plaintext.txt rsa_pub.asc" and the decryption goes with "-d plaintext.txt.asc rsa_secret.asc 123456".