Getting a BouncyCastle to decrypt a GPG-encrypted message

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?

+3
source share
2 answers

if anyone is interested in learning how to encrypt and decrypt gpg files using the openPGP library using bouncy, check out the Java code below:

Below are 4 methods you will need:

The following is a method for reading and importing a private key from a .asc file:

 public static PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException { in = PGPUtil.getDecoderStream(in); PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator()); PGPSecretKey key = pgpSec.getSecretKey(keyId); if (key == null) { throw new IllegalArgumentException("Can't find encryption key in key ring."); } return key; } 

The following is a method for reading and importing a public key from a .asc file:

 @SuppressWarnings("rawtypes") public static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException { in = PGPUtil.getDecoderStream(in); PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator()); PGPPublicKey key = null; Iterator rIt = pgpPub.getKeyRings(); while (key == null && rIt.hasNext()) { PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next(); Iterator kIt = kRing.getPublicKeys(); while (key == null && kIt.hasNext()) { PGPPublicKey k = (PGPPublicKey) kIt.next(); if (k.isEncryptionKey()) { key = k; } } } if (key == null) { throw new IllegalArgumentException("Can't find encryption key in key ring."); } return key; } 

Below are two ways to decrypt and encrypt gpg files:

 public void decryptFile(InputStream in, InputStream secKeyIn, InputStream pubKeyIn, char[] pass) throws IOException, PGPException, InvalidCipherTextException { Security.addProvider(new BouncyCastleProvider()); PGPPublicKey pubKey = readPublicKeyFromCol(pubKeyIn); PGPSecretKey secKey = readSecretKeyFromCol(secKeyIn, pubKey.getKeyID()); in = PGPUtil.getDecoderStream(in); JcaPGPObjectFactory pgpFact; PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); Object o = pgpF.nextObject(); PGPEncryptedDataList encList; if (o instanceof PGPEncryptedDataList) { encList = (PGPEncryptedDataList) o; } else { encList = (PGPEncryptedDataList) pgpF.nextObject(); } Iterator<PGPPublicKeyEncryptedData> itt = encList.getEncryptedDataObjects(); PGPPrivateKey sKey = null; PGPPublicKeyEncryptedData encP = null; while (sKey == null && itt.hasNext()) { encP = itt.next(); secKey = readSecretKeyFromCol(new FileInputStream("PrivateKey.asc"), encP.getKeyID()); sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass)); } if (sKey == null) { throw new IllegalArgumentException("Secret key for message not found."); } InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey)); pgpFact = new JcaPGPObjectFactory(clear); PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject(); pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); InputStream inLd = ld.getDataStream(); int ch; while ((ch = inLd.read()) >= 0) { bOut.write(ch); } //System.out.println(bOut.toString()); bOut.writeTo(new FileOutputStream(ld.getFileName())); //return bOut; } public static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey) throws IOException, NoSuchProviderException, PGPException { Security.addProvider(new BouncyCastleProvider()); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName)); comData.close(); PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom())); cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey)); byte[] bytes = bOut.toByteArray(); OutputStream cOut = cPk.open(out, bytes.length); cOut.write(bytes); cOut.close(); out.close(); } 

Now here is how to invoke / run above:

 try { decryptFile(new FileInputStream("encryptedFile.gpg"), new FileInputStream("PrivateKey.asc"), new FileInputStream("PublicKey.asc"), "yourKeyPassword".toCharArray()); PGPPublicKey pubKey = readPublicKeyFromCol(new FileInputStream("PublicKey.asc")); encryptFile(new FileOutputStream("encryptedFileOutput.gpg"), "fileToEncrypt.txt", pubKey); } catch (PGPException e) { fail("exception: " + e.getMessage(), e.getUnderlyingException()); } 
+4
source

This means that the content has been signed and then encrypted, the provided routine does not know how to deal with it, but at least it tells you about it. PGP is a series of packets, some of which can be wrapped in others (for example, compressed data can also transfer signed data or just literal data, they can also be used to generate encrypted data, the actual content is always displayed in literal data).

If you look at the verifyFile method in SignedFileProcessor in the OpenPGP Bouncy Castle sample package, you will see how to process the signature data and obtain literal data containing the actual content.

I would also recommend looking at the RFC 4880 so you know how the protocol works. The protocol is very small, and both GPG, BC and the various products there reflect this - this suggests that easing means that if you try to cut and paste your path to the solution, you will get a disaster. It is not difficult, but understanding is required here.

+1
source

Source: https://habr.com/ru/post/981790/


All Articles