让 BouncyCastle 解密 GPG 加密的消息

Cod*_*Med 5 java encryption bouncycastle gnupg public-key-encryption

如何让 BouncyCastle 解密 GPG 加密的消息?

我在 CentOS 7 命令行中使用gpg --gen-key. 我选择了 RSA RSA 作为加密类型,并使用gpg --export-secret-key -a "User Name" > /home/username/username_private.key和导出了密钥gpg --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.
Run Code Online (Sandbox Code Playgroud)

如果您查看下面的代码,您将看到这与PGPUtils.decryptFile()状态所在的行相对应else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");

原始代码来自此链接的博客条目,尽管我做了一些小改动以使其在带有 Java 7 的 Eclipse Luna 中编译。链接博客的用户报告了同样的错误,博客作者回复说它不适用于 GPG。 那么我如何解决这个问题以使其与 GPG 一起使用?

当 GPG-encoded-file 和 GPG-secret-key 传入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
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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);
}  
Run Code Online (Sandbox Code Playgroud)

单击此链接,可以在文件共享站点上找到所有三个 Java 文件的完整代码。

单击此链接可以找到错误的完整堆栈跟踪。

作为参考,远程 Thunderbird 发送器的 GUI 说明总结在以下屏幕截图中:

我已经阅读了很多关于此的帖子和链接。特别是,这个其他 SO 发布看起来相似,但不同。我的密钥使用 RSA RSA,但其他帖子没有。

编辑#1

根据@DavidHook 的建议,我已经阅读了SignedFileProcessor,并且我开始阅读更长的RFC 4880。但是,我需要实际的工作代码来研究才能理解这一点。大多数通过谷歌搜索找到这个的人还需要工作代码来说明示例。

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

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.");}
}
Run Code Online (Sandbox Code Playgroud)

编辑#2

SignedFileProcessor.verifyFile()通过@DavidHook推荐的方法是几乎相同的PGPUtils.verifyFile()在我的代码上述方法中,不同之处在于PGPUtils.verifyFile()使副本extractContentFile并调用PGPOnePassSignature.init()代替PGPOnePassSignature.initVerify()。这可能是由于版本差异。此外,PGPUtils.verifyFile()返回一个布尔值,同时SignedFileProcessor.verifyFile()为两个布尔值提供 SYSO,并在 SYSO 之后返回 void。

如果我正确解释了@JRichardSnape 的评论,这意味着verifyFile()最好在上游调用该方法以使用发件人的公钥确认传入文件的签名,然后,如果文件上的签名得到验证,则使用另一种方法来解密文件使用收件人的私钥。这样对吗?如果是这样,我该如何重构代码来实现这一点?

小智 6

如果有人有兴趣知道如何使用充气城堡 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;
}
Run Code Online (Sandbox Code Playgroud)

以下方法将从 .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;
}
Run Code Online (Sandbox Code Playgroud)

以下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();
}
Run Code Online (Sandbox Code Playgroud)

现在这里是如何调用/运行上面的:

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());
    }
Run Code Online (Sandbox Code Playgroud)


Dav*_*ook 2

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

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

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