Bouncy Castle Java PGP加密&解密

Bouncy Castle Java PGP encryption & decryption

我正在尝试使用 Bouncy Castle 在 Java 中实现 PGP 加密,同时使用他们提供的一些示例。

但是,如果我尝试解密我刚刚加密的消息,那是行不通的。密钥和解密方法似乎都可以,因为我可以使用外部工具加密(link)然后在这里成功解密。

    public static void main(String[] args) throws PGPException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
    Security.addProvider(new BouncyCastleProvider());

    byte[] msg = ("Test blabla BLA").getBytes();

    byte[] encData = encrypt(msg, publicKey);
    System.out.println(new String(encData));

    System.err.println("DECRYPTED");
    System.out.println(decrypt(IOUtils.toInputStream(new String(encData), StandardCharsets.UTF_8), privateKey, passphrase));

    System.err.println("DECRYPTED external");
    String testEncryptedMsg = "-----BEGIN PGP MESSAGE-----\n" +
            "Version: BCPG C# v1.6.1.0\n" +
            "\n" +
            "hQIMA4p9BgtBOg+BARAAiLdmGZBupsSkrji1qdC/y6KACuaWytcjVg3LWudWRlH8\n" +
            "Ye1U0bz+HYc6mLt21RivAZ3p+lPXPzh/h0GVLW/aI2ngJracL+iEA7eam5/H3NQV\n" +
            "2o0CR1mjw4xOf0XwDxaLrnivdeuWniXiZyFN7Ud7lmNG+EmQ0d5ZfR7KEnTJNjSe\n" +
            "FpzwRffBG5lKB4CxF49jBp+Iupdv4A5JzHuDVBkypPMNhS/UEbuPOt5cfmddYVvW\n" +
            "gtDM3rX+ePBhg7d/mVkiDT2KM0Cr7GSrdZ8q83fF4sat3Xp8OHMq61GWriNK4gwb\n" +
            "ViT/vmEsY0n55TVgN5VagMZJJlEThkqWE2wpEjq4HM8pNxR4PvP5inqP5oipn8Os\n" +
            "Nq2a5f77BS7GB9NQ+hnaLSBsywJkZVKqygN81MeHBnNrVXFQ/Hqg24BLwk49bOuH\n" +
            "jTUzorudCnjj/p8/WiyR+PuESkQDLKpo1z9KGEE/rKjTqAwH/fBKJ73XpmAWwd4U\n" +
            "1hv/EdF+XDZv7XwxeVjSIK2inAjdNz+way1WNY3qouo7G+gjKf1gbL14q2HVPnd/\n" +
            "7mfiW3uWCQ7GhXlHtd1E0WeK2296TdBuSGAvLlVxI4xepsiuVw+zC4BxvAkTXB/a\n" +
            "HyJZhKPksHMRCt8ZxitMfhdvMNGjc4MDhv1MLqKijpagKhgrmDTiJRMb3ng6QLvJ\n" +
            "L2GICyuS4aIsz6XiCQvZG8ebfb0J4uogbb5HxS2fGFT+Y/VgpCnwhwj6LnLBy1Q3\n" +
            "=D/1d\n" +
            "-----END PGP MESSAGE-----\n";
    System.out.println(decrypt(IOUtils.toInputStream(testEncryptedMsg, StandardCharsets.UTF_8), privateKey, passphrase));
}

我认为问题出在加密方面,但我无法弄清楚到底是什么问题。有人有什么想法吗?

public static byte[] encrypt(byte[] bytes, String publicKey) throws IOException {

    ByteArrayOutputStream encOut = new ByteArrayOutputStream();

    try {
        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_128)
                        .setSecureRandom(new SecureRandom())
                        .setWithIntegrityPacket(true)
                        .setProvider("BC"));

        PGPPublicKey encryptionKey = readPublicKey(publicKey);
        encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey).setProvider("BC"));

        ArmoredOutputStream aOut = new ArmoredOutputStream(encOut);
        OutputStream cOut = encGen.open(aOut, bytes.length);
        // write out the literal data
        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream pOut = lData.open(aOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
        pOut.write(bytes);
        pOut.close();

        // finish the encryption
        cOut.close();
        aOut.close();

    } catch (PGPException e) {
        log.error("Exception encountered while encoding data.", e);
    }
    return encOut.toByteArray();
}

