找不到合适的公钥用于加密 p12 电子邮件地址

问题描述

我试图用 bouncy-gpg 和我的 p12 密钥加密文件。 但它收到错误 No (suitable) public key for encryption to p12 email address found 老实说,我是一个拥有充气城堡的新手。 如有任何建议,我们将不胜感激。

            KeyStore keystore = KeyStore.getInstance("PKCS12","SunjsSE");
            keystore.load(is,p12Password.tochararray());
            String alias = keystore.aliases().nextElement();

            PrivateKey privateKey = (PrivateKey)keystore.getKey(alias,p12Password.tochararray());

            Certificate cert = keystore.getCertificate(alias);
            PublicKey publicKey = cert.getPublicKey();

            X509Certificate x509cert = (X509Certificate) cert;
            X509Principal principal = PrincipalUtil.getSubjectX509Principal(x509cert);
            Vector<?> values = principal.getValues(X509Name.EmailAddress);
            String email = (String) values.get(0);

            JcaPGPKeyConverter jcaPGPKeyConverter = new JcaPGPKeyConverter();
            PGPPublicKey pgpPublicKey = jcaPGPKeyConverter.getPGPPublicKey(1,publicKey,new Date());

            PGPPrivateKey pgpPrivateKey = jcaPGPKeyConverter.getPGPPrivateKey(pgpPublicKey,privateKey);
            PGPSecretKey pgpSecretKey = new PGPSecretKey(pgpPrivateKey,pgpPublicKey,null,true,null);

            final InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withPassword(p12Password));
            keyring.addPublicKey(pgpPublicKey.getEncoded());
            keyring.addSecretKey(pgpSecretKey.getEncoded());

            final OutputStream outputStream = BouncyGPG
                            .encryptToStream()
                            .withConfig(keyring)
                            .withStrongalgorithms()
                            .toRecipient(email)
                            .andDoNotSign()
                            .binaryOutput()
                            .andWriteto(bufferedOut);

解决方法

好的,这花了一些时间,因为 that project 显然是一个真正的“kOOlaid Drinker”——一个类中的 10 行代码可以轻松完成的相关操作需要 20 个类中的 50 个方法分布在多个层次结构的一部分,有很多不需要的重新调度。需要明确的是,这不是 BouncyCastle,它大多是理智的,虽然并不完美,而且经常没有得到很好的评论; Bouncy-GPG 位于 BouncyCastle 之上。

PGP 密钥可以具有多种关联的元数据——通常是用户标识和签名,很少有属性。 See RFC4880 section 11.1 and 11.2. 由真正的 PGP 程序(如 GnuPG)创建的 PGP 密钥将始终添加此元数据,尽管根据程序可能随后将其删除。因为 PGP 最初设计用于电子邮件,传统上并且通常用户 ID 包含电子邮件地址,可选地加上其他信息,但 technically this is not required 和用户 ID 可以是任何字符串。除了实际签名之外,PGP 密钥签名还包含带有其他数据的“子数据包”。此外,PGP 密钥通常——现在几乎总是——进入 groups of one masterkey and one or more subkey(s) 链接到该主密钥,如上面链接的第 11.1 节所示。在这种情况下,用户 ID 适用于(并且相同)组中的所有密钥,但每个实际密钥和/或用户 ID 的签名是分开的,并且通常每个实际密钥上至少有一个由“所有者”签名' 即由用户 ID(总是由主密钥)标识的实体,其中包含定义密钥到期(如果有)和使用标志的子包。正如我在另一个 Q 中指出的那样,BouncyCastle 将这组相关键称为 {Public,Secret}KeyRing,而其他软件使用该名称来表示可以包含多个这样的组的文件,从而增加了混乱。

并且大多数 PGP 程序允许您在需要时(用于加密或签名)通过用户 ID(或其中之一)或“keyid”(或指纹或密钥夹,取决于程序)选择一个密钥)。在前一种情况下,如果使用子密钥,它会根据使用情况(即加密或签名)自动选择适当的;在后一种情况下,通常您只需使用主密钥的 keyid 并让它选择子密钥,但如果合适,您可以通过 keyid 指定特定的(子)密钥。 (对于解密和验证,需要使用与之前加密或签名相同的实际密钥,通常会通过keyid自动选择正确的(子)密钥,但在某些情况下可能需要或可以手动选择。) 特别是 BouncyCastle 在它调用 KeyRingCollection 的级别允许您选择一个密钥组,它通过 userid 或 keyid 调用 KeyRing:请参阅 {{3} 的 javadoc } 和 PGPPublicKeyRingCollection

