如何利用RecipientInformation找到解密smime.p7m消息的正确私钥?
如何匹配S/MIME解密所需的正确私钥
你可以通过RecipientInformation对象携带的收件人标识信息,与你持有的私钥对应的证书做匹配,找到能解密当前S/MIME消息的私钥,具体步骤如下:
- 遍历
RecipientInformationStore中的每一个RecipientInformation实例,不要直接取第一个(避免消息有多个收件人时选错) - 对每个
RecipientInformation,提取其收件人标识:
大部分S/MIME加密场景使用的是KeyTransRecipientInformation,可以将其强转为该类型后,调用getRecipientID()方法获取RecipientID对象。这个对象会包含两种标识之一:IssuerAndSerialNumber:证书的颁发者DN和序列号SubjectKeyIdentifier:证书的主题密钥标识符
- 遍历你的私钥集合,每个私钥通常会关联对应的证书(可从KeyStore中获取私钥对应的证书链,取第一个元素即为用户证书)
- 对比证书与
RecipientID中的标识:- 如果是
IssuerAndSerialNumber:检查证书的颁发者DN(cert.getIssuerDN())和序列号(cert.getSerialNumber())是否与标识中的一致 - 如果是
SubjectKeyIdentifier:获取证书的主题密钥标识符扩展,对比是否与标识中的字节数组一致
- 如果是
- 找到匹配的私钥后,即可传入
JceKeyTransEnvelopedRecipient进行解密
修改后的代码示例:
String mimeType = mail.getContentType(); if (mimeType == null) { return mail; } ContentType contentType = new ContentType(mimeType); if ("application/pkcs7-mime".equals(contentType.getBaseType()) && "smime.p7m".equals(contentType.getParameter("name"))) { Object content = mail.getContent(); if (content instanceof InputStream) { CMSEnvelopedDataParser ep = new CMSEnvelopedDataParser((InputStream) content); RecipientInformationStore recipients = ep.getRecipientInfos(); // 遍历所有收件人信息 for (RecipientInformation recipientInfo : recipients.getRecipients()) { if (recipientInfo instanceof KeyTransRecipientInformation) { KeyTransRecipientInformation keyTransRecipient = (KeyTransRecipientInformation) recipientInfo; RecipientID recipientID = keyTransRecipient.getRecipientID(); // 遍历你的私钥集合(假设privateKeys是你的私钥列表,每个元素包含私钥和对应证书) for (KeyEntry keyEntry : privateKeys) { X509Certificate cert = keyEntry.getCertificate(); boolean match = false; // 匹配颁发者和序列号 if (recipientID.getIssuer() != null && recipientID.getSerialNumber() != null) { match = recipientID.getIssuer().equals(cert.getIssuerDN()) && recipientID.getSerialNumber().equals(cert.getSerialNumber()); } // 匹配主题密钥标识符 else if (recipientID.getSubjectKeyIdentifier() != null) { byte[] skid = getSubjectKeyIdentifier(cert); match = Arrays.equals(recipientID.getSubjectKeyIdentifier(), skid); } if (match) { // 找到匹配的私钥,执行解密 byte[] decryptedContent = recipientInfo.getContent( new JceKeyTransEnvelopedRecipient(keyEntry.getPrivateKey()).setProvider(provider) ); // 处理解密后的内容 return processDecryptedContent(decryptedContent); } } } } } } // 辅助方法:获取证书的主题密钥标识符 private static byte[] getSubjectKeyIdentifier(X509Certificate cert) throws CertificateParsingException { byte[] extensionValue = cert.getExtensionValue("2.5.29.14"); if (extensionValue == null) { return null; } // 解析DER编码的扩展值 ASN1Primitive primitive = JcaX509ExtensionUtils.parseExtensionValue(extensionValue); if (primitive instanceof ASN1OctetString) { return ((ASN1OctetString) primitive).getOctets(); } return null; }
注:KeyEntry是自定义类,用来封装私钥和对应的证书,你可以根据自己的私钥存储方式调整这部分逻辑。
内容的提问来源于stack exchange,提问作者Horcrux7
相关产品推荐
相关产品推荐

