Cod*_*Med 5 java encryption bouncycastle gnupg public-key-encryption
如何让 BouncyCastle 解密 GPG 加密的消息?
我在 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
我能够导入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.
Run Code Online (Sandbox Code Playgroud)
如果您查看下面的代码,您将看到这与PGPUtils.decryptFile()状态所在的行相对应else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");
原始代码来自此链接的博客条目,尽管我做了一些小改动以使其在带有 Java 7 的 Eclipse Luna 中编译。链接博客的用户报告了同样的错误,博客作者回复说它不适用于 GPG。 那么我如何解决这个问题以使其与 GPG 一起使用?
当 GPG-encoded-file 和 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
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
单击此链接,可以在文件共享站点上找到所有三个 Java 文件的完整代码。
单击此链接可以找到错误的完整堆栈跟踪。
作为参考,远程 Thunderbird 发送器的 GUI 说明总结在以下屏幕截图中:
我已经阅读了很多关于此的帖子和链接。特别是,这个其他 SO 发布看起来相似,但不同。我的密钥使用 RSA RSA,但其他帖子没有。
编辑#1
根据@DavidHook 的建议,我已经阅读了SignedFileProcessor,并且我开始阅读更长的RFC 4880。但是,我需要实际的工作代码来研究才能理解这一点。大多数通过谷歌搜索找到这个的人还需要工作代码来说明示例。
供参考,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.");}
}
Run Code Online (Sandbox Code Playgroud)
编辑#2
在SignedFileProcessor.verifyFile()通过@DavidHook推荐的方法是几乎相同的PGPUtils.verifyFile()在我的代码上述方法中,不同之处在于PGPUtils.verifyFile()使副本extractContentFile并调用PGPOnePassSignature.init()代替PGPOnePassSignature.initVerify()。这可能是由于版本差异。此外,PGPUtils.verifyFile()返回一个布尔值,同时SignedFileProcessor.verifyFile()为两个布尔值提供 SYSO,并在 SYSO 之后返回 void。
如果我正确解释了@JRichardSnape 的评论,这意味着verifyFile()最好在上游调用该方法以使用发件人的公钥确认传入文件的签名,然后,如果文件上的签名得到验证,则使用另一种方法来解密文件使用收件人的私钥。这样对吗?如果是这样,我该如何重构代码来实现这一点?
小智 6
如果有人有兴趣知道如何使用充气城堡 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;
}
Run Code Online (Sandbox Code Playgroud)
以下方法将从 .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;
}
Run Code Online (Sandbox Code Playgroud)
以下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();
}
Run Code Online (Sandbox Code Playgroud)
现在这里是如何调用/运行上面的:
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());
}
Run Code Online (Sandbox Code Playgroud)
它只是意味着内容已经被签名然后加密,提供的例程不知道如何处理它,但至少告诉你这一点。PGP协议呈现为一系列数据包,其中一些数据包可以包装在其他数据包中(例如压缩数据也可以包装签名数据或简单的文字数据,这些也可以用于生成加密数据,实际内容总是出现在文字数据中)。
如果您查看 Bouncy Castle OpenPGP 示例包中 SignedFileProcessor 中的 verifyFile 方法,您将了解如何处理签名数据并获取包含实际内容的文字数据。
我还建议您查看 RFC 4880,以便您了解该协议的工作原理。该协议非常宽松,GPG、BC 和各种产品都反映了这一点 - 也就是说,宽松确实意味着,如果您尝试剪切粘贴解决方案,您最终会遇到灾难。这并不复杂,但这里也需要理解。