为什么我的密钥标识符不匹配?

时间:2011-06-29 15:20:53

标签: java certificate bouncycastle smime

我正在尝试解密S / MIME电子邮件(最初通过Outlook发送),为此,我正在使用bouncycastle API。不过,我遇到了麻烦。

在Windows证书存储区中,我有收件人的证书。我之前用它将签名和加密的电子邮件发送给另一方,然后他们用它向我发送加密回复。然后我将证书(带私钥)导出为.pfx文件,然后将此pfx文件加载到Java KeyStore中。但是,它不起作用,我怀疑这是因为主题密钥标识符不匹配。

这是我用来从KeyStore获取主题密钥id的代码:

KeyStore ks = KeyStore.getInstance("PKCS12");
char[]   pw = "password".toCharArray();

ks.load(new FileInputStream("d:\\cert_priv_key.pfx"), pw);

Enumeration en = ks.aliases();

while( en.hasMoreElements() )
{
    String alias = (String)en.nextElement();
    System.out.println(alias);

    if( ks.isKeyEntry(alias) )
    {
        Certificate[]   chain = ks.getCertificateChain(alias);
        X509Certificate cert  = (X509Certificate)chain[0];

        byte[] id = cert.getExtensionValue("2.5.29.14");

        System.out.println("  " + toHex(id));
    }
}

这将打印出以下密钥标识符:

04 16 04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

但是,当我检查Windows证书存储区时,密钥标识符是不同的:

88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

KeyStore在前面返回额外的4个字节(主题密钥标识符应该是密钥的160位SHA1散列,因此长20个字节,正确吗?)。

更令人困惑的是,当我使用bouncycastle API解析S / MIME电子邮件并通过收件人(SMIMEEnveloped.getRecipientInfos().getRecipients())时,唯一收到的收件人(应该只有一个)有这个主题密钥标识符:

04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

...它只有两个额外的字节,而不是四个,我想这就是为什么我无法用证书解密电子邮件。

为什么这些主题密钥标识符都不匹配?我做错了什么?

3 个答案:

答案 0 :(得分:16)

如果您了解所有规格,所有这些答案都是一致的,但当然这意味着如果您不理解它们会让您感到困惑。首先要看的是RFC 5280, section 4.2.1.2。在这种情况下,使用方法(1)。接下来,在KeyIdentifier的定义中查看section A.2。它被定义为OCTET STRING。现在看看如何ASN.1 OCTET STRING should be encoded。它应该以十六进制04开始,后跟长度(以字节为单位)(20字节或14十六进制),后跟实际的八位字节字符串(SHA1哈希)。所以扩展的内容应该是

04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

最后,查看Extension的ASN.1定义。它表示扩展值必须编码为OCTET STRING。在这种特定扩展的情况下,净效果是连续两次编码为OCTET STRING。在此级别,OCTET STRING是前一个OCTET STRING,其中包含两个头字节04 14,因此长度为十六进制16,编码为

04 16 04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

这是X509Extension.getExtensionValue()方法返回的值。现在,密钥id的有趣部分只是以88开头的SHA1哈希,因此这就是Windows实用程序显示的内容。显然,您正在使用的bouncycastle方法显示扩展而不进行额外的解码。

答案 1 :(得分:1)

答案 2 :(得分:0)

GregS接受的答案帮助了我很多。

最终为我工作的代码是:

X509Certificate certificate = ...
byte[] encExtensionSubjectKeyIdentifier = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId());

// Unwrap first 'layer'
ASN1Primitive skiPrimitive = JcaX509ExtensionUtils.parseExtensionValue(encExtensionSubjectKeyIdentifier);

// Unwrap second 'layer'
byte[] keyIdentifier = ASN1OctetString.getInstance(skiPrimitive.getEncoded()).getOctets();

// Use keyIdentifier in e.g. CMS SignerInfo
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keyIdentifier);