Java Bouncycastle PGP 解密和验证
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19173181/
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
Bouncycastle PGP decrypt and verify
提问by Ramo
I'm trying to decrypt and verify a PGP message using the java BouncyCastle libraries, but am running into issues, complaining about premature ends of PartialInputStream.
我正在尝试使用 java BouncyCastle 库解密和验证 PGP 消息,但遇到了问题,抱怨 PartialInputStream 过早结束。
I know the encrypt works fine, because I can decrypt and verify messages created with the encrypt function using gpg on the command line.
我知道加密工作正常,因为我可以在命令行上使用 gpg 解密和验证使用加密函数创建的消息。
Here's the code:
这是代码:
public static void signEncryptMessage(InputStream in, OutputStream out, PGPPublicKey publicKey, PGPPrivateKey secretKey, SecureRandom rand) throws Exception {
out = new ArmoredOutputStream(out);
PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_256).setWithIntegrityPacket(true).setSecureRandom(rand));
encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey));
OutputStream compressedOut = new PGPCompressedDataGenerator(PGPCompressedData.ZIP).open(encryptedDataGenerator.open(out, 4096), new byte[4096]);
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(publicKey.getAlgorithm(), HashAlgorithmTags.SHA512));
signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, secretKey);
signatureGenerator.generateOnePassVersion(true).encode(compressedOut);
OutputStream finalOut = new PGPLiteralDataGenerator().open(compressedOut, PGPLiteralData.BINARY, "", new Date(), new byte[4096]);
byte[] buf = new byte[4096];
int len;
while ((len = in.read(buf)) > 0) {
finalOut.write(buf, 0, len);
signatureGenerator.update(buf, 0, len);
}
finalOut.close();
in.close();
signatureGenerator.generate().encode(compressedOut);
compressedOut.close();
encryptedDataGenerator.close();
out.close();
}
public static void decryptVerifyMessage(InputStream in, OutputStream out, PGPPrivateKey secretKey, PGPPublicKey publicKey) throws Exception {
in = new ArmoredInputStream(in);
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc = (PGPEncryptedDataList) pgpF.nextObject();
PGPObjectFactory plainFact = new PGPObjectFactory(((PGPPublicKeyEncryptedData) enc.getEncryptedDataObjects().next()).getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(secretKey)));
Object message = null;
PGPOnePassSignatureList onePassSignatureList = null;
PGPSignatureList signatureList = null;
PGPCompressedData compressedData = null;
message = plainFact.nextObject();
ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
while (message != null) {
System.out.println(message.toString());
if (message instanceof PGPCompressedData) {
compressedData = (PGPCompressedData) message;
plainFact = new PGPObjectFactory(compressedData.getDataStream());
message = plainFact.nextObject();
System.out.println(message.toString());
}
if (message instanceof PGPLiteralData) {
Streams.pipeAll(((PGPLiteralData) message).getInputStream(), actualOutput);
} else if (message instanceof PGPOnePassSignatureList) {
onePassSignatureList = (PGPOnePassSignatureList) message;
} else if (message instanceof PGPSignatureList) {
signatureList = (PGPSignatureList) message;
} else {
throw new PGPException("message unknown message type.");
}
message = plainFact.nextObject();
}
actualOutput.close();
byte[] output = actualOutput.toByteArray();
if (onePassSignatureList == null || signatureList == null) {
throw new PGPException("Poor PGP. Signatures not found.");
} else {
for (int i = 0; i < onePassSignatureList.size(); i++) {
PGPOnePassSignature ops = onePassSignatureList.get(0);
System.out.println("verifier : " + ops.getKeyID());
if (publicKey != null) {
ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), publicKey);
ops.update(output);
PGPSignature signature = signatureList.get(i);
if (ops.verify(signature)) {
Iterator<?> userIds = publicKey.getUserIDs();
while (userIds.hasNext()) {
String userId = (String) userIds.next();
System.out.println("Signed by " + userId);
}
System.out.println("Signature verified");
} else {
throw new SignatureException("Signature verification failed");
}
}
}
}
out.write(output);
out.flush();
out.close();
}
public static void main(String args[]) {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
byte inBytes[] = "The quick brown fox jumps over the lazy dog.".getBytes();
try {
SecureRandom rand = new SecureRandom();
RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), rand, 1024, 90));
BcPGPKeyPair sender = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpg.generateKeyPair(), new Date());
BcPGPKeyPair recip = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpg.generateKeyPair(), new Date());
ByteArrayOutputStream sendMessage = new ByteArrayOutputStream();
ByteArrayOutputStream recvMessage = new ByteArrayOutputStream();
signEncryptMessage(new ByteArrayInputStream(inBytes), sendMessage, recip.getPublicKey(), sender.getPrivateKey(), rand);
System.out.println(sendMessage.toString());
decryptVerifyMessage(new ByteArrayInputStream(sendMessage.toByteArray()), recvMessage, recip.getPrivateKey(), sender.getPublicKey());
System.out.println(recvMessage.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
After a few runs of message = plainFact.nextObject();
the exception is thrown:
几次运行后message = plainFact.nextObject();
抛出异常:
-----BEGIN PGP MESSAGE-----
Version: BCPG v1.49
hIwDbgERMnl/xpUBA/98O/by9Ib6/nzXiYWuwT2CYulTqzcY07iuHKB4KQc6m+H1
ZBVAx+HozgxQXQdQcBTcp+YS7Xn3tsReiH28Z9805f65tmASoqrzdf35qiVgFhfA
CbCfIq7cqC4rKut3Y8pNOs1mmhpeVNa+AqTZ1r46uyuloBTllI8OWzWoxjTcZdLP
aQHe2BQnfYk+dFgXZ2LMBMtL9mcsEqRLWIdisJQ4gppyIbQNNE7q5gV1Es63yVoM
3dpfYHxlnIZASuynSZyGorHpYMV6tWNwSRQ9Ziwaw4DwvQGyAHpb1O/tLqrfjLqN
5dj5qNY6nElT1EM94Dd4FOBzI6x6JkfuCH3/Jp8lCA/p8K7jmYu9Xvdld8BgHmRF
ymasPf1JC4xYFa9YQVnn4fK2l//2iVcVayv0On32kxD9XfkPUysYVH38glPaHb48
qWk9i/x0Y3mmCy1RVAGWqimR5DEhZPubJ+Kjk3UsB1m90Pm/6a+/ZfpAEHcxshdX
JeVBr7aQIX3PQIUl+ZPQsgAGEmo0abQVufuKfkfjX0Gh
=ApMf
-----END PGP MESSAGE-----
org.bouncycastle.openpgp.PGPCompressedData@cd36a6d
org.bouncycastle.openpgp.PGPOnePassSignatureList@7e224235
org.bouncycastle.openpgp.PGPLiteralData@7b28e644
java.io.EOFException: premature end of stream in PartialInputStream
at org.bouncycastle.bcpg.BCPGInputStream$PartialInputStream.read(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
at java.io.InputStream.read(InputStream.java:101)
at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:103)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:177)
at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
at org.bouncycastle.openpgp.PGPEncryptedData$TruncatedStream.read(Unknown Source)
at java.io.InputStream.read(InputStream.java:170)
at org.bouncycastle.util.io.TeeInputStream.read(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream$PartialInputStream.read(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
at org.bouncycastle.openpgp.PGPCompressedData.fill(Unknown Source)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158)
at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream$PartialInputStream.read(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
at org.bouncycastle.util.io.Streams.readFully(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.readFully(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.readFully(Unknown Source)
at org.bouncycastle.bcpg.MPInteger.<init>(Unknown Source)
at org.bouncycastle.bcpg.SignaturePacket.<init>(Unknown Source)
at org.bouncycastle.bcpg.BCPGInputStream.readPacket(Unknown Source)
at org.bouncycastle.openpgp.PGPSignature.<init>(Unknown Source)
at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source)
at main.decryptVerifyMessage(main.java:113)
at main.main(main.java:167)
Any ideas?
有任何想法吗?
Side note, this decryption code came from How to decrypt a signed pgp encrypted file?, slightly modified to suit: Messages will only be coming from that encryption method, and handling of the keys directly instead of key streams.
旁注,此解密代码来自如何解密已签名的 pgp 加密文件?,稍作修改以适应:消息将仅来自该加密方法,并直接处理密钥而不是密钥流。
Cheers
干杯
Ramo
拉莫
回答by Stephen B.
I was recently trying to do the same kind of thing and put together this method based on code I found in the Bouncycastle examples and on tutorials I found on the web. For my purposes, my code has a singleton crypto object that has a public/private key pair. In the example code, you could replace
我最近试图做同样的事情,并根据我在 Bouncycastle 示例和我在网上找到的教程中找到的代码将这种方法组合在一起。出于我的目的,我的代码有一个具有公钥/私钥对的单例加密对象。在示例代码中,您可以替换
INSTANCE._secretKeyRingCollection.getSecretKey(pbe.getKeyID());
with your secret key. I've tested this method with a long-lived process that did several tens of encrypt & sign / decrypt & verify actions and didn't get the exception you're seeing.
用你的密钥。我已经用一个长期的过程测试了这种方法,该过程执行了数十次加密和签名/解密和验证操作,但没有出现您看到的异常。
public static void decryptAndVerify(InputStream in, OutputStream fOut, InputStream publicKeyIn) throws IOException, SignatureException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpF = new PGPObjectFactory(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();
PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(INSTANCE._secretKeyPass.toCharArray());
PGPSecretKey psKey = INSTANCE._secretKeyRingCollection.getSecretKey(pbe.getKeyID());
if (psKey != null) {
sKey = psKey.extractPrivateKey(decryptor);
}
}
if (sKey == null) {
throw new IllegalArgumentException("Unable to find secret key to decrypt the message");
}
InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
Object message;
PGPOnePassSignatureList onePassSignatureList = null;
PGPSignatureList signatureList = null;
PGPCompressedData compressedData;
message = plainFact.nextObject();
ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
while (message != null) {
__l.trace(message.toString());
if (message instanceof PGPCompressedData) {
compressedData = (PGPCompressedData) message;
plainFact = new PGPObjectFactory(compressedData.getDataStream());
message = plainFact.nextObject();
}
if (message instanceof PGPLiteralData) {
// have to read it and keep it somewhere.
Streams.pipeAll(((PGPLiteralData) message).getInputStream(), actualOutput);
} else if (message instanceof PGPOnePassSignatureList) {
onePassSignatureList = (PGPOnePassSignatureList) message;
} else if (message instanceof PGPSignatureList) {
signatureList = (PGPSignatureList) message;
} else {
throw new PGPException("message unknown message type.");
}
message = plainFact.nextObject();
}
actualOutput.close();
PGPPublicKey publicKey = null;
byte[] output = actualOutput.toByteArray();
if (onePassSignatureList == null || signatureList == null) {
throw new PGPException("Poor PGP. Signatures not found.");
} else {
for (int i = 0; i < onePassSignatureList.size(); i++) {
PGPOnePassSignature ops = onePassSignatureList.get(0);
__l.trace("verifier : " + ops.getKeyID());
PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(publicKeyIn));
publicKey = pgpRing.getPublicKey(ops.getKeyID());
if (publicKey != null) {
ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), publicKey);
ops.update(output);
PGPSignature signature = signatureList.get(i);
if (ops.verify(signature)) {
Iterator<?> userIds = publicKey.getUserIDs();
while (userIds.hasNext()) {
String userId = (String) userIds.next();
__l.trace(String.format("Signed by {%s}", userId));
}
__l.trace("Signature verified");
} else {
throw new SignatureException("Signature verification failed");
}
}
}
}
if (pbe.isIntegrityProtected() && !pbe.verify()) {
throw new PGPException("Data is integrity protected but integrity is lost.");
} else if (publicKey == null) {
throw new SignatureException("Signature not found");
} else {
fOut.write(output);
fOut.flush();
fOut.close();
}
}
回答by Michael Kanis
You are calling:
您正在致电:
encryptedDataGenerator.open(out, 4096)
where you probably meant:
你可能的意思是:
encryptedDataGenerator.open(out, new byte[4096])
The first version is giving a size to open (which is wrong), the second version is giving a byte buffer.
第一个版本给出了打开的大小(这是错误的),第二个版本给出了一个字节缓冲区。
(I know this is old, but came here because I had the same problem with some example code and so might others)
(我知道这是旧的,但来到这里是因为我对一些示例代码有同样的问题,其他人也有同样的问题)
回答by Jens
Getting Bouncy Castle to play along is not always easy. Code-snipets from Stackoverflow make it work just so, but they are mostly arcane pieces of code that none of the users really understand.
让充气城堡一起玩并不总是那么容易。Stackoverflow 的代码片段使它能够正常工作,但它们大多是用户真正理解的晦涩难懂的代码片段。
The problem with this is: In a production system copy'n'paste snippetsquickly become "no go" and "do not touch" areas.
这样做的问题是:在生产系统中,copy'n'paste 片段很快就会变成“no go”和“do not touch”区域。
Shelling out executables can have some serious security implications, least of all is handling command line parameters (talking about filenames with spaces ... how do I know? Don't ask ...)
删除可执行文件可能会产生一些严重的安全隐患,最重要的是处理命令行参数(谈论带空格的文件名......我怎么知道?不要问......)
I had all of these (and more..) problems, and after some yak shaving I wrote a library to handle PGP with Bouncycastle.
我遇到了所有这些(以及更多……)问题,在剃须后我写了一个库来处理 Bouncycastle 的 PGP。
Decrypting works like this:
解密工作是这样的:
final InputStream plaintextStream = BouncyGPG
.decryptAndVerifyStream()
.withConfig(keyringConfig)
.andRequireSignatureFromAllKeys("[email protected]")
.fromEncryptedInputStream(cipherTextStream)
The library can be found at https://github.com/neuhalje/bouncy-gpg:
该库可以在https://github.com/neuhalje/bouncy-gpg找到:
// in build.gradle add a dependency to bouncy castle and bouncy-gpg
// ...
dependencies {
compile 'org.bouncycastle:bcprov-jdk15on:1.56'
compile 'org.bouncycastle:bcpg-jdk15on:1.56'
// ...
compile 'name.neuhalfen.projects.crypto.bouncycastle.openpgp:bouncy-gpg:2.+'