使用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
相关产品推荐
相关产品推荐

