Revisited: PGP Encryption/Decryption in Java

One of the most popular posts on my blog is my article about PGP Encryption and Decryption. I’ve had a lot of great questions as well as responses from user who have even been kind enough to post modifications to the original code to allow it to do more.

I thought I would revisit the post to provide a new version of the code that incorporated what some readers (Mateusz Klos, Juraj, and Sumit in particular) provided.

At the same time I thought I would upgrade the code to the latest library from Bouncy Castle. Yeah, so, boy did they change some things. The latest examples from Bouncy Castle use code that they have deprecated (they didn’t update their examples). There are no examples on how to use the new API’s or how to correct the deprecations. The API does mention (“use this instead”) which is great and more than most projects provide however it was only a part of the solution.

Anyway, after some time I’ve gotten the code to use the latest Bouncy Castle libraries (1.47).

The Code

First is the Util which does the real work. It has been upgraded to 1.47 and contains suggestions from readers. I should point out that two methods are from LockBox Lobs PGP Encryption tools. I didn’t see the point of requiring a rather LARGE library for two little methods so I just put them in here.


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Iterator;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;

public class PGPUtils {

    private static final int   BUFFER_SIZE = 1 << 16; // should always be power of 2
    private static final int   KEY_FLAGS = 27;
    private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[]{
    	PGPSignature.POSITIVE_CERTIFICATION,
    	PGPSignature.CASUAL_CERTIFICATION,
    	PGPSignature.NO_CERTIFICATION,
    	PGPSignature.DEFAULT_CERTIFICATION
    };

