让BouncyCastle解密GPG加密的消息

如何让BouncyCastle解密GPG加密的邮件?

我使用gpg --gen-key在CentOS 7命令行创建了一个GPG密钥对。 我选择RSA RSA作为加密类型,我使用gpg --export-secret-key -a "User Name" > /home/username/username_private.key导出密钥gpg --export-secret-key -a "User Name" > /home/username/username_private.keygpg --armor --export 66677FC6 > /home/username/username_pubkey.asc

我能够将username_pubkey.asc导入另一个电子邮件帐户的远程Thunderbird客户端,并成功将加密的电子邮件发送到username@mydomain.com。 但是当我在mydomain.com上运行的Java / BouncyCastle代码尝试解密GPG编码的数据时,它会出现以下错误:

 org.bouncycastle.openpgp.PGPException: Encrypted message contains a signed message - not literal data. 

如果你查看下面的代码,你会看到这与PGPUtils.decryptFile()的行相对应,其中说明了else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");

原始代码来自此链接的博客条目 ,但我做了一些小改动,以便在Eclipse Luna中使用Java 7进行编译。链接博客的用户报告了相同的错误,博客作者回答说它不适用于GPG。 那么我该如何解决这个问题才能让它与GPG一起使用呢?

当GPG编码文件和GPG-secret-key传递到Tester.testDecrypt()时,Java解密代码开始,如下所示:

Tester.java包含:

 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包括:

 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包括:

 /** * 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 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); } 

通过单击此链接,可以在文件共享站点上找到所有三个Java文件的完整代码。

单击此链接可找到错误的完整堆栈跟踪。

作为参考,以下屏幕截图总结了远程Thunderbird发送器加密的GUI指令:

我已经阅读了很多关于此的post和链接。 特别是, 这个其他的SOpost看起来很相似 ,但却有所不同。 我的密钥使用RSA RSA,但另一个post没有。

编辑#1

根据@ DavidHook的建议,我已经阅读了SignedFileProcessor ,我开始阅读更长的RFC 4880 。 但是,我需要学习实际工作代码才能理解这一点。 大多数通过谷歌搜索找到这一点的人也需要工作代码来说明这些例子。

作为参考, SignedFileProcessor.verifyFile()推荐的SignedFileProcessor.verifyFile()方法如下。 如何定制以修复上述代码中的问题?

 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.");} } 

编辑#2

SignedFileProcessor.verifyFile()推荐的SignedFileProcessor.verifyFile()方法几乎与我上面代码中的PGPUtils.verifyFile()方法相同,只是PGPUtils.verifyFile()生成extractContentFile的副本并调用PGPOnePassSignature.init()而不是PGPOnePassSignature.initVerify() 。 这可能是由于版本差异造成的。 此外, PGPUtils.verifyFile()返回一个布尔值,而SignedFileProcessor.verifyFile()为SYSO提供两个布尔值,并在SYSO后返回void。

如果我正确地解释了@JRichardSnape的注释,这意味着最好使用sender的公钥来validationverifyFile()方法上游以确认传入文件的签名,然后,如果validation了文件上的签名,则使用其他方法使用收件人的私钥解密文件。 它是否正确? 如果是这样,我如何重组代码来实现这一目标?

如果有人有兴趣知道如何使用bouncy castle openPGP库加密和解密gpg文件,请检查以下java代码:

以下是您需要的4种方法:

以下方法将从.asc文件中读取并导入您的密钥:

 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; } 

以下方法将从.asc文件中读取并导入您的公钥:

 @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; } 

以下2种解密和加密gpg文件的方法:

 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 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(); } 

现在这里是如何调用/运行上面的:

 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()); } 

它只是意味着内容已经签名然后加密,提供的例程不知道如何处理它,但至少告诉你。 PGP协议表示为一系列数据包,其中一些可以包装在其他数据包中(例如压缩数据也可以包装有符号数据或简单文字数据,这些数据也可用于生成加密数据,实际内容始终显示在文字数据中)。

如果您查看Bouncy Castle OpenPGP示例包中SignedFileProcessor中的verifyFile方法,您将看到如何处理签名数据并获取包含实际内容的文字数据。

我还建议您查看RFC 4880,以便了解协议的工作原理。 该协议非常松散,GPG,BC和各种产品都反映了这一点 – 说松散意味着如果你试图剪切并粘贴到解决方案中,你最终会遇到灾难。 这并不复杂,但这里也需要理解。