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

使用CNG API解密p7m文件的步骤及密钥识别问题咨询

使用CNG API解密PKCS#7(p7m)Blob的正确流程

CNG API本身没有提供直接解析PKCS#7加密消息结构的能力,因此解密p7m的合理流程是结合CryptoAPI的消息解析函数与CNG的密钥操作API,既解决Legacy API无法访问CNG密钥的问题,又避免遍历所有密钥的低效操作。具体步骤如下:

1. 解析PKCS#7消息,提取收件人信息

首先用CryptoAPI的CryptMsg系列函数解析p7m的结构,获取收件人的证书标识(Issuer+序列号,或主题密钥标识符),这一步和Legacy API的逻辑一致,因为只是处理消息格式,不涉及密钥存储:

  • 调用CryptMsgOpenToDecode初始化消息解析上下文,无需指定密钥(仅解析结构):
    HCRYPTMSG hMsg = CryptMsgOpenToDecode(
        PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
        0,
        CMSG_ENVELOPED,
        NULL,
        NULL,
        NULL
    );
    
  • 调用CryptMsgUpdate传入完整的p7m数据:
    BOOL success = CryptMsgUpdate(hMsg, pP7mData, dwP7mSize, TRUE);
    
  • 获取收件人数量,遍历每个收件人提取证书信息:
    DWORD dwRecipientCount = 0;
    DWORD dwSize = sizeof(dwRecipientCount);
    CryptMsgGetParam(hMsg, CMSG_RECIPIENT_COUNT_PARAM, 0, &dwRecipientCount, &dwSize);
    
    for (DWORD i = 0; i < dwRecipientCount; i++) {
        CMSG_RECIPIENT_INFO recipInfo = {0};
        dwSize = sizeof(recipInfo);
        CryptMsgGetParam(hMsg, CMSG_RECIPIENT_INFO_PARAM, i, &recipInfo, &dwSize);
    
        if (recipInfo.dwRecipientInfoType == CMSG_KEY_TRANS_RECIPIENT_INFO) {
            PCMSG_KEY_TRANS_RECIPIENT_INFO pKeyTrans = recipInfo.pKeyTrans;
            // 拿到用于查找证书的CERT_INFO(包含Issuer和序列号)
            PCERT_INFO pCertInfo = &pKeyTrans->RecipientCertInfo;
            // 保存该信息用于后续查找证书
        }
    }
    

2. 通过证书标识查找本地证书

利用步骤1获取的CERT_INFO,在本地个人证书存储中定位对应的证书:

  • 打开本地个人证书存储:
    HCERTSTORE hStore = CertOpenSystemStore(NULL, L"MY");
    
  • 调用CertFindCertificateInStore匹配证书:
    PCCERT_CONTEXT pCertCtx = CertFindCertificateInStore(
        hStore,
        PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
        0,
        CERT_FIND_SUBJECT_CERT_INFO,
        pCertInfo,
        NULL
    );
    

3. 获取证书对应的CNG私钥

通过CryptAcquireCertificatePrivateKey直接获取与证书关联的CNG密钥句柄,无需手动枚举CNG密钥:

NCRYPT_KEY_HANDLE hKey = NULL;
DWORD dwKeySpec = 0;
BOOL success = CryptAcquireCertificatePrivateKey(
    pCertCtx,
    CRYPT_ACQUIRE_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG,
    NULL,
    &hKey,
    &dwKeySpec,
    NULL
);

这里的CRYPT_ACQUIRE_NCRYPT_KEY_FLAG强制函数返回CNG密钥句柄,完美适配CNG KSP存储的密钥。

4. 解密PKCS#7中的加密会话密钥

从p7m消息中提取加密的会话密钥,用CNG密钥解密:

  • 获取Enveloped信息,找到对应收件人的加密密钥:
    CMSG_ENVELOPED_INFO envInfo = {0};
    dwSize = sizeof(envInfo);
    CryptMsgGetParam(hMsg, CMSG_ENVELOPED_INFO_PARAM, 0, &envInfo, &dwSize);
    
    PCMSG_ENCRYPTED_KEY_ENCODE_INFO pEncryptedKey = NULL;
    for (DWORD i = 0; i < envInfo.cRecipientEncryptedKeys; i++) {
        // 匹配步骤1中对应的收件人索引或证书信息
        if (/* 匹配逻辑,比如比较Issuer和序列号 */) {
            pEncryptedKey = &envInfo.rgpRecipientEncryptedKeys[i];
            break;
        }
    }
    
  • 调用NCryptDecrypt解密会话密钥:
    BYTE pbSessionKey[256] = {0};
    DWORD dwSessionKeySize = sizeof(pbSessionKey);
    SECURITY_STATUS status = NCryptDecrypt(
        hKey,
        pEncryptedKey->EncryptedKey.pbData,
        pEncryptedKey->EncryptedKey.cbData,
        NULL,
        pbSessionKey,
        dwSessionKeySize,
        &dwSessionKeySize,
        0
    );
    

5. 用会话密钥解密明文数据

提取p7m中的加密明文,用CNG对称加密API解密(以AES为例,PKCS#7通常使用PKCS#5填充):

  • 获取加密的明文内容:
    BYTE pbEncryptedContent[4096] = {0};
    DWORD dwEncryptedContentSize = sizeof(pbEncryptedContent);
    CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, pbEncryptedContent, &dwEncryptedContentSize);
    
  • 使用CNG对称解密:
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, NULL, 0);
    
    BCRYPT_KEY_HANDLE hSessionKey = NULL;
    BCryptGenerateSymmetricKey(hAlg, &hSessionKey, NULL, 0, pbSessionKey, dwSessionKeySize, 0);
    
    BYTE pbPlaintext[4096] = {0};
    DWORD dwPlaintextSize = sizeof(pbPlaintext);
    BCryptDecrypt(
        hSessionKey,
        pbEncryptedContent,
        dwEncryptedContentSize,
        NULL,
        envInfo.EncryptionAlgorithm.Parameters.pbData, // 使用EnvelopedInfo中的IV
        envInfo.EncryptionAlgorithm.Parameters.cbData,
        pbPlaintext,
        dwPlaintextSize,
        &dwPlaintextSize,
        BCRYPT_BLOCK_PADDING
    );
    

关键说明

  • 无需遍历所有CNG密钥:通过p7m中的收件人证书标识直接定位证书,再关联到CNG密钥,是最高效的方式。
  • Legacy API失败原因:Legacy CryptoAPI默认使用CSP(加密服务提供程序),无法直接访问CNG KSP中的密钥,而CryptAcquireCertificatePrivateKey可以跨CSP/CNG获取密钥,解决了兼容性问题。

内容的提问来源于stack exchange,提问作者Dmitry Streblechenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 21:22:03