让BouncyCastle解密GPG加密的消息

时间:2015-02-11 01:00:35

标签: java encryption bouncycastle public-key-encryption gnupg

如何让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客户端,并成功将加密的电子邮件发送到username@mydomain.com。但是当我在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,虽然我做了一些小改动,让它在Eclipse Luna中用Java 7编译。链接博客的用户报告了同样的错误,博客作者回复了说它不适用于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,但另一个帖子没有。

编辑#1

根据@ DavidHook的建议,我已阅读SignedFileProcessor,我开始阅读的时间更长RFC 4880。但是,我需要学习实际工作代码才能理解这一点。大多数通过谷歌搜索找到这一点的人也需要工作代码来说明这些例子。

作为参考,@ 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.");}
}

编辑#2

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

如果我正确解释@ JRichardSnape的评论,这意味着SignedFileProcessor.verifyFile()方法可能最好被称为上游,以使用发件人的公钥确认传入文件的签名,然后,如果签名上验证文件,使用另一种方法使用收件人的私钥解密文件。它是否正确?如果是这样,我如何重组代码来实现这一目标?

2 个答案:

答案 0 :(得分:4)

如果有兴趣知道如何使用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());
    }

答案 1 :(得分:1)

这只是意味着内容已经签名然后加密,提供的例程不知道如何处理它,但至少告诉你。 PGP协议表示为一系列数据包,其中一些可以包装在其他数据包中(例如压缩数据也可以包装有符号数据或简单文字数据,这些数据也可用于生成加密数据,实际内容总是出现在文字数据中)。

如果查看Bouncy Castle OpenPGP示例包中SignedFileProcessor中的verifyFile方法,您将看到如何处理签名数据并获取包含实际内容的文字数据。

我还建议您查看RFC 4880,以便了解协议的工作原理。协议非常宽松,GPG,BC和各种产品都反映了这一点 - 说松散意味着如果你试图剪切并粘贴到解决方案中,你最终会遇到灾难。它并不复杂,但也需要理解。