How can I get BouncyCastle to decrypt a GPG-encrypted message?
I created a GPG key pair on the CentOS 7 command line using gpg --gen-key . I chose RSA RSA as the encryption types and exported the keys using gpg --export-secret-key -a "User Name" > /home/username/username_private.key and gpg --armor --export 66677FC6 > /home/username/username_pubkey.asc
I can import username_pubkey.asc into a remote Thunderbird client with another email account and successfully send an encrypted email to username@mydomain.com. But when my Java / BouncyCastle code running on mydomain.com tries to decrypt GPG encoded data, it produces the following error:
org.bouncycastle.openpgp.PGPException: Encrypted message contains a signed message - not literal data.
If you look at the code below, you will see that it corresponds to a line in PGPUtils.decryptFile() , which indicates else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");
The original code for this is taken from the blog entry at this link , although I made minor changes to force it to compile in Eclipse Luna with Java 7. The user of the linked blog reported the same error, and the author of the blog replied that he did not work with GPG. So how do I fix this so that it works with GPG?
The Java decryption code is triggered when the GPG-encoded file and the GPG private key are passed to Tester.testDecrypt() as follows:
Tester.java contains:
public InputStream testDecrypt(String input, String output, String passphrase, String skeyfile) throws Exception { PGPFileProcessor p = new PGPFileProcessor(); p.setInputFileName(input);//this is GPG-encoded data sent from another email address using Thunderbird p.setOutputFileName(output); p.setPassphrase(passphrase); p.setSecretKeyFileName(skeyfile);//this is the GPG-generated key return p.decrypt();//this line throws the error }
PGPFileProcessor.java includes:
public InputStream 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());//error thrown here in.close(); out.close(); keyIn.close(); InputStream result = new FileInputStream(outputFileName);//I changed return type from boolean on 1/27/15 Files.deleteIfExists(Paths.get(outputFileName));//I also added this to accommodate change of return type on 1/27/15 return result; }
PGPUtils.java includes:
/** * 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); //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html PGPObjectFactory pgpF = new JcaPGPObjectFactory(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)); //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html PGPObjectFactory plainFact = new JcaPGPObjectFactory(clear); Object message = plainFact.nextObject(); if (message instanceof PGPCompressedData) { PGPCompressedData cData = (PGPCompressedData) message; //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html PGPObjectFactory pgpFact = new JcaPGPObjectFactory(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");} } } /** * 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 { //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html PGPSecretKeyRingCollection pgpSec = new JcaPGPSecretKeyRingCollection(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); }
The full code for all three Java files can be found on the file sharing site by clicking this link .
A full stack trace for the error can be found by clicking on this link .
For reference, the GUI instructions for encrypting with the Thunderbird remote sender are shown in the following screenshot:

I read a lot of posts and links about this. In particular, this other SO publication looks similar , but different. My keys use RSA RSA, but other messages do not.
EDIT # 1
As suggested by @DavidHook, I read SignedFileProcessor , and I'm starting to read the much longer RFC 4880 . However, I need the actual working code to learn in order to understand this. Most people who find this through a Google search will also need working code to illustrate the examples.
For reference, the SignedFileProcessor.verifyFile() method recommended by @DavidHook is as follows. How to configure this to fix problems in the above code?
private static void verifyFile(InputStream in, InputStream keyIn) 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(); int ch; PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn)); PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID()); FileOutputStream out = new FileOutputStream(p2.getFileName()); ops.initVerify(key, "BC"); while ((ch = dIn.read()) >= 0){ ops.update((byte)ch); out.write(ch); } out.close(); PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject(); if (ops.verify(p3.get(0))){System.out.println("signature verified.");} else{System.out.println("signature verification failed.");} }
EDIT # 2
The SignedFileProcessor.verifyFile() method recommended by @DavidHook is almost identical to the PGPUtils.verifyFile() method in my code above, except that PGPUtils.verifyFile() makes a copy of extractContentFile and calls PGPOnePassSignature.init() instead of PGPOnePassSignature.initVerify() . This may be due to version differences. In addition, PGPUtils.verifyFile() returns a boolean, and SignedFileProcessor.verifyFile() gives SYSO for two booleans and returns void after SYSO.
If I interpret @JRichardSnape's comments correctly, it means that the verifyFile() method is best called upstream to confirm the signature of the incoming file using the sender’s public key, and then if the signature verification in the file is verified using another method. Decrypt the file using the recipient’s private key. It's right? If so, how do I restructure the code for this?