但 Bouncy-GPG 没有:它只使用前一个选项并通过整个用户 ID 或用户 ID 的电子邮件部分进行选择,而不是通过 keyid 进行选择。 JcaPGPKeyConverter 从 JCA 转换的密钥没有用户 ID,因此它们不能被用户 ID 电子邮件选择,因此可以' t 被 Bouncy-GPG 用于需要此类选择的操作。

有两种方法可以解决这个问题。这些键对象可以直接与 BouncyCastle 一起使用,代码类似于 PGPSecretKeyRingCollection 中的代码,除了使用转换后的键而不是从“KeyRingCollection”中选择的键(通常是一个密钥环文件),如示例所示。

另一种方法是不使用仅对应于实际键的简单 PGP{Public,Secret}Key 对象,而是使用元数据创建 KeyRing。这让我进入了一些未评论的领域,这花了一段时间并留下了一些不确定的东西,但我认为这样的事情会奏效:

    String userid = "<email@host>"; // Bouncy-GPG defaults to email,but can be changed for other userids
    PGPSignatureSubpacketGenerator hsub = new PGPSignatureSubpacketGenerator();
    hsub.setKeyFlags(false,0xF); // should be enough for masterkey-only,I hope
    // don't bother with expiration,features,or (multiple) preferences,for now at least
    // the generator below automatically adds hashed 2=signtime and unhashed 16=issuerkeyid
    PGPKeyRingGenerator gen = new PGPKeyRingGenerator(
            PGPSignature.DEFAULT_CERTIFICATION,new PGPKeyPair(pub2,prv2),userid,new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1),hsub.generate(),/*unhash*/null,new JcaPGPContentSignerBuilder(pub2.getAlgorithm(),HashAlgorithmTags.SHA1),new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.NULL).build(new char[0]) );
    // I'm not sure ContentSigner will work right for all key-algos with SHA1,may need to tweak;
    // if so I don't _think_ DigestCalculator has to match ContentSigner,but I could be wrong
    // use gen.generatePublicKeyRing() directly or its .getEncoded() see below
    // and gen.generateSecretKeyRing() directly or its .getEncoded() see below

注意 Bouncy-GPG their example KeyBasedFileProcessor 实际上将它们的(编码的)参数解析为 KeyRing(在 BC 的含义中)而不仅仅是一个简单的密钥(技术上这是此类组的最小情况),或者您可以使用来自 BC 的 add{Public,Secret}KeyRing 的(结构化)对象直接调用 generate{Public,Secret}KeyRing

ADDED:我想说,但忘了,这个设计有味道。公钥加密的全部意义在于我们通常使用属于另一个人或实体(或其他几个人)的公钥进行加密,并且我们永远不应该拥有此类其他人(公钥)密钥的私钥,只有我们自己的密钥.这对于 PGP 格式和信任模型以及 PKCS12 和 X.509 等其他格式都是正确的。对于电子邮件,PGP 被设计并经常用于,有一个部分例外,我们可能想要加密给另一个人或多个人,给我们自己相同的消息(并保存它)以便我们以后可以准确地检查发送的内容,以防沟通不畅或意见不一致。但你没有这样做;您仅对您拥有私钥的密钥进行加密,这没有任何安全优势且毫无意义。

呸。

,

修改后的代码:

            hsub.setKeyFlags(false,0xF);

            PGPKeyRingGenerator gen = new PGPKeyRingGenerator(
                    PGPSignature.DEFAULT_CERTIFICATION,new PGPKeyPair(pgpPublicKey,pgpPrivateKey),'<' + email + '>',null,new JcaPGPContentSignerBuilder(pgpPublicKey.getAlgorithm(),new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.NULL).build(p12Password.toCharArray())
            );

            //PGPSecretKey pgpSecretKey = new PGPSecretKey(pgpPrivateKey,pgpPublicKey,true,null);

            final InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withPassword(p12Password));
            keyring.addPublicKey(gen.generatePublicKeyRing().getEncoded());
            keyring.addSecretKey(gen.generateSecretKeyRing().getEncoded());

            try (
                    final InputStream cipherTextStream = Files.newInputStream(sourceFile);

                    final OutputStream fileOutput = Files.newOutputStream(destFile);
                    final BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOutput,BUFFERSIZE);

                    final InputStream plaintextStream = BouncyGPG
                            .decryptAndVerifyStream()
                            .withConfig(keyring)
                            .andValidateSomeoneSigned()
                            .fromEncryptedInputStream(cipherTextStream)