    @SuppressWarnings("unchecked")
    public static PGPPublicKey readPublicKey(InputStream in)
    	throws IOException, PGPException
    {

        PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in));

        //
        // we just loop through the collection till we find a key suitable for encryption, in the real
        // world you would probably want to be a bit smarter about this.
        //
        PGPPublicKey publicKey = null;

        //
        // iterate through the key rings.
        //
        Iterator<PGPPublicKeyRing> rIt = keyRingCollection.getKeyRings();

        while (publicKey == null && rIt.hasNext()) {
            PGPPublicKeyRing kRing = rIt.next();
            Iterator<PGPPublicKey> kIt = kRing.getPublicKeys();
            while (publicKey == null && kIt.hasNext()) {
                PGPPublicKey key = kIt.next();
                if (key.isEncryptionKey()) {
                    publicKey = key;
                }
            }
        }

        if (publicKey == null) {
            throw new IllegalArgumentException("Can't find public key in the key ring.");
        }
        if (!isForEncryption(publicKey)) {
            throw new IllegalArgumentException("KeyID " + publicKey.getKeyID() + " not flagged for encryption.");
        }

        return publicKey;
    }

    @SuppressWarnings("unchecked")
	public static PGPSecretKey readSecretKey(InputStream in)
		throws IOException, PGPException
	{

        PGPSecretKeyRingCollection keyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in));

        //
        // We just loop through the collection till we find a key suitable for signing.
        // In the real world you would probably want to be a bit smarter about this.
        //
        PGPSecretKey secretKey = null;

        Iterator<PGPSecretKeyRing> rIt = keyRingCollection.getKeyRings();
        while (secretKey == null && rIt.hasNext()) {
            PGPSecretKeyRing keyRing = rIt.next();
            Iterator<PGPSecretKey> kIt = keyRing.getSecretKeys();
            while (secretKey == null && kIt.hasNext()) {
                PGPSecretKey key = kIt.next();
                if (key.isSigningKey()) {
                    secretKey = key;
                }
            }
        }

        // Validate secret key
        if (secretKey == null) {
            throw new IllegalArgumentException("Can't find private key in the key ring.");
        }
        if (!secretKey.isSigningKey()) {
            throw new IllegalArgumentException("Private key does not allow signing.");
        }
        if (secretKey.getPublicKey().isRevoked()) {
            throw new IllegalArgumentException("Private key has been revoked.");
        }
        if (!hasKeyFlags(secretKey.getPublicKey(), KeyFlags.SIGN_DATA)) {
            throw new IllegalArgumentException("Key cannot be used for signing.");
        }

        return secretKey;
    }

    /**
     * Load a secret key ring collection from keyIn and find the private key corresponding to
     * keyID if it exists.
     *
     * @param keyIn input stream representing a key ring collection.
     * @param keyID keyID we want.
     * @param pass passphrase to decrypt secret key with.
     * @return
     * @throws IOException
     * @throws PGPException
     * @throws NoSuchProviderException
     */
    public  static PGPPrivateKey findPrivateKey(InputStream keyIn, long keyID, char[] pass)
    	throws IOException, PGPException, NoSuchProviderException
    {
        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
        return findPrivateKey(pgpSec.getSecretKey(keyID), pass);

    }

    /**
     * Load a secret key and find the private key in it
     * @param pgpSecKey The secret key
     * @param pass passphrase to decrypt secret key with
     * @return
     * @throws PGPException
     */
    public static PGPPrivateKey findPrivateKey(PGPSecretKey pgpSecKey, char[] pass)
    	throws PGPException
    {
    	if (pgpSecKey == null) return null;

        PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
        return pgpSecKey.extractPrivateKey(decryptor);
    }

    /**
     * decrypt the passed in message stream
     */
    @SuppressWarnings("unchecked")
	public static void decryptFile(InputStream in, OutputStream out, InputStream keyIn, char[] passwd)
    	throws Exception
    {
    	Security.addProvider(new BouncyCastleProvider());

        in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in);

        PGPObjectFactory pgpF = new PGPObjectFactory(in);
        PGPEncryptedDataList enc;

        Object o = pgpF.nextObject();
        //
        // the first object might be a PGP marker packet.
        //
        if (o instanceof  PGPEncryptedDataList) {
            enc = (PGPEncryptedDataList) o;
        } else {
            enc = (PGPEncryptedDataList) pgpF.nextObject();
        }

        //
        // find the secret key
        //
        Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
        PGPPrivateKey sKey = null;
        PGPPublicKeyEncryptedData pbe = null;

        while (sKey == null && it.hasNext()) {
            pbe = it.next();

            sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd);
        }

        if (sKey == null) {
            throw new IllegalArgumentException("Secret key for message not found.");
        }

        InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));

        PGPObjectFactory plainFact = new PGPObjectFactory(clear);

        Object message = plainFact.nextObject();

        if (message instanceof  PGPCompressedData) {
            PGPCompressedData cData = (PGPCompressedData) message;
            PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream());

            message = pgpFact.nextObject();
        }

        if (message instanceof  PGPLiteralData) {
            PGPLiteralData ld = (PGPLiteralData) message;

            InputStream unc = ld.getInputStream();
            int ch;

            while ((ch = unc.read()) >= 0) {
                out.write(ch);
            }
        } else if (message instanceof  PGPOnePassSignatureList) {
            throw new PGPException("Encrypted message contains a signed message - not literal data.");
        } else {
            throw new PGPException("Message is not a simple encrypted file - type unknown.");
        }

        if (pbe.isIntegrityProtected()) {
            if (!pbe.verify()) {
            	throw new PGPException("Message failed integrity check");
            }
        }
    }

    public static void encryptFile(
        OutputStream out,
        String fileName,
        PGPPublicKey encKey,
        boolean armor,
        boolean withIntegrityCheck)
        throws IOException, NoSuchProviderException, PGPException
    {
    	Security.addProvider(new BouncyCastleProvider());

        if (armor) {
            out = new ArmoredOutputStream(out);
        }

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);

        PGPUtil.writeFileToLiteralData(
                comData.open(bOut),
                PGPLiteralData.BINARY,
                new File(fileName) );

        comData.close();

        BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES);
        dataEncryptor.setWithIntegrityPacket(withIntegrityCheck);
        dataEncryptor.setSecureRandom(new SecureRandom());

        PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor);
        encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey));

        byte[] bytes = bOut.toByteArray();
        OutputStream cOut = encryptedDataGenerator.open(out, bytes.length);
        cOut.write(bytes);
        cOut.close();
        out.close();
    }

    @SuppressWarnings("unchecked")
	public static void signEncryptFile(
        OutputStream out,
        String fileName,
        PGPPublicKey publicKey,
        PGPSecretKey secretKey,
        String password,
        boolean armor,
        boolean withIntegrityCheck )
        throws Exception
    {

        // Initialize Bouncy Castle security provider
        Provider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        if (armor) {
            out = new ArmoredOutputStream(out);
        }

        BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES);
        dataEncryptor.setWithIntegrityPacket(withIntegrityCheck);
        dataEncryptor.setSecureRandom(new SecureRandom());

        PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor);
        encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey));

        OutputStream encryptedOut = encryptedDataGenerator.open(out, new byte[PGPUtils.BUFFER_SIZE]);

        // Initialize compressed data generator
        PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
        OutputStream compressedOut = compressedDataGenerator.open(encryptedOut, new byte [PGPUtils.BUFFER_SIZE]);

        // Initialize signature generator
        PGPPrivateKey privateKey = findPrivateKey(secretKey, password.toCharArray());

        PGPContentSignerBuilder signerBuilder = new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(),
                HashAlgorithmTags.SHA1);

        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
        signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);

        boolean firstTime = true;
        Iterator<String> it = secretKey.getPublicKey().getUserIDs();
        while (it.hasNext() && firstTime) {
            PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
            spGen.setSignerUserID(false, it.next());
            signatureGenerator.setHashedSubpackets(spGen.generate());
            // Exit the loop after the first iteration
            firstTime = false;
        }
        signatureGenerator.generateOnePassVersion(false).encode(compressedOut);

        // Initialize literal data generator
        PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
        OutputStream literalOut = literalDataGenerator.open(
            compressedOut,
            PGPLiteralData.BINARY,
            fileName,
            new Date(),
            new byte [PGPUtils.BUFFER_SIZE] );

        // Main loop - read the "in" stream, compress, encrypt and write to the "out" stream
        FileInputStream in = new FileInputStream(fileName);
        byte[] buf = new byte[PGPUtils.BUFFER_SIZE];
        int len;
        while ((len = in.read(buf)) > 0) {
            literalOut.write(buf, 0, len);
            signatureGenerator.update(buf, 0, len);
        }

        in.close();
        literalDataGenerator.close();
        // Generate the signature, compress, encrypt and write to the "out" stream
        signatureGenerator.generate().encode(compressedOut);
        compressedDataGenerator.close();
        encryptedDataGenerator.close();
        if (armor) {
            out.close();
        }
    }

    public static boolean verifyFile(
    	InputStream in,
        InputStream keyIn,
        String extractContentFile)
        throws Exception
    {
        in = PGPUtil.getDecoderStream(in);

        PGPObjectFactory pgpFact = new PGPObjectFactory(in);
        PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();

        pgpFact = new PGPObjectFactory(c1.getDataStream());

        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();

        PGPOnePassSignature ops = p1.get(0);

        PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();

        InputStream dIn = p2.getInputStream();

        IOUtils.copy(dIn, new FileOutputStream(extractContentFile));

        int ch;
        PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));

        PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID());

        FileOutputStream out = new FileOutputStream(p2.getFileName());

        ops.init(new BcPGPContentVerifierBuilderProvider(), key);

        while ((ch = dIn.read()) >= 0)
        {
            ops.update((byte)ch);
            out.write(ch);
        }

        out.close();

        PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
        return ops.verify(p3.get(0));
    }

    /**
     * From LockBox Lobs PGP Encryption tools.
     * http://www.lockboxlabs.org/content/downloads
     *
     * I didn't think it was worth having to import a 4meg lib for three methods
     * @param key
     * @return
     */
    public static boolean isForEncryption(PGPPublicKey key)
    {
        if (key.getAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
            || key.getAlgorithm() == PublicKeyAlgorithmTags.DSA
            || key.getAlgorithm() == PublicKeyAlgorithmTags.EC
            || key.getAlgorithm() == PublicKeyAlgorithmTags.ECDSA)
        {
            return false;
        }

        return hasKeyFlags(key, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
    }

    /**
     * From LockBox Lobs PGP Encryption tools.
     * http://www.lockboxlabs.org/content/downloads
     *
     * I didn't think it was worth having to import a 4meg lib for three methods
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
	private static boolean hasKeyFlags(PGPPublicKey encKey, int keyUsage) {
        if (encKey.isMasterKey()) {
            for (int i = 0; i != PGPUtils.MASTER_KEY_CERTIFICATION_TYPES.length; i++) {
                for (Iterator<PGPSignature> eIt = encKey.getSignaturesOfType(PGPUtils.MASTER_KEY_CERTIFICATION_TYPES[i]); eIt.hasNext();) {
                    PGPSignature sig = eIt.next();
                    if (!isMatchingUsage(sig, keyUsage)) {
                        return false;
                    }
                }
            }
        }
        else {
            for (Iterator<PGPSignature> eIt = encKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING); eIt.hasNext();) {
                PGPSignature sig = eIt.next();
                if (!isMatchingUsage(sig, keyUsage)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * From LockBox Lobs PGP Encryption tools.
     * http://www.lockboxlabs.org/content/downloads
     *
     * I didn't think it was worth having to import a 4meg lib for three methods
     * @param key
     * @return
     */
    private static boolean isMatchingUsage(PGPSignature sig, int keyUsage) {
        if (sig.hasSubpackets()) {
            PGPSignatureSubpacketVector sv = sig.getHashedSubPackets();
            if (sv.hasSubpacket(PGPUtils.KEY_FLAGS)) {
                // code fix suggested by kzt (see comments)
                if ((sv.getKeyFlags() == 0 && keyUsage) == 0) {
                    return false;
                }
            }
        }
        return true;
    }

}

