获取BouncyCastle来解密GPG加密的消息

问题描述 投票:3回答:2

我如何获得BouncyCastle来解密GPG加密的消息?

我已经使用gpg --gen-key在CentOS 7命令行上创建了一个GPG密钥对。我选择RSA RSA作为加密类型,并使用gpg --export-secret-key -a "User Name" > /home/username/username_private.keygpg --armor --export 66677FC6 > /home/username/username_pubkey.asc

导出了密钥

我能够将username_pubkey.asc导入另一个电子邮件帐户的远程Thunderbird客户端,并成功将加密的电子邮件发送到[email protected]。但是,当我在mydomain.com上运行的Java / BouncyCastle代码尝试解密GPG编码的数据时,会出现以下错误:

org.bouncycastle.openpgp.PGPException:  
Encrypted message contains a signed message - not literal data.

如果您看下面的代码,您将看到它与PGPUtils.decryptFile()中指出else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");的行相对应>

原始代码来自the blog entry at this link,尽管我做了一些小的改动以使其可以在Java 7的Eclipse Luna中进行编译。链接博客的用户报告了相同的错误,博客作者回答说:不适用于GPG。 所以我该如何解决才能使其与GPG兼容?

[如下所示,当将GPG编码文件和GPG秘密密钥传递到Tester.testDecrypt()中时,Java解密代码开始:

Tester.java包含:

public InputStream testDecrypt(String input, String output, String passphrase, String skeyfile) throws Exception {
    PGPFileProcessor p = new PGPFileProcessor();
    p.setInputFileName(input);//this is GPG-encoded data sent from another email address using Thunderbird
    p.setOutputFileName(output);
    p.setPassphrase(passphrase);
    p.setSecretKeyFileName(skeyfile);//this is the GPG-generated key
    return p.decrypt();//this line throws the error
}

PGPFileProcessor.java包括:

public InputStream decrypt() throws Exception {
    FileInputStream in = new FileInputStream(inputFileName);
    FileInputStream keyIn = new FileInputStream(secretKeyFileName);
    FileOutputStream out = new FileOutputStream(outputFileName);
    PGPUtils.decryptFile(in, out, keyIn, passphrase.toCharArray());//error thrown here
    in.close();
    out.close();
    keyIn.close();
    InputStream result = new FileInputStream(outputFileName);//I changed return type from boolean on 1/27/15
    Files.deleteIfExists(Paths.get(outputFileName));//I also added this to accommodate change of return type on 1/27/15
    return result;
}

PGPUtils.java包括:

/**
 * decrypt the passed in message stream
 */
@SuppressWarnings("unchecked")
public static void decryptFile(InputStream in, OutputStream out, InputStream keyIn, char[] passwd)
    throws Exception
{
    Security.addProvider(new BouncyCastleProvider());

    in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in);

    //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
    PGPObjectFactory 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<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
    PGPPrivateKey sKey = null;
    PGPPublicKeyEncryptedData pbe = null;

    while (sKey == null && it.hasNext()) {
        pbe = it.next(); 
        sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd);
    }

    if (sKey == null) {throw new IllegalArgumentException("Secret key for message not found.");}

    InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));

    //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
    PGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);

    Object message = plainFact.nextObject();

    if (message instanceof  PGPCompressedData) {
        PGPCompressedData cData = (PGPCompressedData) message;
        //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
        PGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream()); 
        message = pgpFact.nextObject();
    }

    if (message instanceof  PGPLiteralData) {
        PGPLiteralData ld = (PGPLiteralData) message;

        InputStream unc = ld.getInputStream();
        int ch;

        while ((ch = unc.read()) >= 0) {out.write(ch);}
    } 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()) {throw new PGPException("Message failed integrity check");}
    }
}