public static String decrypt(InputStream in, String secretKey, String passphrase) throws IOException {
    String content = null;

    try {
        in = PGPUtil.getDecoderStream(in);
        JcaPGPObjectFactory 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<PGPEncryptedData> it = enc.getEncryptedDataObjects();
        PGPPrivateKey sKey = null;
        PGPPublicKeyEncryptedData pbe = null;
        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new ByteArrayInputStream(secretKey.getBytes())), new JcaKeyFingerprintCalculator());

        while (sKey == null && it.hasNext()) {
            pbe = (PGPPublicKeyEncryptedData) it.next();
            sKey = findSecretKey(pgpSec, pbe.getKeyID(), passphrase.toCharArray());
        }
        if (sKey == null) {
            log.error("Secret key for message not found.");
            throw new IllegalArgumentException("secret key for message not found.");
        }

        InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
        JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
        Object message = plainFact.nextObject();

        if (message instanceof PGPCompressedData) {
            PGPCompressedData cData = (PGPCompressedData) message;
            JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
            message = pgpFact.nextObject();
        }

        if (message instanceof PGPLiteralData) {
            PGPLiteralData ld = (PGPLiteralData) message;
            content = new String(ld.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        } 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()) {
                log.error("message failed integrity check");
            } else {
                log.info("message integrity check passed");
            }
        } else {
            log.warn("no message integrity check");
        }
    } catch (PGPException e) {
        log.error("Exception encountered while decrypting file.", e);
    }
    return content;
}

private static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass) throws PGPException {
    PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
    if (pgpSecKey == null) {
        return null;
    }

    return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
}

private static PGPPublicKey readPublicKey(String publicKey) throws IOException, PGPException {
    InputStream input = IOUtils.toInputStream(publicKey, StandardCharsets.UTF_8);

    PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());

    Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
    while (keyRingIter.hasNext()) {
        PGPPublicKeyRing keyRing = keyRingIter.next();

        Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
        while (keyIter.hasNext()) {
            PGPPublicKey key = keyIter.next();

            if (key.isEncryptionKey()) {
                return key;
            }
        }
    }

    throw new IllegalArgumentException("Can't find encryption key in key ring.");
}

您的 encrypt() 方法代码中存在一些错误:

首先,在这一行中,您将直接在装甲输出流上打开文字数据流,而您应该在加密流上打开它:

OutputStream pOut = lData.open(aOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
// replace with
OutputStream pOut = lData.open(cOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());

此外,您应该更换行

OutputStream cOut = encGen.open(aOut, bytes.length);
// with
OutputStream cOut = encGen.open(aOut, new byte[1<<9]);

否则您将运行陷入编码问题。 这些更改应该使您的代码 运行.

不过,我建议使用 PGPainless(免责声明:此处为作者)之类的库,它会为您完成所有低级流包装和检查。最重要的是,它进行了适当的签名验证,这是大多数其他基于 Bouncycastle 的库所缺乏的。

这是最终对我有用的加密方法。 它与 BCFipsIn100 书中提供的示例一起帮助调试。

public byte[] encrypt(byte[] bytes, String publicKey) throws IOException {

    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
        byte[] compressedData = compress(bytes);

        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
                        .setWithIntegrityPacket(true)
                        .setSecureRandom(new SecureRandom())
                        .setProvider(PROVIDER));

        PGPPublicKey encryptionKey = readPublicKey(publicKey);
        encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey).setProvider(PROVIDER));

        ArmoredOutputStream aOut = new ArmoredOutputStream(byteArrayOutputStream);
        OutputStream cOut = encGen.open(aOut, compressedData.length);
        cOut.write(compressedData);
        cOut.close();
        aOut.close();

        return byteArrayOutputStream.toByteArray();
    } catch (PGPException e) {
        log.error("Exception encountered while encoding data.", e);
    }

    return new byte[0];
}

private static byte[] compress(byte[] clearData) throws IOException {
    try (ByteArrayOutputStream bOut = new ByteArrayOutputStream()) {
        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
        OutputStream cos = comData.open(bOut);

        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream pOut = lData.open(cos,
                PGPLiteralData.BINARY,
                PGPLiteralData.CONSOLE,
                clearData.length,
                new Date());

        pOut.write(clearData);
        pOut.close();

        comData.close();

        return bOut.toByteArray();
    }
}