The PGPFileProcessor is a bean based implementation of calling the Util so that you can easily integrate with Spring.


import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;

public class PGPFileProcessor {

    private String passphrase;
    private String publicKeyFileName;
    private String secretKeyFileName;
    private String inputFileName;
    private String outputFileName;
    private boolean asciiArmored = false;
    private boolean integrityCheck = true;

    public boolean encrypt() throws Exception {
        FileInputStream keyIn = new FileInputStream(publicKeyFileName);
        FileOutputStream out = new FileOutputStream(outputFileName);
        PGPUtils.encryptFile(out, inputFileName, PGPUtils.readPublicKey(keyIn), asciiArmored, integrityCheck);
        out.close();
        keyIn.close();
        return true;
    }

    public boolean signEncrypt() throws Exception {
        FileOutputStream out = new FileOutputStream(outputFileName);
        FileInputStream publicKeyIn = new FileInputStream(publicKeyFileName);
        FileInputStream secretKeyIn = new FileInputStream(secretKeyFileName);

        PGPPublicKey publicKey = PGPUtils.readPublicKey(publicKeyIn);
        PGPSecretKey secretKey = PGPUtils.readSecretKey(secretKeyIn);

        PGPUtils.signEncryptFile(
                out,
                this.getInputFileName(),
                publicKey,
                secretKey,
                this.getPassphrase(),
                this.isAsciiArmored(),
                this.isIntegrityCheck() );

        out.close();
        publicKeyIn.close();
        secretKeyIn.close();

        return true;
    }

    public boolean decrypt() throws Exception {
        FileInputStream in = new FileInputStream(inputFileName);
        FileInputStream keyIn = new FileInputStream(secretKeyFileName);
        FileOutputStream out = new FileOutputStream(outputFileName);
        PGPUtils.decryptFile(in, out, keyIn, passphrase.toCharArray());
        in.close();
        out.close();
        keyIn.close();
        return true;
    }

    public boolean isAsciiArmored() {
            return asciiArmored;
    }

    public void setAsciiArmored(boolean asciiArmored) {
            this.asciiArmored = asciiArmored;
    }

    public boolean isIntegrityCheck() {
            return integrityCheck;
    }

    public void setIntegrityCheck(boolean integrityCheck) {
            this.integrityCheck = integrityCheck;
    }

    public String getPassphrase() {
            return passphrase;
    }

    public void setPassphrase(String passphrase) {
            this.passphrase = passphrase;
    }

    public String getPublicKeyFileName() {
            return publicKeyFileName;
    }

    public void setPublicKeyFileName(String publicKeyFileName) {
            this.publicKeyFileName = publicKeyFileName;
    }

    public String getSecretKeyFileName() {
            return secretKeyFileName;
    }

    public void setSecretKeyFileName(String secretKeyFileName) {
            this.secretKeyFileName = secretKeyFileName;
    }

    public String getInputFileName() {
            return inputFileName;
    }

    public void setInputFileName(String inputFileName) {
            this.inputFileName = inputFileName;
    }

    public String getOutputFileName() {
            return outputFileName;
    }

    public void setOutputFileName(String outputFileName) {
            this.outputFileName = outputFileName;
    }

}

Examples

The code is still set up in a bean standard manner to support easy integration with Spring. The examples are very simple but should give more than enough guidance for basic usage. If there is any confusion please ask.

public class Tester {

	private static final String PASSPHRASE = "test";

	private static final String DE_INPUT = "src/test/x.pgp";
	private static final String DE_OUTPUT = "src/test/x.txt";
	private static final String DE_KEY_FILE = "src/test/secring.skr";

	private static final String E_INPUT = "src/test/x.txt";
	private static final String E_OUTPUT = "src/test/x.pgp";
	private static final String E_KEY_FILE = "src/test/pubring.pkr";


	public static void testDecrypt() throws Exception {
		PGPFileProcessor p = new PGPFileProcessor();
		p.setInputFileName(DE_INPUT);
		p.setOutputFileName(DE_OUTPUT);
		p.setPassphrase(PASSPHRASE);
		p.setSecretKeyFileName(DE_KEY_FILE);
		System.out.println(p.decrypt());
	}

	public static void testEncrypt() throws Exception {
		PGPFileProcessor p = new PGPFileProcessor();
		p.setInputFileName(E_INPUT);
		p.setOutputFileName(E_OUTPUT);
		p.setPassphrase(PASSPHRASE);
		p.setPublicKeyFileName(E_KEY_FILE);
		System.out.println(p.encrypt());
	}
}

Conclusion

That’s it. I hope you don’t feel I’m cheating out of a post for this month but I thought this would be very helpful since so many people read the original post.

I should also warn that this code is not in my production env at work because we haven’t upgraded our encryption libraries yet. This means there could be a bug lurking in there somewhere. If you find one please tell me and I’ll correct it.

Question: (Lets see if anyone reads down this far): Would you prefer that I bundle the code into a little util jar to d/l to make it easier to use?

About sseaman

Connect with me on Google+
This entry was posted in Java, Programming and tagged , , . Bookmark the permalink.

