java 让 BouncyCastle 解密 GPG 加密的消息
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28444819/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Getting BouncyCastle to decrypt a GPG-encrypted message
提问by CodeMed
How can I get BouncyCastle to decrypt a GPG-encrypted message?
如何让 BouncyCastle 解密 GPG 加密的消息?
I have created a GPG key pair at the CentOS 7 command line using gpg --gen-key
. I chose RSA RSA as the encryption types, and I 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
我在 CentOS 7 命令行中使用gpg --gen-key
. 我选择了 RSA RSA 作为加密类型,并使用gpg --export-secret-key -a "User Name" > /home/username/username_private.key
和导出了密钥gpg --armor --export 66677FC6 > /home/username/username_pubkey.asc
I am able to import username_pubkey.asc
into a remote Thunderbird client of another email account and successfully send an encrypted email to [email protected]. But when my Java/BouncyCastle code running at mydomain.com tries to decrypt the GPG-encoded data, it gives the following error:
我能够导入username_pubkey.asc
到另一个电子邮件帐户的远程 Thunderbird 客户端,并成功地将加密的电子邮件发送到 [email protected]。但是,当我在 mydomain.com 上运行的 Java/BouncyCastle 代码尝试解密 GPG 编码的数据时,会出现以下错误:
org.bouncycastle.openpgp.PGPException:
Encrypted message contains a signed message - not literal data.
If you look at the code below, you will see this corresponds with the line in PGPUtils.decryptFile()
which states else if (message instanceof PGPOnePassSignatureList) {throw new 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 original code for this came from the blog entry at this link, though I made minor changes to get it to compile in Eclipse Luna with Java 7. A user of the linked blog reported the same error, and the blog author replied by saying that it does not work with GPG. So how do I fix this to make it work with GPG?
原始代码来自此链接的博客条目,尽管我做了一些小改动以使其在带有 Java 7 的 Eclipse Luna 中编译。链接博客的用户报告了同样的错误,博客作者回复说它不适用于 GPG。 那么我如何解决这个问题以使其与 GPG 一起使用?
The Java decryption code starts when the GPG-encoded-file and the GPG-secret-key are passed into Tester.testDecrypt()
as follows:
当 GPG-encoded-file 和 GPG-secret-key 传入Tester.testDecrypt()
如下时,Java 解密代码开始:
Tester.java contains:
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 includes:
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 includes:
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);
}
The complete code of all three Java files can be found on a file sharing site by clicking on this link.
单击此链接,可以在文件共享站点上找到所有三个 Java 文件的完整代码。
The complete stack trace for the error can be found by clicking on this link.
单击此链接可以找到错误的完整堆栈跟踪。
For reference, the GUI instructions for encryption by the remote Thunderbird sender are summarized in the following screen shot:
作为参考,远程 Thunderbird 发送者加密的 GUI 说明总结在以下屏幕截图中:
I have read many postings and links about this. In particular, this other SO posting looks similar, but is different. My Keys use RSA RSA, but the other posting does not.
我已经阅读了很多关于此的帖子和链接。特别是,这个其他 SO 发布看起来相似,但不同。我的密钥使用 RSA RSA,但其他帖子没有。
EDIT#1
编辑#1
As per @DavidHook's suggestion, I have read SignedFileProcessor, and I am starting to read the much longer RFC 4880. However, I need actual working code to study in order to understand this. Most people who find this via google searches will also need working code to illustrate the examples.
根据@DavidHook 的建议,我已经阅读了SignedFileProcessor,并且我开始阅读更长的RFC 4880。但是,我需要研究实际的工作代码才能理解这一点。大多数通过谷歌搜索找到这个的人还需要工作代码来说明示例。
For reference, the SignedFileProcessor.verifyFile()
method recommended by @DavidHook is as follows. How should this be customized to fix the problems in the code above?
供参考,SignedFileProcessor.verifyFile()
@DavidHook 推荐的方法如下。 这应该如何定制以解决上面代码中的问题?
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
编辑#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 a version difference. Also, PGPUtils.verifyFile()
returns a boolean, while SignedFileProcessor.verifyFile()
gives SYSO for the two boolean values and returns void after the SYSO.
在SignedFileProcessor.verifyFile()
通过@DavidHook推荐的方法是几乎相同的PGPUtils.verifyFile()
在我的代码上述方法中,不同之处在于PGPUtils.verifyFile()
使副本extractContentFile
并调用PGPOnePassSignature.init()
代替PGPOnePassSignature.initVerify()
。这可能是由于版本差异。此外,PGPUtils.verifyFile()
返回一个布尔值,同时SignedFileProcessor.verifyFile()
为两个布尔值提供 SYSO,并在 SYSO 之后返回 void。
If I interpret @JRichardSnape's comments correctly, this means that the verifyFile()
method might best be called upstream to confirm the signature of the incoming file using the sender's public key, and then, if the signature on the file is verified, using another method to decrypt the file using the recipient's private key. Is this correct? If so, how do I restructure the code to accomplish this?
如果我正确解释了@JRichardSnape 的评论,这意味着verifyFile()
最好在上游调用该方法以使用发件人的公钥确认传入文件的签名,然后,如果文件上的签名得到验证,则使用另一种方法来解密文件使用收件人的私钥。这个对吗?如果是这样,我该如何重构代码来实现这一点?
回答by sheckoo90
if anyone is interested to know how to encrypt and decrypt gpg files using bouncy castle openPGP library, check the below java code:
如果有人有兴趣知道如何使用充气城堡 openPGP 库加密和解密 gpg 文件,请查看以下 java 代码:
The below are the 4 methods you going to need:
以下是您需要的4种方法:
The below method will read and import your secret key from .asc file:
以下方法将从 .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;
}
The below method will read and import your public key from .asc file:
以下方法将从 .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;
}
The below 2 methods to decrypt and encrypt gpg files:
以下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<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 the 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());
}
回答by David Hook
It just means that content has been signed and then encrypted, the routine provided does not know how to deal with it, but at least tells you that. PGP protocol presents as a series of packets some of which can be wrapped in other ones (for example compressed data can also wrap signed data or simply literal data, these can be used to generate encrypted data as well, actual content always appears in literal data).
它只是意味着内容已经被签名然后加密,提供的例程不知道如何处理它,但至少告诉你。PGP协议表现为一系列的数据包,其中一些可以包装在其他数据包中(例如压缩数据也可以包装签名数据或简单的文字数据,这些也可以用来生成加密数据,实际内容总是出现在文字数据中)。
If you look at the verifyFile method in the SignedFileProcessor in the Bouncy Castle OpenPGP examples package you will see how to handle the signature data and get to the literal data containing the actual content.
如果您查看 Bouncy Castle OpenPGP 示例包中 SignedFileProcessor 中的 verifyFile 方法,您将看到如何处理签名数据并获取包含实际内容的文字数据。
I would also recommend looking at RFC 4880 so you have some idea of how the protocol works. The protocol is very loose and both GPG, BC, and the variety of products out there reflect this - that said the looseness does mean that if you try and cut and paste your way to a solution you'll end up with a disaster. It's not complicated, but understanding is required here as well.
我还建议您查看 RFC 4880,以便您对该协议的工作原理有所了解。该协议非常松散,GPG、BC 和那里的各种产品都反映了这一点——也就是说,松散确实意味着如果您尝试将自己的方式剪切并粘贴到解决方案中,您最终会遇到灾难。这并不复杂,但这里也需要理解。