我如何获得BouncyCastle来解密GPG加密的消息?
我已经使用gpg --gen-key
在CentOS 7命令行上创建了一个GPG密钥对。我选择RSA RSA作为加密类型,并使用gpg --export-secret-key -a "User Name" > /home/username/username_private.key
和gpg --armor --export 66677FC6 > /home/username/username_pubkey.asc
我能够将username_pubkey.asc
导入另一个电子邮件帐户的远程Thunderbird客户端,并成功将加密的电子邮件发送到[email protected]。但是,当我在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.");
的行相对应>
原始代码来自the blog entry at this link,尽管我做了一些小的改动以使其可以在Java 7的Eclipse Luna中进行编译。链接博客的用户报告了相同的错误,博客作者回答说:不适用于GPG。 所以我该如何解决才能使其与GPG兼容?
[如下所示,当将GPG编码文件和GPG秘密密钥传递到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<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); }
可以在文件共享站点by clicking on this link上找到所有三个Java文件的完整代码。
可以找到错误的完整堆栈跟踪by clicking on this link。
作为参考,以下屏幕快照总结了由远程Thunderbird发送方进行加密的GUI指令:
我已经阅读了许多与此相关的帖子和链接。特别是this other SO posting looks similar,但有所不同。我的密钥使用RSA RSA,但其他发布则不使用。
EDIT#1
根据@DavidHook的建议,我已阅读SignedFileProcessor,并且我开始阅读更长的RFC 4880。但是,我需要学习实际的工作代码才能理解这一点。通过Google搜索找到此内容的大多数人还需要有效的代码来说明示例。
供参考,@ DavidHook推荐的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.");} }
EDIT#2
@ DavidHook推荐的SignedFileProcessor.verifyFile()
方法与我上面的代码中的PGPUtils.verifyFile()
方法几乎相同,除了PGPUtils.verifyFile()
复制extractContentFile
并调用PGPOnePassSignature.init()
而不是PGPOnePassSignature.initVerify()
。这可能是由于版本差异造成的。同样,PGPUtils.verifyFile()
返回一个布尔值,而SignedFileProcessor.verifyFile()
给出两个布尔值的SYSO并在SYSO之后返回void。
如果我正确解释了@JRichardSnape的注释,这意味着最好在上游调用verifyFile()
方法,以使用发送者的公钥来确认传入文件的签名,然后,如果验证了文件上的签名, ,使用另一种方法使用收件人的私钥解密文件。这个对吗?如果是这样,我如何重组代码以完成此操作?
我如何获得BouncyCastle来解密GPG加密的邮件?我已经使用gpg --gen-key在CentOS 7命令行上创建了一个GPG密钥对。我选择RSA RSA作为加密类型,然后选择[...