【问题标题】:using bouncy castle to create public PGP key usable by Thunderbird使用充气城堡创建可供 Thunderbird 使用的公共 PGP 密钥
【发布时间】:2015-03-30 12:08:40
【问题描述】:

我使用org.bouncycastle.openpgp.PGPKeyRingGenerator 创建了公共和私人PGP 密钥。在进行 GregS 建议的更改后,公钥为.asc 文件,私钥为.skr 文件。我需要先将公钥分发给 Thunderbird 用户,然后再分发给 Outlook 和其他电子邮件客户端的用户。我阅读了these instructions for receiving a public key in thunderbird,但说明仅指定了.asc 扩展名,而没有指定.asc 文件的内容/结构。

如何设置,以便我的(修改后的?)代码创建一个公钥,可供 Thunderbird 的远程用户用来发送加密的电子邮件,然后可以用我的私钥解密,我的私钥也是由(修改后的)创建的?) 下面的代码? 接受的答案将包括分步说明,不仅用于对以下代码进行任何必要的更改,还用于设置每个远程 Thunderbird 用户使用以下生成的公钥发送电子邮件由我的应用程序中的私钥解密,由下面的(修改?)代码创建。

这是我的密钥生成代码的初稿:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Date;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.sig.Features;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;  

public class RSAGen {
    public static void main(String args[]) throws Exception {
        char pass[] = {'h', 'e', 'l', 'l', 'o'};
        PGPKeyRingGenerator krgen = generateKeyRingGenerator("alice@example.com", pass);

        // Generate public key ring, dump to file.
        PGPPublicKeyRing pkr = krgen.generatePublicKeyRing();
        ArmoredOutputStream pubout = new ArmoredOutputStream(new BufferedOutputStream(new FileOutputStream("/home/user/dummy.asc")));
        pkr.encode(pubout);
        pubout.close();

        // Generate private key, dump to file.
        PGPSecretKeyRing skr = krgen.generateSecretKeyRing();
        BufferedOutputStream secout = new BufferedOutputStream(new FileOutputStream("/home/user/dummy.skr"));
        skr.encode(secout);
        secout.close();
    }

    public final static PGPKeyRingGenerator generateKeyRingGenerator(String id, char[] pass) throws Exception{
        return generateKeyRingGenerator(id, pass, 0xc0); 
    }

    // Note: s2kcount is a number between 0 and 0xff that controls the number of times to iterate the password hash before use. More
    // iterations are useful against offline attacks, as it takes more time to check each password. The actual number of iterations is
    // rather complex, and also depends on the hash function in use. Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
    // you more iterations.  As a rough rule of thumb, when using SHA256 as the hashing function, 0x10 gives you about 64
    // iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0, or about 1 million iterations. The maximum you can go to is
    // 0xff, or about 2 million iterations.  I'll use 0xc0 as a default -- about 130,000 iterations.

    public final static PGPKeyRingGenerator generateKeyRingGenerator(String id, char[] pass, int s2kcount) throws Exception {
        // This object generates individual key-pairs.
        RSAKeyPairGenerator  kpg = new RSAKeyPairGenerator();

        // Boilerplate RSA parameters, no need to change anything
        // except for the RSA key-size (2048). You can use whatever key-size makes sense for you -- 4096, etc.
        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), new SecureRandom(), 2048, 12));

        // First create the master (signing) key with the generator.
        PGPKeyPair rsakp_sign = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN, kpg.generateKeyPair(), new Date());
        // Then an encryption subkey.
        PGPKeyPair rsakp_enc = new BcPGPKeyPair(PGPPublicKey.RSA_ENCRYPT, kpg.generateKeyPair(), new Date());

        // Add a self-signature on the id
        PGPSignatureSubpacketGenerator signhashgen = new PGPSignatureSubpacketGenerator();

        // Add signed metadata on the signature.
        // 1) Declare its purpose
        signhashgen.setKeyFlags(false, KeyFlags.SIGN_DATA|KeyFlags.CERTIFY_OTHER);
        // 2) Set preferences for secondary crypto algorithms to use when sending messages to this key.
        signhashgen.setPreferredSymmetricAlgorithms
            (false, new int[] {
                SymmetricKeyAlgorithmTags.AES_256,
                SymmetricKeyAlgorithmTags.AES_192,
                SymmetricKeyAlgorithmTags.AES_128
            });
        signhashgen.setPreferredHashAlgorithms
            (false, new int[] {
                HashAlgorithmTags.SHA256,
                HashAlgorithmTags.SHA1,
                HashAlgorithmTags.SHA384,
                HashAlgorithmTags.SHA512,
                HashAlgorithmTags.SHA224,
            });
        // 3) Request senders add additional checksums to the message (useful when verifying unsigned messages.)
        signhashgen.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);

        // Create a signature on the encryption subkey.
        PGPSignatureSubpacketGenerator enchashgen = new PGPSignatureSubpacketGenerator();
        // Add metadata to declare its purpose
        enchashgen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS|KeyFlags.ENCRYPT_STORAGE);

        // Objects used to encrypt the secret key.
        PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);
        PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA256);

        // bcpg 1.48 exposes this API that includes s2kcount. Earlier versions use a default of 0x60.
        PBESecretKeyEncryptor pske = (new BcPBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha256Calc, s2kcount)).build(pass);

        // Finally, create the keyring itself. The constructor takes parameters that allow it to generate the self signature.
        PGPKeyRingGenerator keyRingGen =
            new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, rsakp_sign,
         id, sha1Calc, signhashgen.generate(), null,
             new BcPGPContentSignerBuilder(rsakp_sign.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), pske);

        // Add our encryption subkey, together with its signature.
        keyRingGen.addSubKey(rsakp_enc, enchashgen.generate(), null);
        return keyRingGen;
    }
}

