【问题标题】:Getting GPG Decryption To Work In Java (Bouncy Castle)让 GPG 解密在 Java 中工作(Bouncy Castle)
【发布时间】:2013-02-21 01:43:17
【问题描述】:

首先让我说我对这一切都非常陌生。我正在尝试做的是在 Java 中使用 gpg 来解密加密文件。

我成功完成了什么:

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

  • 另辟蹊径

  • 让另一位同事尝试解密不适合他的文件:失败(如预期)

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

(gpg --version 告诉我我使用的是 1.4.5,而我使用的是 Bouncy Castle 1.47)

gpg --gen-ley

选择选项“DSA 和 Elgamal(默认)”

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

该文件是使用我的公钥和另一个人的密钥加密的。我想解密它。我编写了以下 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 的未知密钥类型 在 org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(未知来源) 在 org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(未知来源) 在 javax.crypto.Cipher.init(DashoA13*..) 在 javax.crypto.Cipher.init(DashoA13*..) ... 8 更多

我愿意接受很多建议,从“不要使用 gpg,使用 x”到“不要使用充气城堡,使用 x”,再到介于两者之间的任何建议。谢谢!

【问题讨论】:

    标签: java bouncycastle openpgp elgamal


    【解决方案1】:

    如果有人有兴趣了解如何使用 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;
        }
    

    以下2种解密和加密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 新手
    【解决方案2】:

    对于任何寻找替代解决方案的人,请参阅https://stackoverflow.com/a/42176529/7550201

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

    长话短说:Bouncycastle 的编程通常有很多 cargo cult programming,我编写了一个库来改变这一点。

    【讨论】:

    • 我可以使用这个库仅使用私钥解密吗?
    【解决方案3】:

    我决定采用一种截然不同的方法,即完全放弃使用充气城堡,而只使用运行时进程。对我来说,这个解决方案是有效的,并且完全消除了充气城堡周围的复杂性:

    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 的答案)以获得更多背景信息:Calling GnuPG in Java via a Runtime Process to encrypt and decrypt files - Decrypt always hangs

    【讨论】:

    • -1 原因很明显。此外,在通话期间,查看活动进程列表的任何人都可能会看到您的密码,也可能会出现在系统日志中。
    • 这里的密码是多少?
    【解决方案4】:

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

    有两种简单的可能性:

    • 您的钥匙圈收藏有多个钥匙圈。
    • 您的密钥环有子密钥。

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

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

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

    【讨论】:

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

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

    你可以通过在jvm上设置系统属性来查看调试信息

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

    【讨论】:

    • 为什么投反对票?!其他帖子都没有指出您实际上可以通过添加 java 系统属性来查看底层消息。如果您这样做,那么您将收到一条更直接的消息,说明您需要安装无限强度策略。我得到了那个错误并按照上面的方法修复了它。
    • 我没有投反对票,但是:nested 异常(由以下原因引起)非常清楚; 您的 案例(具有有限策略的旧 Java)将始终是“InvalidKeyException:非法密钥大小”,永远不会是“错误的密钥类型”。另外,您现在参加聚会已经很晚了;正如更好的下载页面 oracle.com/java/technologies/javase-jce-all-downloads.html 解释的那样,自 2017 年 8u161 以来的 Java 版本根本不再存在受限策略问题。
    • 我认为这个信息一点都不清晰。我怀疑许多其他人也不会觉得它有帮助。有人总是可以要求不支持的非法密钥大小并获得该消息,这与策略限制无关。不管迟到与否,其他答案都没有解释如何获得更直接的错误消息,这在您相信已安装后调试内容时很有帮助。
    猜你喜欢
    • 2021-11-04
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 1970-01-01
    • 2013-02-20
    • 2011-08-20
    • 2012-06-21
    • 1970-01-01
    相关资源
    最近更新 更多