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

如何在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的核心值
  • 最后把哈希结果包装成证书扩展格式,添加到目标证书中

具体实现步骤

  1. 获取CNG公钥句柄:从证书对象或者密钥存储中拿到NCRYPT_KEY_HANDLE类型的公钥句柄
  2. 导出公钥原始字节流:调用NCryptExportKey函数,指定对应算法的导出格式(比如RSA用BCRYPT_RSA_PUBLIC_KEY_BLOB)
  3. 计算SHA1哈希:用CNG的哈希API对导出的公钥字节流进行哈希运算
  4. 构造并添加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 06:42:48