当我运行上面的代码生成.asc文件,然后尝试将.asc文件导入Thunderbird时,我得到以下错误屏幕:

请注意,我没有在我的 CentOS 7 机器上安装 GnuPG。

另外,您可以在自己的机器上轻松重现此问题,因为 Thunderbird 是免费的。 You can download thunderbird for free using this link。或者,在我的 CentOS 7 机器上,我使用 yum install thunderbird 下载了 Thunderbird。您可以通过将以下内容添加到您的pom.xml 来下载充气城堡:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpg-jdk15on</artifactId>
    <version>1.51</version>
</dependency>

编辑#1:

为了解决 JRichardSnape 的问题,我发现 maven 也必须自动下载 org.bouncycastle.crypto 库,因为它是 bcpg-jdk15on 的依赖项。 JRichardSnape 是正确的,RSAKeyGenerationParameters 和 RSAKeyPairGenerator 不在 bcpg-jdk15on.jar 手动下载中。 (注意:链接中的版本可能不是最新的。)但是,这两个类都在自动 maven 下载中,这是由上面显示的 pom.xml 的单个依赖项 sn-p 产生的。 我这样说因为我的pom.xml 中没有其他bouncycastle 依赖项。我正在使用 Java 7。

Eclipse 将导入的两个类描述为:

import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;  

我将 RSAGen.java 中的所有 import 语句添加到上面我的 OP 中的代码段中。我认为问题可能与需要密钥的名称/签名有关。

谷歌搜索此错误会导致以下链接等:

Convert userId to UTF8 before generating signature #96
non-ascii characters in Name field #92
Unable to import PGP certificate into Keychain

编辑#2

按照@JRichardSnape 的建议,我尝试了Enigmail-&gt;Key management -&gt;File -&gt;Import keys from file。这导致出现以下对话框,这似乎表明在导入密钥时,密钥未签名。因此,似乎没有与导入的.asc 文件关联的名称或电子邮件地址。之后,该密钥也不会出现在 EnigMail 的密钥列表中。

编辑#3

使用gpg --gen-key,我能够让 CentOS 7 终端创建一个密钥对,包括一个公钥,我能够成功地将它导入到 Thunderbird,以便 Thunderbird 现在可以关联终端-gpg 生成的公共与预期的电子邮件收件人密匙。但是,当我采取所有步骤使用公钥从 Thunderbird 发送加密电子邮件时,电子邮件及其附件仍然未加密。我将带有公钥的加密电子邮件从远程 Thunderbird 发送到具有私钥的服务器所采取的步骤是 described in this SuperUser posting。

鉴于gpg --gen-key 似乎有效,目前剩下的主要问题似乎是这个赏金问题的 Thunderbird 部分。我已经在上一段的超级用户问题中发布了解决 Thunderbird 部分的很多进展。您回答它的帮助也将对回答这个问题大有帮助。

编辑#4

我仍然无法获得 BouncyCastle-created 的密钥来导入 Thunderbird。但是,当我使用在CentOS 7 终端上使用gpg --gen-key 创建的密钥时,我可以完成以下步骤:

1.) I configured my Thunderbird to manage another (second) 
    email account I have not been using.  
2.) I then created a gpg key for that second account and 
    configured encryption for that second account in Thunderbird.  
3.) I sent an encrypted email containing an attachment from the  
    first Thunderbird account to the second Thunderbird account.
4.) I was able to see that the attachment remained encrypted in  
    the second account's inbox until I used the recipient key's  
    passphrase to decrypt it.

我的CentOS 7 服务器仍然在产生未加密的附件,当我从本次编辑中描述的相同“第一个”Thunderbird 帐户向它发送电子邮件时。我正在尝试确定这是否是由于 CentOS 7 服务器中的 dovecot/postfix/mailx/gpg 中的某些“自动解密”,还是由于 Thunderbird 中的某些设置发件人。我正在研究这个。

