让Java(Bouncy Castle)实现GPG解密

18

首先声明我对这一切都非常陌生。我的目标是在Java中使用gpg解密一个加密文件。

我已经成功做到了以下几点:

  • 让同事使用我的公钥和他的私钥加密文件,并成功解密。

  • 反向操作

  • 让另一个同事尝试解密不属于他的文件:失败(预期)

我的密钥是这样生成的...

(gpg --version告诉我我正在使用1.4.5版本,我正在使用Bouncy Castle 1.47)

gpg --gen-ley

选择选项“DSA and Elgamal (default)”

填写其他字段并生成一个密钥。

该文件是使用我的公钥和另一个人的秘密密钥进行加密的。 我想解密它。 我编写了以下Java代码来完成此操作。 我使用了几个已弃用的方法,但我无法弄清楚如何正确实现工厂方法以使用非过时版本,因此如果有人知道我应该使用哪些实现那将是一个很好的奖励。

    Security.addProvider(new BouncyCastleProvider());

        PGPSecretKeyRingCollection secretKeyRing = new PGPSecretKeyRingCollection(new FileInputStream(new File("test-files/secring.gpg")));
        PGPSecretKeyRing pgpSecretKeyRing = (PGPSecretKeyRing) secretKeyRing.getKeyRings().next();
        PGPSecretKey secretKey = pgpSecretKeyRing.getSecretKey();
        PGPPrivateKey privateKey = secretKey.extractPrivateKey("mypassword".toCharArray(), "BC");

        System.out.println(privateKey.getKey().getAlgorithm());
        System.out.println(privateKey.getKey().getFormat());

        PGPObjectFactory pgpF = new PGPObjectFactory(
    new FileInputStream(new File("test-files/test-file.txt.gpg")));
        Object pgpObj = pgpF.nextObject();
        PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) pgpObj;

        Iterator objectsIterator = encryptedDataList.getEncryptedDataObjects();

        PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) objectsIterator.next();
        InputStream inputStream = publicKeyEncryptedData.getDataStream(privateKey, "BC");

因此,当我运行这段代码时,我了解到我的算法和格式如下,用于我的秘密密钥:

算法: DSA 格式: PKCS#8

然后它在最后一行中出现错误:

Exception in thread "main" org.bouncycastle.openpgp.PGPException: error setting asymmetric cipher
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.access$000(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder$2.recoverSessionData(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at TestBouncyCastle.main(TestBouncyCastle.java:74)

由于: java.security.InvalidKeyException: 传递给ElGamal的未知密钥类型 at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source) at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source) at javax.crypto.Cipher.init(DashoA13*..) at javax.crypto.Cipher.init(DashoA13*..) ... 8 more

我对很多建议持开放态度,从“不要使用gpg,改用x”到“不要使用bouncy castle,改用x”,任何在中间的建议都可以。谢谢!

5个回答

18

如果有人想知道如何使用Bouncy Castle 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;
}

以下方法将读取并导入您的公钥文件(.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;
    }

以下是解密和加密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();
    }

现在,以下是如何调用/运行上述内容的方法:
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());
        }

这是2018年!我有一个问题。 我正在加密一个.xlsx文件(Bank_21_05_2018.xlsx)。 输出文件与原始文件看起来相同(相同的名称),但现在我无法打开它。 该文件名是否应该仅为Bank_21_05_2018.gpg? 我是PGP的新手。 - Black Diamond

4

如果有人正在寻找替代方案,请查看https://dev59.com/Veo6XIcBkEYKwwoYLhTO#42176529

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

长话短说:Bouncycastle编程通常存在很多空口说白话编程,我写了一个库来改变这种情况。


我可以使用这个库仅使用私钥进行解密吗? - Rocky4Ever

3

我决定采用一种完全不同的方法,放弃使用bouncy castle,而是直接使用运行时进程。对我来说,这个解决方案起作用,并且完全消除了围绕bouncy castle的复杂性:

String[] gpgCommands = new String[] {
        "gpg",
        "--passphrase",
        "password",
        "--decrypt",
        "test-files/accounts.txt.gpg"
};

Process gpgProcess = Runtime.getRuntime().exec(gpgCommands);
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgError = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));

完成此操作后,需要记得在进程执行时清空输入流,否则你的程序可能会挂起,这取决于你输出了多少内容。有关更多上下文信息,请参见我在此线程中的回答(以及Cameron Skinner和Matthew Wilson的回答): 通过运行时进程在Java中调用GnuPG进行文件加密和解密 - 解密总是挂起

13
由于明显的原因,你的密码短语可能会被任何查看活动进程列表的人所看到,并且在呼叫期间它也可能会出现在系统日志中。 - user359996
这里的密码是什么? - Rocky4Ever

1

第一个Google的结果是this。看起来你正在尝试解密ElGamal数据,但你没有传入ElGamal密钥。

有两个简单的可能性:

  • 你的密钥环集合有多个密钥环。
  • 你的密钥环有子密钥。

你选择了DSA与ElGamal加密,所以我怀疑至少后者:子密钥由主密钥签名;ElGamal不是签名算法(我不知道DSA和ElGamal是否可以使用相同的密钥,但通常认为为不同的目的使用不同的密钥是一个好主意)。

我认为你想要这样的东西(另外,secretKeyRing可能应该重命名为secretKeyRingCollection):

PGPSecretKey secretKey = secretKeyRing.getSecretKey(publicKeyEncryptedData.getKeyID());

感谢回复。我同意命名方案。我的密钥肯定有一个子密钥(至少在使用gpg --list-keys查看时是这样)。但我可以在命令行中使用它进行解密。那么我在这里的问题是什么? - Craig
ElGamal密钥是子密钥吗? - tc.
嗨tc。我已经决定采用Java中不同的方法进行gpg解密。非常感谢您的回复,但对我来说有一个更简单的解决方案,我将在下面的答案中详细说明。 - Craig

-1

错误信息很难懂,因为它不完全准确。除了非法密钥大小或默认参数之外,异常并没有说明可能由于加密权限检查失败而导致失败。这意味着您没有正确设置JCE权限。您需要安装JCE Unlimited Strength Policy

您可以通过在jvm上设置系统属性来查看调试消息。

java -Djava.security.debug=access ....

为什么会有负面评价?!其他帖子都没有指出你可以通过添加Java系统属性来查看底层消息。如果这样做,您将获得一条更直接的消息,指出您需要安装无限制强度策略。我遇到了这个错误,通过上述方法解决了它。 - chubbsondubs
我没有点踩,但是:_嵌套_异常(由...引起)非常清楚;_你的_情况(旧版Java受限策略)总是会出现“InvalidKeyException: illegal key size”,而不是“wrong key type”。此外,你现在来晚了;自2017年8u161以来的Java版本根本没有受限策略问题,因为更好的下载页面https://www.oracle.com/java/technologies/javase-jce-all-downloads.html有所解释。 - dave_thompson_085
我认为这个信息并不是很清晰。我怀疑许多人也不会觉得它有帮助。有人可能会请求一个不受支持的非法密钥大小,然后得到这个信息,而这与策略限制无关。无论是否迟到,其他答案都没有解释如何获得更直接的错误信息,这在你认为已经安装好东西后进行调试时非常有帮助。 - chubbsondubs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接