117 Responses to Revisited: PGP Encryption/Decryption in Java

  1. Pingback: PGP Encryption/Decryption in Java | Waiting for Wit

  2. Jevo says:

    Would it be possible to create a .gpg file using this method and/or use a GPG key? From the research I have done, the PGP and GPG are compatible but does this code work for GPG as well?

  3. merqurious says:

    Instead of using
    private static final String DE_KEY_FILE = “src/test/secring.skr”;
    private static final String E_KEY_FILE = “src/test/pubring.pkr”;

    Can we use the ones that are maintaing by gpg in
    C:\Users\\AppData\Roaming\gnupg\pubring.gpg
    C:\Users\\AppData\Roaming\gnupg\secring.gpg

    Appreciate your help.

    • sseaman says:

      As I’ve never used gpg I can’t really say. My suggestion is to simply try it and see if it works.

      • merqurious says:

        I have encrypted and signed a file on a linux server1
        I have the sender’s public key (used for encrypting) in server2’s pubring.gpg and the recipient’s secret key (used for signing) on server2 secring.gpg

        I can decrypt the encrypted file using gpg command line on server2.
        On server2 when I run bouncy castle code
        it gives an error

        org.bouncycastle.openpgp.PGPException: Encrypted message contains a signed message – not literal data.
        at pgpencryption.PGPUtils.decryptFile(PGPUtils.java:252)
        at pgpencryption.PGPFileProcessor.decrypt(PGPFileProcessor.java:57)
        at pgpencryption.Tester.testDecrypt(Tester.java:23)
        at pgpencryption.Tester.main(Tester.java:40)
        Process exited with exit code 0.

        • sseaman says:

          Please see my comment to Jevo that states that the code does not appear to work with GPG.

          Sorry.

          • Aaron Hart says:

            Stay tuned! I’m building a similar PGP encryption/decryption DataFormat to use with Apache Camel. It is very compatible with GnuPG as it is able to use the *.gpg key files and can successfully (a) decrypt messages generated by GnuPG and (b) generate encrypted messages which can be decrypted using GnuPG. The DataFormat can decrypt not only simple encrypted messages, but also messages that are only signed, messages that are encrypted AND signed, and messages that encrypted for more than one recipient (or any combination of the above). As it is built for Apache Camel, it can not only handle Encrypted Files but any kind of input generated by Queues, Beans, or any other endpoint supported by Apache Camel 2.9.2. Look for a patch to the Apache Camel sources, as I will hopefully be able to convince Claus Ibsen to replace the current Apache Camel PGP DataFormat with the one I am building. Feel free to e-mail me with requirements, questions, concerns, or files to test encryption/decryption against.

            Cheers!

            -Aaron (MerlinOfMines)

            • Scarta says:

              Hi Aaron were you able to build this solution for Camel?

            • Balaji says:

              Hi Aaron. Were you able to build this solution? If so, please post the url link.

            • Balaji says:

              Hi,

              I am getting following Class Cast exception while decrypting the encrypted file. Let me know your thoughts.

              Exception in thread “main” java.lang.ClassCastException: org.bouncycastle.crypto.params.DSAPrivateKeyParameters cannot be cast to org.bouncycastle.crypto.params.ElGamalKeyParameters
              at org.bouncycastle.crypto.engines.ElGamalEngine.init(Unknown Source)
              at org.bouncycastle.crypto.encodings.PKCS1Encoding.init(Unknown Source)
              at org.bouncycastle.crypto.BufferedAsymmetricBlockCipher.init(Unknown Source)
              at org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory.recoverSessionData(Unknown Source)
              at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
              at gov.nysels.pgp.PGPUtility.decryptFile(PGPUtility.java:302)
              at gov.nysels.pgp.Test.main(Test.java:69)

              In addition to above error, I have some more queries. While encrypting the file in encryptFile method, the program is using symmetric key tag algorithm to encrypt the file. But while decrypting the file, the program is trying to use asymmetric key tag algorithm (Elgamal .. etc)!!!! I am bit confused here. Can someone explain me clearly?

              During Encryption ::
              BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES);

              During decryption ::

              clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));

  4. satya says:

    I am trying to encrypt using the same code with a GnuPG v1.4.10 public key, when i run the program this method ‘isMatchingUsage(PGPSignature sig, int keyUsage)’ returns false and readpublickey method throws error message illlegalargument key id is not flagged for encryption.
    Can you pl. let me know can we use this program for encrypt/decrypt using GnuPG v1.4.10 keys

    • sseaman says:

      Code does not work with GPG. Sorry

      • kzt says:

        hi , it is some wrong with this method

        	private static boolean isMatchingUsage(PGPSignature sig, int keyUsage) {
        		if (sig.hasSubpackets()) {
        			PGPSignatureSubpacketVector sv = sig.getHashedSubPackets();
        			if (sv.hasSubpacket(PGPUtilRevi.Key_Flags)) {
        				if ((sv.getKeyFlags()  &amp; keyUsage) == 0) {
        					return false;
        				}
        			}
        		}
        		return true;
        	}
        
        • sseaman says:

          Why do you ask? Is it throwing an error when you are using it?

          • kzt says:

            hi sseaman,

            yes, I have changed the condition inside isMatchingUsage method.

            if ((sv.getKeyFlags() == 0 && keyUsage == 0)) {
            return false;
            }

            • jskhun says:

              Thanks.. Code work with GPG well.

            • lrswan says:

              Just ran into same problem, tracked down to same code. Correction works. Thanks.

            • lrswan says:

              Also found that this works, and wonder if it may not be more accurate?

              if ((PGPUtils.KEY_FLAG & keyUsage) == 0) {
               return false;
               }
              
            • Eevrt says:


              if ((sv.getKeyFlags() == 0 && keyUsage) == 0)

              This code doesn’t make any sense. The problems here are 1) the double & and 2) sv.getKeyFlags() == 0.

              The fix by kzt makes even less sense to me (but I might be missing something):

              if (sv.getKeyFlags() == 0 && keyUsage == 0)

              Why would keyUsage be 0 in the first place? and getKeyFlags is only 0 if no flags are set. This condition makes no sense.

              What this code should do is to check whether the keyflags of the current subpacket include the flags that the user wants to check for. So a logical and on the available flags should do it:


              if ((sv.getKeyFlags() & keyUsage) == 0) {
              return false;
              }

              That’s the original code. Nothing wrong with it, as far as I see. Or am I missing something?

              • moloch says:

                The fix makes no sense, but the test with the logical AND returns false with a good encryption key.

                IMHO the quick fix is to avoid the check and trust the user with the key.

      • Kapil says:

        Hi, A great article indeed!!
        I understand that this code will not work with GnuPG. Can you help me with the code which will work with it. Or, a tool to generate key on windows7 with which the code will work?

        How can I make this code work with PGP with my self generated keys?

        Thanks. Kapil

  5. nhanntv says:

    When i encrypt x.txt, it’s ok. But when i decrypt x.pgp. It’s error.

    Exception in thread "main" org.bouncycastle.openpgp.PGPException: checksum mismatch at 0 of 2
    	at org.bouncycastle.openpgp.PGPSecretKey.extractKeyData(PGPSecretKey.java:451)
    	at org.bouncycastle.openpgp.PGPSecretKey.extractPrivateKey(PGPSecretKey.java:580)
    	at PGPUtils.findPrivateKey(PGPUtils.java:181)
    	at PGPUtils.findPrivateKey(PGPUtils.java:164)
    	at PGPUtils.decryptFile(PGPUtils.java:218)
    	at PGPFileProcessor.decrypt(PGPFileProcessor.java:54)
    	at Tester.testDecrypt(Tester.java:19)
    	at Main.main(Main.java:12)
    

    what must i do?

  6. Herc says:

    Hi,
    I am encountering a weird issue. I have encrypted the file bouncy castle code you have shared and trying to decrypt it using pgp 5.6.8 in command line. It asks for the passphrase and when i enter it nothing happens. I have tried using the command “pgp -m ” but nothing gets displayed on the screen.

    I have added the command line statements for your reference.

    $ pgp -m abcd.pgp
    Pretty Good Privacy(tm) Version 6.5.8
    (c) 1999 Network Associates Inc.
    Uses the RSAREF(tm) Toolkit, which is copyright RSA Data Security, Inc.
    Export of this software may be restricted by the U.S. government.

    File is encrypted. Secret key is required to read it.

    Key for user ID: Herc Test
    2048-bit RSA key, Key ID 0x7365EF55, created 2010/06/04, expires 2040/05/27
    Key can sign.
    You need a pass phrase to unlock your secret key.

    Enter pass phrase:
    $

    Any help would be highly appreciated. Thanks

  7. George H says:

    Great article, I found other articles on the net dealing with bouncy castle PGP cryptography but yours is the most complete. Excelent job providing a full and well rounded example 🙂

    I’ve noticed that the PGPSecretKey() constructor you are using is marked as deprecated by bouncy castle. Then again when I looked in the sourcecode of BC… the non-deprecated constructors end up using the deprecated ones and the compiler still complains 😛 hehe

  8. George H says:

    You have a “signEncrypt()” method but not a “verifyDecrypt()” method. Though PGP successfully verifies and decrypts the output of signEncrypt. Is there a way to do it?

    • sseaman says:

      I’m sure there is but i don’t know if off the top of my head. If you come up with something would you mind posting it?

      • Joseph says:

        Hi,
        Your code is extremely helpful. However while trying to use your verifyFile method, it throws the below exception.

        Exception in thread “main” java.lang.ClassCastException: org.bouncycastle.openpgp.PGPEncryptedDataList cannot be cast to org.bouncycastle.openpgp.PGPCompressedData
        at ironmountain.talend.pgp.PGPUtils.verifyFile(PGPUtils.java:392)
        at ironmountain.talend.pgp.PGPFileProcessor.verifyDecrypt(PGPFileProcessor.java:69)
        at ironmountain.talend.pgp.Tester.main(Tester.java:51)

        After debugging, I was able to figure out that the exception is thrown at below line. Could you please help?

        PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();

        • sseaman says:

          Looks like the library either changed or something else is going on further up in your code. Try skipping the object till it is an instanceof PGPCompressedData

    • tom says:

      I think the method ‘verifyFile’ in PGPUtils does exactly that. However when i run it on an encrypted file i get a Classcast Exception while executing it.

      PGPOnePassSignatureList p1= (PGPOnePassSignatureList)pgpFact.nextObject();
      -> Tries to cast to a PGPOnePassSignatureList but a ‘PGPEncryptedDataList’ is insided…

  9. George H says:

    I’ll try, because I am in need to of encrypting+signing a message and then on the other side to decrypt and verify. Also like in PGP you can add multiple recipients when you encrypt a message (you need their public keys for that too).

    I’ll see what I can do and if I get something working i’ll be sure to post it here.

  10. Gunasekhar says:

    Hi sseaman,

    I very well appreciate your time and effort in writing this code. I have some basic doubts, pardon me if I sound too illiterate. From this link (http://www.pgpi.org/doc/pgpintro/) I understood that PGP encryption uses a random passphrase to encrypt the data and then the passphrase is also encrypted using the public key. These two are sent to the receiver who first decrypts the passphrase and then decrypts the data using the passphrase.

    Now here is my doubt – how are we going to retrieve this ecrypted passphrase from the file sent by the client? Where is that part in this code?

  11. tom says:

    Hi,

    tried out your code however initially the encryption method is not working in my case. I have downloaded PGP and created a key. Now when i try to encrypt something with it but the method ‘isMatchingUsage’ always returns false – which is wrong since the key is explicitly made for encryption. Maybe the the methods you copied from LockBox are buggy. If I comment out the ‘isForEncryption’ check then everything is encrypted fine…

  12. tom says:

    Made a minor improvemnt to the decryption part which is otherwise very, very slow and this is due to the follwing (higly inefficient stream handling) in the decryptFile(…) method:

    //needs 243 seconds to write a 22MB file!

    while ((ch = unc.read()) >= 0) {
    out.write(ch);
    }

    replaced it with (from apache commons io library)
    //needs 282ms = 0,2 seconds!

    IOUtils.copy(unc, out);

  13. Dirar says:

    Hi guys,

    thanks for posting this code, it really helps! I’ve had a look at the code and it seems that it only encrypts/decrypts files. In the PGPUtils there is the method encryptFile() which calls Bouncy Castle functions to do the real work. So, if I want to encrypt/descrypt String messages in memory without writing anything to disk, are there functions to do that for me?

    • Dirar says:

      Just a quick clarification about my comment above.

      I had the Decrypt method all in memory without the need to write to a file, but I don’t seem to be successful in finding a Bouncy Castle encrypt method that deals with a String rather than an input file and give me the byte[] as output. Specifically, it is in the encryptFile() where I have to write the data to a file using PGPEncryptedDataGenerator object.

      Hope this is clearer.. Thanks!

  14. Dinil Mithra says:

    Please find below link for generating private and public keys
    https://www.igolder.com/pgp/generate-key/

  15. Dinil Mithra says:

    Above code works with below jar files

    Commons-io-2.4.jar
    bcpg-jdk15on-147.jar
    bcprov-jdk15on-147.jar

  16. Gunasekhar says:

    Hi I have one more question.
    In your code you had set asciiArmored=false for encryption. To see how it behaves I encrypted files with asciiArmored=true and tried to decrypt them on the same machine as well as on a different machine with the same key pair (encrypted with public key on machine A and trying to decrypt on machine B). But I always get “premature end of stream in PartialInputStream” error.

    Am I missing something here?

  17. hari says:

    I am getting following exception with some of the files…any clue would be helpful…

    java.io.EOFException: Unexpected end of ZIP input stream
    at org.bouncycastle.openpgp.PGPCompressedData$2.fill(Unknown Source)
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158)
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:122)
    at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
    at org.bouncycastle.bcpg.BCPGInputStream$PartialInputStream.read(Unknown Source)
    at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
    at com.wellmanage.gtsfi.ftp.server.ambs.encdec.EncryptionDecryptionUtils.decryptFile(EncryptionDecryptionUtils.java:272)
    at com.wellmanage.gtsfi.ftp.server.encrydecry.TestEncDecManagerImpl.testDecrypt(TestEncDecManagerImpl.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at junit.framework.TestCase.runTest(TestCase.java:168)
    at junit.framework.TestCase.runBare(TestCase.java:134)
    at junit.framework.TestResult$1.protect(TestResult.java:110)
    at junit.framework.TestResult.runProtected(TestResult.java:128)
    at junit.framework.TestResult.run(TestResult.java:113)
    at junit.framework.TestCase.run(TestCase.java:124)
    at junit.framework.TestSuite.runTest(TestSuite.java:232)
    at junit.framework.TestSuite.run(TestSuite.java:227)
    at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:81)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

  18. Kris Jurka says:

    The code in decryptFile at line 218 where it loops around trying to find the private key doesn’t work if the private key is not found for the first loop iteration. Because keyIn is passed as an InputStream, once it is consumed, it’s gone. A PGPSecretKeyRingCollection must be constructed and retained to be able to find the private key in subsequent loop iterations.

    • jskhun says:

      “Secret key for message not found.” problem Solved!! Thanks!!

    • Ramon Michael says:

      Thank you Kris, your suggestion fixed my issue as well. I changed the code to:

      while (sKey == null && it.hasNext()) {
      pbe = it.next();
      sKey = findPrivateKey(new ByteArrayInputStream(pgpPrivateKey.getBytes()), pbe.getKeyID(), passwd);
      }

  19. Ramon Michael says:

    Thank you so much for this complete code, not found anywhere else. I am getting an exception when I try to decrypt a PGP encrypted file from below call to pbe.verify(). So this is not from your code. It seems to work fine without this so I’m wondering if I really need it. Exception is also below. I’m using Maven for the dependencies and I’m thinking the maven repo is not updated correctly. Thanks for any insight.

    if (pbe.isIntegrityProtected()) {
    if (!pbe.verify()) {
    throw new PGPException(“Message failed integrity check”);
    }
    }

    Exception in thread “Thread-1” java.lang.NoSuchMethodError: org.bouncycastle.util.Arrays.constantTimeAreEqual([B[B)Z
    at org.bouncycastle.openpgp.PGPEncryptedData.verify(Unknown Source)
    at PGPUtils.decryptFile(PGPUtils.java:257)

    • arjun says:

      hi i got the same error, have you fixed it by any chance? can we remove the if condition there? Can you please reply back.

  20. Balaji says:

    Hi,

    I am getting following Class Cast exception while decrypting the encrypted file. Let me know your thoughts.

    Exception in thread “main” java.lang.ClassCastException: org.bouncycastle.crypto.params.DSAPrivateKeyParameters cannot be cast to org.bouncycastle.crypto.params.ElGamalKeyParameters
    at org.bouncycastle.crypto.engines.ElGamalEngine.init(Unknown Source)
    at org.bouncycastle.crypto.encodings.PKCS1Encoding.init(Unknown Source)
    at org.bouncycastle.crypto.BufferedAsymmetricBlockCipher.init(Unknown Source)
    at org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory.recoverSessionData(Unknown Source)
    at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
    at gov.nysels.pgp.PGPUtility.decryptFile(PGPUtility.java:302)
    at gov.nysels.pgp.Test.main(Test.java:69)

    In addition to above error, I have some more queries. While encrypting the file in encryptFile method, the program is using symmetric key tag algorithm to encrypt the file. But while decrypting the file, the program is trying to use asymmetric key tag algorithm (Elgamal .. etc)!!!! I am bit confused here. Can someone explain me clearly?

    During Encryption ::
    BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES);

    During decryption ::

    clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));

  21. I am newbie to PGP so forgive me for basic questions. I tested your code and it works fine for me. I have one question based on my limited knowledge:

    i) How receiver and sender share the PASSPHRASE. This is the same PASSPHRASE which is used to generate the keys. Can you please explain how it is exchanged between sender and receiver?
    ii) How PGP public/private keys are different from standard public/private keys? I initially generated keys using openssl but it did not work. I had to use the keys mentioned in the link. I know it is not related to this post but any link to this information will be helpful.

    Thanks again for a consolidated example. It is very helpful.

  22. Naveen says:

    Hi,

    I used same code to encrypt the file and got encrypted successfully.

    Now i want to decrypt the same file in terminal using pgp command.
    Can you please help me how i can achieve this.

    (To encrypt the file i used the public key as same as what i generated using pgp -kg.)

  23. Rishi says:

    Hi,
    How to get the encrypted file and set it in the MessageExchange without writing the encrypted file to a File in the directory. I tried removing the cOut.write(bytes) code but the encryptedDataGenerator.open(out, bytes.length) is also writing the File to the Output Directory. How to avoid this by storing just the Encrypted File which is in InputStream/OutputStream and set it in the body of the exchange.
    Any pointers would be extremely helpful.

    Thanks in Advance.

  24. Varun says:

    Hi,

    I cannot find the bouncycastle 1.47 libraries in their site. and no hits online as well. Do you know any location where I can get those?

  25. Dr Old Guy says:

    I can’t thank you enough for this marvelous post (and prior post)! I had to convert an existing gpg (Unix) process to Java. By following the steps and using the sample code the process worked the first time! You really saved my bacon!

  26. Self_Described_Newb says:

    I also want to just continue the thanks for this amazing post! Was for sure worth the revision! I mean, well, I’ve now read both and for sure!

    I have not implemented the code, and will likely use modified versions. If I have any issues I might write back.

    But the way you’ve written these posts and the way you make it easy for a … a really crappy programmer to read.

    I’ll be reading this blog in hopes of making myself less crappy ! 😀

    Thanks a lot again!!!

  27. Abhinav says:

    Thanks for your insightful post. I was looking for a good example on PGP and your blog hit the spot. Thanks again!

    One question though, is there a way to use bouncycastle PGP API to sign and encrypt emails? I’ve looking for a combination of javamail and bcpg API but could not find anything out there. The Cryptix project is dead and javamail-crypto API uses Cryptix, not bcpg.

    Any suggestions?

    • sseaman says:

      Nothing quick off the top of my head. Integration between the PGP api’s and java.mail shouldn’t be that difficult though. You just need to covert the body of the mail to a byte[] and basically pump it into the crypto…

  28. angrysoul says:

    Bug: A message can only be decrypted if we have the secret key corresponding to the first key listed in the PGP stream of the message.

    Hello,

    I’ve been using your code for a while. Thanks for your contribution.

    However, I just came across a potential bug. In the decrypt() method, the following code attempts to find the secret key to use to decrypt the message:

    while (sKey == null &amp;&amp; it.hasNext()) {
                pbe = it.next();
                sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd);
    }
    

    inside the findPrivateKey method, keyIn (which is an InputStream) is read into a PBESecretKeyDecryptor. The problem is that it will work perfectly on the first pass in the loop, but no key will ever be found in any of the subsequent passes, since the keyIn stream will already have been consumed.

    I’ve fixed the issue by changing the code to

    PGPSecretKeyRingCollection lKeyRing = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
    	
    while (sKey == null &amp;&amp; it.hasNext()) {
    	pbe = it.next();
    	sKey = findPrivateKey(lKeyRing, pbe.getKeyID(), passwd);
    }
    

    (Naturally, I also slightly altered findPrivateKey(…) so the signature fits)

    • Julian Montenegro says:

      Hello.
      For this reason, when I run the project, this one return the exception “encrypted file empty”?

      • sseaman says:

        Sounds like the file you are passing in doesn’t have any data or content…

        • Julian Montenegro says:

          Thanks!
          The file not is empty. I can decrypt the same file in this web site :http://www.igolder.com/
          The process that I do is: A partner encrypt a file, I do not know how him does that. I use your code and its return the exception “encrypted file empty”

          P.D. I have used your code to encrypt and decrypt in a successful way.

  29. damien says:

    Hi, thanks for this nice piece of code. Works like a charm.
    Now, I have a question regarding pgp decryption and maybe you can help me with that : i am looking for a way to get the size of the original document before proceeding to decryption ? I’d like to make the decryption ouput into a in-memory outputstream (namely ByteArrayOutputStream) but want first to assure i have enough memory in my JVM to proceed, so that it doesn’t end in a OutOfMemoryException. Thank you.

    • sseaman says:

      No good way that I know of. Encryption does more than just encrypt, it fundamentally changes the file so that unless the original size is stored in metadata, the only way to get it is from decompression.

      • damien says:

        Thank you for your answer.
        Do you mean that document original size is not a mandatory meta-info ? Well, too bad for me. Anyway …Talking about those metadata, have you got any clue to read them through the BoucyCastle API ? To speak frankly, i’ve been looking for it in the javadoc without much success.

        • sseaman says:

          They don’t really make much available (as the whole idea is to be as secure as possible). I did find a link on how you can mine some information: http://tech.michaelaltfield.net/2013/10/19/analyzing-pgp-content/

          • damien says:

            Thank you for this link.
            Well, it seems the size of the original document is not part of the available infos.
            Annyway, i suppose i can do as you first suggested : write some sort of NullOutputStream implementation that will just count the total amount of bytes written without writing anything in memory.
            It’s twice the decryption cost, but i don’t think i have much choice here.
            Thanks again.

  30. Ali says:

    I have tried your code, and I am trying to decrypt PGP file but I am getting following exception:

    Exception in thread “main” org.bouncycastle.openpgp.PGPException: Encrypted message contains a signed message – not literal data.
    at com.riyadbank.utility.encryption.PGPUtilCustom.decryptFile(PGPUtilCustom.java:250)
    at com.riyadbank.utility.encryption.PGPFileProcessor.decrypt(PGPFileProcessor.java:56)
    at com.riyadbank.utility.encryption.PGPFileProcessor.testDecrypt(PGPFileProcessor.java:138)
    at com.riyadbank.utility.encryption.PGPFileProcessor.main(PGPFileProcessor.java:130)

  31. Babitha says:

    After days of sifting through all the codes providing encryption and decryption and messy codes, finally this part of code feels like a life saver. Thanks a lot for this. Works like a charm 🙂

  32. greg says:

    Any restrictions on using this code in our product? I was going to credit this page at the top of the class in our source code (which is released).

  33. Andres G says:

    The ‘if’ condition in the line 489: if ((sv.getKeyFlags() == 0 && keyUsage) == 0) …
    I’m afraid it’s not ok, because a misplacing in the parenthesis. I think it should be something like this:

    if ((sv.getKeyFlags() == 0) && (keyUsage == 0))

    Do you agree? Or am I soooo wrong?

  34. Shivani says:

    Hi, Thanks a lot for the code. It has helped me a lot. I am able to encrypt and decrypt files by generating public and private keys from http://www.igolder.com/ website. but now I am stuck at a point. I have a requirement for using puttygen.exe for generating keys. I am trying but the code is not able to read public key for encryption. I googled and found this article http://www.symantec.com/business/support/index?page=content&id=TECH149003 . this clearly states that I need to import my putty generated SSH key into PGP server. I dont know how this should be done. can you please help me out in this? we are following 32 bit PGP encryption.

    I really appreciate your help in advance.

    Regards
    Shivani

    • sseaman says:

      Well, the code in this article doesn’t involve a PGP server, it’s just an encrypt/decrypt method. What the article is most likely talking about is a centralized keystore which I’m not going to be able to help you with unfortunately. Sorry.

      • Shivani says:

        Thanks a lot sseaman for your reply. Can you please suggest me some tool for creating PGP public and private keys, that would work with your code. I don’t want to use online key generation methods. Waiting for your reply.

        Regards
        Shivani

  35. Mohini says:

    can we set an expiry to the keys generated by your code.if yes please suggest me how to do it?

  36. Pingback: Getting BouncyCastle to decrypt a GPG-encrypted message - Technology

  37. slwong says:

    Hi,

    I have a question at here. I was trying to decrypt a file by using the code that same as yours. But I keep hitting such error (below):

    Caused by: java.lang.NoSuchMethodError: org.bouncycastle.util.Arrays.constantTimeAreEqual([B[B)Z>
    at org.bouncycastle.openpgp.PGPEncryptedData.verify(Unknown Source)

    Fyi, I’m using the bcpg-jdk14-1.47.jar and bcprov-jdk-1.47.jar libraries. One funny thing is, I’m able to use the method in my own local testing. Whenever I push my code changes to weblogic server (using jdk1.7), it will keep prompt with above error message.

    I’ve been asking Mr.Google for advice/help but in the end I get nothing from him. I also saw someone asking the same question at here but with no reply or answers. So, if any of you guys got any idea, please do advice me. Thanks!

    • sseaman says:

      If you push it to weblogic and this happens, odds are that weblogic has a different version of the library being loaded in its war. You’ll need to override with your version or use their version.

  38. Nagaraju says:

    Hi,
    I am trying to implement the same in my environment and i am getting the below error at line

    Object o= pgpF.nextObject();

    Error :

    Exception in thread “main” java.lang.SecurityException: class “org.bouncycastle.util.io.TeeInputStream”‘s signer information does not match signer information of other classes in the same package
    at java.lang.ClassLoader.checkCerts(ClassLoader.java:805)
    at java.lang.ClassLoader.preDefineClass(ClassLoader.java:486)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:624)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:614)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:305)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:246)
    at org.bouncycastle.openpgp.PGPEncryptedDataList.(Unknown Source)
    at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source)
    at com.lametro.pgp.PGPUtils.decryptFile(PGPUtils.java:200)
    at com.lametro.pgp.PGPFileProcessor.decrypt(PGPFileProcessor.java:61)
    at com.lametro.pgp.test.main(test.java:11)
    Process exited with exit code 1.

    Could you please help me

    Regards,
    Nagaraju

  39. Cris says:

    Anyone else seeing “The constructor PGPPublicKeyRingCollection(InputStream) is undefined”?

  40. Zhan says:

    I made a small change to encryptFile method. By using this approach you could be able to encrypt an input file on a fly without outputting to the flat file.

        public static byte[] encryptFile(OutputStream out, String fileName,
                                       PGPPublicKey encKey, boolean armor,
                                       boolean withIntegrityCheck) throws IOException,
                                                                          NoSuchProviderException,
                                                                          PGPException {
            Security.addProvider(new BouncyCastleProvider());
    
            if (armor) {
                out = new ArmoredOutputStream(out);
            }
    
            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
            PGPCompressedDataGenerator comData =
                new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
    
            PGPUtil.writeFileToLiteralData(comData.open(bOut),
                                           PGPLiteralData.BINARY,
                                           new File(fileName));
    
            comData.close();
    
            BcPGPDataEncryptorBuilder dataEncryptor =
                new BcPGPDataEncryptorBuilder(PGPEncryptedData.TRIPLE_DES);
            dataEncryptor.setWithIntegrityPacket(withIntegrityCheck);
            dataEncryptor.setSecureRandom(new SecureRandom());
    
            PGPEncryptedDataGenerator encryptedDataGenerator =
                new PGPEncryptedDataGenerator(dataEncryptor);
            encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey));
    
            byte[] bytes = bOut.toByteArray();
            /*
            OutputStream cOut = encryptedDataGenerator.open(out, bytes.length);
            cOut.write(bytes);
            cOut.close();
            out.close();
            */
    
            // how encrypt an input file without outputting to the flat file
            ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
            Object localObject = localByteArrayOutputStream;
            if (armor) {
                localObject = new ArmoredOutputStream((OutputStream)localObject);
            }
            
            OutputStream localOutputStream = encryptedDataGenerator.open((OutputStream)localObject,
                                                    bytes.length);
            localOutputStream.write(bytes);
            localOutputStream.close();
            
            // your encrypted bytes array 
            return localByteArrayOutputStream.toByteArray();
            
            //For validation purposes
    //        String currPath = System.getProperty("user.dir");
    //        OutputStream outNew = new FileOutputStream(currPath.substring(0, currPath.indexOf("PGPProject")).concat("dummy.pgp")); 
    //        outNew.write(localByteArrayOutputStream.toByteArray());
    //        outNew.close();
        }
    
  41. Adam Decatur says:

    Hello,

    I have the following line in my code and the keyRingCollection is empty after the line is executed:

    PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), null);

    What does this mean if the keyRingCollection is empty?

    “in” is an InputStream to a TIFF file. I am using Bouncy Castle 1.54.

    Thank you!

    Adam

  42. Anil Nayak says:

    Exception in thread “main” org.bouncycastle.openpgp.PGPException: Encrypted message contains a signed message – not literal data.
    at com.test.encryption.PGPUtils.decryptFile(PGPUtils.java:267)
    at com.test.encryption.PGPFileProcessor.decrypt(PGPFileProcessor.java:55)
    at com.test.encryption.Tester.testDecrypt(Tester.java:22)
    at com.test.encryption.Tester.main(Tester.java:36)

  43. Vinícios says:

    I have a compilation problem in this line:
    PGPObjectFactory pgpF = new PGPObjectFactory(in);

    This construtor requires a second argument with the type “KeyFingerPrintCalculator”.

    Anybody had the same problem?

  44. Vinícios says:

    Right, thank you.

    Do you know another example with the new libs?

  45. Muhammad Amir Hanif says:

    The code works when I am passing PassPhrase but is it possible without passing Passphrase for signEncryption

Leave a Reply

Your email address will not be published. Required fields are marked *