Bouncycastle PGP 解密和验证

Posted

技术标签:

【中文标题】Bouncycastle PGP 解密和验证【英文标题】:Bouncycastle PGP decrypt and verify 【发布时间】:2013-10-10 23:27:11 【问题描述】:

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

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

代码如下:

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(); 运行几次后抛出异常:

-----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.<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)

有什么想法吗?

附注,此解密代码来自How to decrypt a signed pgp encrypted file?,稍作修改以适应:消息将仅来自该加密方法,并且直接处理密钥而不是密钥流。

干杯

拉莫

【问题讨论】:

你找到解决办法了吗? 【参考方案1】:

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

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

使用您的密钥。我已经用一个长期存在的过程测试了这种方法,该过程执行了数十次加密和签名/解密和验证操作,并且没有得到您所看到的异常。

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

【讨论】:

有趣的事实:将此代码移植到 C# 实际上比执行此操作的任何实际 C# 示例都更好。【参考方案2】:

你在打电话:

encryptedDataGenerator.open(out, 4096)

你的意思可能是:

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

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

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

【讨论】:

【参考方案3】:

让 Bouncy Castle 玩起来并不容易。来自 *** 的代码片段使其正常工作,但它们大多是用户都无法真正理解的晦涩难懂的代码。

这样做的问题是:在生产系统中copy'n'paste sn-ps很快就变成了“no go”和“do not touch”的区域。

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

我遇到了所有这些(以及更多..)问题,经过一些牦牛剃须后,我编写了一个库来处理带有 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.+'

【讨论】:

嗨 Jens,我正在使用你的库,它真的很好。我遇到了这个问题,虽然我加密了文件并使用用户 test@test.com 签名,然后当我尝试使用 mac 的 Open PGP 解密时,它显示 test@test.com 的验证失败,即使文件已解密。你能帮忙吗? 请在 Github bugtracker 中创建问题并在此处链接(以便我们可以在跟踪器中解决问题并在此处更新答案) 在过去几天与 Bouncy Castle 战斗,我可以验证这个库是涂料。 嗨,你有PGP加密的东西吗? 是的,BouncyGPG 也支持加密,只需访问 Github repo github.com/neuhalje/bouncy-gpg

以上是关于Bouncycastle PGP 解密和验证的主要内容,如果未能解决你的问题,请参考以下文章

Bouncy Castle PGP 解密问题

使用BouncyCastle Java API进行PGP签名并使用gpg4win进行验证不起作用

加解密专辑对接触到的PGPRSAAES加解密算法整理

Bouncy Castle 从公钥加密会话数据包中提取 PGP 会话密钥

用于解密加密 p8 文件的 BouncyCastle 替代方案

bouncycastle从1.46更新到1.56后无法正常工作。