如何在Microsoft CNG中计算证书的Subject Key Identifier扩展?
在CNG中实现证书Subject Key Identifier(SKI)扩展填充
之前用CAPI的CryptHashPublicKeyInfo生成SKI确实省心,但现在这个函数被弃用了,好在我们可以照着它的逻辑,用CNG的API手动实现——毕竟官方文档已经明确说了,它的核心就是对公钥信息做SHA1哈希。下面是具体的实现思路和代码示例:
核心逻辑拆解
SKI(OID 2.5.29.14)的生成规则很明确:
- 先把公钥以对应算法的标准格式(比如RSA用PKCS#1格式)导出为原始字节流
- 对这个字节流计算SHA1哈希,得到的20字节结果就是SKI的核心值
- 最后把哈希结果包装成证书扩展格式,添加到目标证书中
具体实现步骤
- 获取CNG公钥句柄:从证书对象或者密钥存储中拿到
NCRYPT_KEY_HANDLE类型的公钥句柄 - 导出公钥原始字节流:调用
NCryptExportKey函数,指定对应算法的导出格式(比如RSA用BCRYPT_RSA_PUBLIC_KEY_BLOB) - 计算SHA1哈希:用CNG的哈希API对导出的公钥字节流进行哈希运算
- 构造并添加SKI扩展:把哈希结果包装成证书扩展结构,通过
CertAddExtension函数添加到证书中
代码示例(C++)
#include <windows.h> #include <bcrypt.h> #include <ncrypt.h> #include <wincrypt.h> #pragma comment(lib, "bcrypt.lib") #pragma comment(lib, "ncrypt.lib") #pragma comment(lib, "crypt32.lib") BOOL GenerateSKI(NCRYPT_KEY_HANDLE hKey, BYTE** ppSKI, DWORD* pdwSKILen) { if (!hKey || !ppSKI || !pdwSKILen) return FALSE; *ppSKI = nullptr; *pdwSKILen = 0; // 1. 导出公钥Blob DWORD dwBlobLen = 0; if (NCryptExportKey(hKey, nullptr, BCRYPT_RSA_PUBLIC_KEY_BLOB, nullptr, 0, &dwBlobLen, 0) != ERROR_SUCCESS) return FALSE; BYTE* pBlob = new BYTE[dwBlobLen]; if (!pBlob) return FALSE; if (NCryptExportKey(hKey, nullptr, BCRYPT_RSA_PUBLIC_KEY_BLOB, pBlob, dwBlobLen, &dwBlobLen, 0) != ERROR_SUCCESS) { delete[] pBlob; return FALSE; } // 2. 初始化SHA1哈希算法 BCRYPT_ALG_HANDLE hHashAlg = nullptr; if (BCryptOpenAlgorithmProvider(&hHashAlg, BCRYPT_SHA1_ALGORITHM, nullptr, 0) != ERROR_SUCCESS) { delete[] pBlob; return FALSE; } DWORD dwHashLen = 0; DWORD dwResultLen = sizeof(DWORD); if (BCryptGetProperty(hHashAlg, BCRYPT_HASH_LENGTH, (BYTE*)&dwHashLen, dwResultLen, &dwResultLen, 0) != ERROR_SUCCESS) { BCryptCloseAlgorithmProvider(hHashAlg, 0); delete[] pBlob; return FALSE; } // 3. 计算公钥Blob的SHA1哈希 BYTE* pHash = new BYTE[dwHashLen]; if (!pHash) { BCryptCloseAlgorithmProvider(hHashAlg, 0); delete[] pBlob; return FALSE; } BCRYPT_HASH_HANDLE hHash = nullptr; if (BCryptCreateHash(hHashAlg, &hHash, nullptr, 0, nullptr, 0, 0) != ERROR_SUCCESS) { delete[] pHash; BCryptCloseAlgorithmProvider(hHashAlg, 0); delete[] pBlob; return FALSE; } if (BCryptHashData(hHash, pBlob, dwBlobLen, 0) != ERROR_SUCCESS) { BCryptDestroyHash(hHash); delete[] pHash; BCryptCloseAlgorithmProvider(hHashAlg, 0); delete[] pBlob; return FALSE; } if (BCryptFinishHash(hHash, pHash, dwHashLen, 0) != ERROR_SUCCESS) { BCryptDestroyHash(hHash); delete[] pHash; BCryptCloseAlgorithmProvider(hHashAlg, 0); delete[] pBlob; return FALSE; } // 4. 清理临时资源并返回结果 BCryptDestroyHash(hHash); BCryptCloseAlgorithmProvider(hHashAlg, 0); delete[] pBlob; *ppSKI = pHash; *pdwSKILen = dwHashLen; return TRUE; } // 示例:将生成的SKI添加到证书 BOOL AddSKIToCert(PCCERT_CONTEXT pCert, BYTE* pSKI, DWORD dwSKILen) { if (!pCert || !pSKI || dwSKILen == 0) return FALSE; CERT_EXTENSION skiExt = {0}; skiExt.pszObjId = szOID_SUBJECT_KEY_IDENTIFIER; skiExt.fCritical = FALSE; skiExt.Value.cbData = dwSKILen; skiExt.Value.pbData = pSKI; return CertAddExtension(&pCert->pCertInfo->Extension, &skiExt, 0); }
关键注意事项
- 对于非RSA算法(比如ECDSA),需要替换导出格式为对应算法的Blob类型,比如
BCRYPT_ECCPUBLIC_BLOB,哈希算法仍用SHA1(SKI标准要求统一使用SHA1,与公钥算法无关) - 确保导出的是公钥的原始信息,避免带有额外包装的格式影响哈希结果
- 使用CNG句柄后要调用对应的关闭函数(比如
BCryptCloseAlgorithmProvider、NCryptFreeObject),防止资源泄漏
内容的提问来源于stack exchange,提问作者Timothy John Laird
相关产品推荐
相关产品推荐

