You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 21:40:35