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

如何通过Win32 API编程提取本地计算机证书存储中的公钥

问题描述

我已将crt文件导入本地计算机的「个人证书」存储中。
证书存储导入界面
我可通过MMC的「Base64编码X509 .CER」选项提取对应公钥。
我的目标是通过Win32 API编程实现该公钥提取功能。
目前我尝试了以下代码,但base64Cert中存储的数据与公钥不匹配。请问有什么方法可以获取正确的数据?

HCERTSTORE hCertStore;
PCCERT_CONTEXT pCertContext = NULL;


if (!(hCertStore = CertOpenStore(
    CERT_STORE_PROV_SYSTEM,
    MY_ENCODING_TYPE,
    NULL,
    CERT_SYSTEM_STORE_LOCAL_MACHINE,
    L"MY")))
{
    std::cout<<"The MY system store did not open."<< std::endl;
}



while (pCertContext = CertEnumCertificatesInStore(
    hCertStore,
    pCertContext))
{
    LPTSTR pszString;
    LPTSTR pszName;
    DWORD cbSize;
    CERT_BLOB blobEncodedName;

    PBYTE pbPKEY = NULL;
    DWORD iPKEYSize;



    bool ret = CryptDecodeObjectEx(X509_ASN_ENCODING,
        RSA_CSP_PUBLICKEYBLOB,
        pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData,
        pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData,
        CRYPT_ENCODE_ALLOC_FLAG,
        NULL,
        &pbPKEY,
        &iPKEYSize);

    CERT_CONTEXT certcontext;
    certcontext.pbCertEncoded = pbPKEY;
    certcontext.cbCertEncoded = iPKEYSize;

    DWORD dwData;
    if (!CryptBinaryToStringA(certcontext.pbCertEncoded, certcontext.cbCertEncoded, CRYPT_STRING_BASE64HEADER, nullptr, &dwData))
    {
        std::cout << "error\n";
    }
    std::string base64Cert(dwData, '\0');
    CryptBinaryToStringA(certcontext.pbCertEncoded, certcontext.cbCertEncoded, CRYPT_STRING_BASE64HEADER, &base64Cert[0], &dwData);
    
    std::cout << base64Cert << "\n";
    
}

MMC导出公钥选项

解决方案

你的代码存在两个核心问题:

  • 错误转换公钥格式:RSA_CSP_PUBLICKEYBLOB是微软私有格式的公钥Blob,并非MMC导出的标准X509格式公钥,直接将其当作证书上下文处理会导致格式不匹配。
  • 解码对象类型错误:你需要导出的是包含完整公钥结构的X509格式数据,而非转换为RSA特定Blob。

正确实现步骤:
直接提取证书中的SubjectPublicKeyInfo字段,将其编码为标准X509 DER格式,再转换为带头部的Base64字符串,即可和MMC导出结果一致。

修改后的代码示例:

#include <windows.h>
#include <wincrypt.h>
#include <iostream>
#include <string>

#pragma comment(lib, "crypt32.lib")

#define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

int main() {
    HCERTSTORE hCertStore;
    PCCERT_CONTEXT pCertContext = NULL;

    // 打开本地计算机的个人证书存储
    if (!(hCertStore = CertOpenStore(
        CERT_STORE_PROV_SYSTEM,
        MY_ENCODING_TYPE,
        NULL,
        CERT_SYSTEM_STORE_LOCAL_MACHINE,
        L"MY")))
    {
        std::cout << "无法打开本地计算机的个人证书存储。" << std::endl;
        return 1;
    }

    // 枚举存储中的证书
    while (pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext))
    {
        PBYTE pbX509PublicKey = NULL;
        DWORD cbX509PublicKey = 0;

        // 将SubjectPublicKeyInfo编码为标准X509 DER格式
        if (!CryptEncodeObjectEx(
            MY_ENCODING_TYPE,
            X509_SUBJECT_PUBLIC_KEY_INFO,
            &pCertContext->pCertInfo->SubjectPublicKeyInfo,
            CRYPT_ENCODE_ALLOC_FLAG | CRYPT_ENCODE_TO_BLOB,
            NULL,
            &pbX509PublicKey,
            &cbX509PublicKey))
        {
            std::cout << "编码X509公钥失败,错误码:" << GetLastError() << std::endl;
            continue;
        }

        // 计算Base64字符串所需缓冲区大小
        DWORD dwBase64Size = 0;
        if (!CryptBinaryToStringA(pbX509PublicKey, cbX509PublicKey, CRYPT_STRING_BASE64HEADER, NULL, &dwBase64Size))
        {
            std::cout << "计算Base64缓冲区大小失败,错误码:" << GetLastError() << std::endl;
            LocalFree(pbX509PublicKey);
            continue;
        }

        // 转换为带头部的Base64字符串
        std::string base64Cert(dwBase64Size, '\0');
        if (!CryptBinaryToStringA(pbX509PublicKey, cbX509PublicKey, CRYPT_STRING_BASE64HEADER, &base64Cert[0], &dwBase64Size))
        {
            std::cout << "转换为Base64失败,错误码:" << GetLastError() << std::endl;
            LocalFree(pbX509PublicKey);
            continue;
        }

        // 输出结果
        std::cout << "提取的X509公钥(Base64):\n" << base64Cert << "\n" << std::endl;

        // 释放内存
        LocalFree(pbX509PublicKey);
    }

    // 关闭证书存储
    CertCloseStore(hCertStore, 0);
    return 0;
}

关键说明:

  • 使用CryptEncodeObjectEx并指定X509_SUBJECT_PUBLIC_KEY_INFO类型,直接将证书内的公钥信息编码为标准X509 DER格式,和MMC导出的底层数据一致。
  • CRYPT_STRING_BASE64HEADER参数会自动添加-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----头部,完全匹配MMC的导出格式。
  • 注意内存释放:通过CRYPT_ENCODE_ALLOC_FLAG分配的内存,必须用LocalFree释放,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:35:07