Bouncycastle PGP解密并validation

我正在尝试使用java BouncyCastle库解密和validationPGP消息,但遇到了问题,抱怨PartialInputStream的过早结束。

我知道加密工作正常,因为我可以在命令行上使用gpg解密和validation使用加密函数创建的消息。

这是代码:

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

运行几次message = plainFact.nextObject(); 抛出exception:

 -----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$1.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.(Unknown Source) at org.bouncycastle.bcpg.SignaturePacket.(Unknown Source) at org.bouncycastle.bcpg.BCPGInputStream.readPacket(Unknown Source) at org.bouncycastle.openpgp.PGPSignature.(Unknown Source) at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source) at main.decryptVerifyMessage(main.java:113) at main.main(main.java:167) 

有任何想法吗?

另请注意,此解密代码来自如何解密已签名的pgp加密文件? ,略微修改以适应:消息将仅来自该加密方法,并直接处理密钥而不是密钥流。

干杯

拉莫

我最近尝试做同样的事情,并根据我在Bouncycastle示例中找到的代码和我在网上找到的教程将这种方法组合在一起。 出于我的目的,我的代码有一个单独的加密对象,它有一个公钥/私钥对。 在示例代码中,您可以替换

  INSTANCE._secretKeyRingCollection.getSecretKey(pbe.getKeyID()); 

用你的秘密密钥。 我已经使用一个长期过程测试了这个方法,该过程执行了几十次加密和签名/解密和validation操作,并且没有得到您所看到的exception。

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

让弹力城堡一起玩并不容易。 来自Stackoverflow的代码snipets使它可以正常工作 ,但它们大多是神秘的代码片段,没有用户真正理解。

这个问题是: 在一个生产系统中, copy’n’paste片段迅速变成“不去”和“不要触摸”的区域。

抛出可执行文件可能会产生一些严重的安全隐患,最重要的是处理命令行参数(谈论带有空格的文件名……我怎么知道?不要问…)

我有所有这些(以及更多……)问题,经过一些牦牛剃须后,我写了一个库来处理与Bouncycastle的PGP。

解密的工作方式如下:

 final InputStream plaintextStream = BouncyGPG .decryptAndVerifyStream() .withConfig(keyringConfig) .andRequireSignatureFromAllKeys("sender@example.com") .fromEncryptedInputStream(cipherTextStream) 

该库可以在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.+' 

你在打电话:

 encryptedDataGenerator.open(out, 4096) 

你可能意味着什么:

 encryptedDataGenerator.open(out, new byte[4096]) 

第一个版本是打开一个大小(这是错误的),第二个版本给出一个字节缓冲区。

(我知道这是旧的,但是来到这里是因为我在一些示例代码中遇到了同样的问题,所以可能会有其他问题)