【问题讨论】:

  • .asc 表示文件是 ASCII 装甲的。有关此过程的说明,请参阅 here。查看 bouncycastle 中的 ArmoredOutputStream 和 ArmoredInputStream 类。
  • 我提供了一些提示,我不知道 Thunderbird 期望什么。

标签: java email encryption bouncycastle pgp


【解决方案1】:

我将尝试一一解决这些问题:

Java bouncycastle 密钥环生成

Java 代码确实有效并生成了一个可用的密钥环对。我已经用不同的电子邮件和不同的密码对其进行了测试,没有任何问题。我让第 3 方使用公钥向我发送了一封电子邮件,并使用此 Java 代码生成的私钥成功解密了它。该键已与以下组合一起使用

  • Thunderbird (31.4.0) + Enigmail (1.7.2) + gpg (Gpg4win) 在 Windows 8 上
  • ubuntu 14.10 上的 Thunderbird + Enigmail(使用 xfer 桌面管理器)

然而

OP 发现导入密钥时出现问题,失败意味着CentOS / Thunderbird / pgp 组合中没有用户 ID。同样,它也无法导入,并出现错误提示 Windows / Outlook / Kleopatra 插件上没有用户 ID(虽然问题专门引用了 Thunderbird,但经过测试)。

我无法重现该错误 - 强烈怀疑这是由于 GNU PG 中的配置差异或版本差异造成的。我的设置为gpg --version 显示以下内容

gpg (GnuPG) 2.0.26 (Gpg4win 2.2.3)
libgcrypt 1.6.2

直接用 gpg 测试 Java 生成的密钥

您可以使用您的 java 代码生成密钥,转到命令行并执行

gpg --import dummy.asc

那么测试就是通过执行

gpg --edit-key alice@example.com

通过在gpg&gt; 提示符下键入check 来检查它是否有用户ID。示例输出:

uid  alice@example.com
sig!3        14AEE94A 2015-02-05  [self-signature]

如果这可行 - 您已经消除了密钥导入的 gpg 问题 - 检查 Thunderbird / Enigmail 版本。

使用雷鸟

我的评论似乎已经解决了大部分问题,建议通过Enigmail-&gt;Key management -&gt;File -&gt;Import keys from file 结合来自超级用户的 OP 的this related question 导入密钥。

另请注意 - 在 Enigmail 的密钥管理对话框中有一个“生成”选项。如果不需要 Java 生成,这可以用来生成密钥对 - 据我所知,它与直接通过 gpg 生成基本相同。


使用 Thunderbird 的剩余问题似乎是对加密缺乏信心,因为消息以纯文本形式显示在 OP 的服务器上(似乎密钥将在客户端发送的服务器/客户端组合中使用加密电子邮件到服务器)。

为了确信邮件确实被加密了 - 我建议更改 Enigmail 设置:

  • Enigmail -> Preference -> Sending tab
  • 选择"Manual encryption settings"
  • 在"confirm before sending" 框中选择"Always"

然后您会在发送前看到加密邮件和一个确认框。

我不能说如何阻止您的服务器自动解密传入的邮件,因为这取决于您的服务器配置,最好作为一个单独的问题提出,很可能在 superuser 或 serverfault StackExchange 网站上.

测试 PGP 电子邮件

您也可以考虑听从the Enigmail tutorial 的建议并发送加密邮件至

Adele,“友好的 OpenPGP 电子邮件机器人”。阿黛尔接受 OpenPGP 以解释方式对任何类型的 OpenPGP 的消息和回复 消息。

地址是adele &lt;at&gt; gnupp &lt;dot&gt; de

【讨论】:

  • 太棒了 - 真的很高兴你能通过它:)
  • 我有一个相关的问题。你愿意帮助我吗?除了密钥的生成和使用之外,一切都设置好了。当我使用上面的 BountyCastle 代码生成 PGP 密钥时,CentOS 7 上的 Thunderbird 在我尝试导入时看不到用户名和电子邮件。但是当我使用gpg --gen-key 生成 GPG 密钥时,我的其他 BouncyCastle 代码无法解密。我很乐意寻找您的其他答案,这些答案根据各自的优点保证 +1。这是新帖子的链接:stackoverflow.com/questions/28444819/…
  • 好的,我去看看。我现在在打电话,以后再说。我知道我的第一个问题是您能否发布两种方法的示例(使用 alice@example),以便我们进行比较和对比。
猜你喜欢
  • 2013-05-25
  • 2011-06-23
  • 1970-01-01
  • 2015-06-29
  • 1970-01-01
  • 2015-08-09
  • 2015-06-07
  • 1970-01-01
  • 2013-04-20
相关资源
最近更新 更多