/**
 * Load a secret key ring collection from keyIn and find the private key corresponding to
 * keyID if it exists.
 *
 * @param keyIn input stream representing a key ring collection.
 * @param keyID keyID we want.
 * @param pass passphrase to decrypt secret key with.
 * @return
 * @throws IOException
 * @throws PGPException
 * @throws NoSuchProviderException
 */
public  static PGPPrivateKey findPrivateKey(InputStream keyIn, long keyID, char[] pass)
    throws IOException, PGPException, NoSuchProviderException
{
    //1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
    PGPSecretKeyRingCollection pgpSec = new JcaPGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
    return findPrivateKey(pgpSec.getSecretKey(keyID), pass);

}

/**
 * Load a secret key and find the private key in it
 * @param pgpSecKey The secret key
 * @param pass passphrase to decrypt secret key with
 * @return
 * @throws PGPException
 */
public static PGPPrivateKey findPrivateKey(PGPSecretKey pgpSecKey, char[] pass)
    throws PGPException
{
    if (pgpSecKey == null) return null;

    PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
    return pgpSecKey.extractPrivateKey(decryptor);
}  

可以在文件共享站点by clicking on this link上找到所有三个Java文件的完整代码。

可以找到错误的完整堆栈跟踪by clicking on this link

作为参考,以下屏幕快照总结了由远程Thunderbird发送方进行加密的GUI指令:

我已经阅读了许多与此相关的帖子和​​链接。特别是this other SO posting looks similar,但有所不同。我的密钥使用RSA RSA,但其他发布则不使用。

EDIT#1

根据@DavidHook的建议,我已阅读SignedFileProcessor,并且我开始阅读更长的RFC 4880。但是,我需要学习实际的工作代码才能理解这一点。通过Google搜索找到此内容的大多数人还需要有效的代码来说明示例。

供参考,@ DavidHook推荐的SignedFileProcessor.verifyFile()方法如下。 应该如何定制以解决上面代码中的问题?

private static void verifyFile(InputStream in, InputStream keyIn) throws Exception {
    in = PGPUtil.getDecoderStream(in);
    PGPObjectFactory pgpFact = new PGPObjectFactory(in);
    PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
    pgpFact = new PGPObjectFactory(c1.getDataStream());
    PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
    PGPOnePassSignature ops = p1.get(0);
    PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
    InputStream dIn = p2.getInputStream();
    int ch;
    PGPPublicKeyRingCollection  pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
    PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID());
    FileOutputStream out = new FileOutputStream(p2.getFileName());
    ops.initVerify(key, "BC");
    while ((ch = dIn.read()) >= 0){
        ops.update((byte)ch);
        out.write(ch);
    }
    out.close();
    PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
    if (ops.verify(p3.get(0))){System.out.println("signature verified.");}
    else{System.out.println("signature verification failed.");}
}

EDIT#2

@ DavidHook推荐的SignedFileProcessor.verifyFile()方法与我上面的代码中的PGPUtils.verifyFile()方法几乎相同,除了PGPUtils.verifyFile()复制extractContentFile并调用PGPOnePassSignature.init()而不是PGPOnePassSignature.initVerify()。这可能是由于版本差异造成的。同样,PGPUtils.verifyFile()返回一个布尔值,而SignedFileProcessor.verifyFile()给出两个布尔值的SYSO并在SYSO之后返回void。

如果我正确解释了@JRichardSnape的注释,这意味着最好在上游调用verifyFile()方法,以使用发送者的公钥来确认传入文件的签名,然后,如果验证了文件上的签名, ,使用另一种方法使用收件人的私钥解密文件。这个对吗?如果是这样,我如何重组代码以完成此操作?

我如何获得BouncyCastle来解密GPG加密的邮件?我已经使用gpg --gen-key在CentOS 7命令行上创建了一个GPG密钥对。我选择RSA RSA作为加密类型,然后选择[...

java encryption bouncycastle public-key-encryption gnupg
2个回答
© www.soinside.com 2019 - 2024. All rights reserved.