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

如何在OpenSSL中切换加密提供程序并生成可导出的Windows证书密钥

问题解决与代码修改方案

问题核心原因

  1. OpenSSL默认生成PKCS#8格式的RSA私钥,Windows导入时会自动选用CNG架构的Microsoft Software Key Storage Provider,而非传统CSP的Microsoft Enhanced Cryptographic Provider v1.0。
  2. 原导入流程未显式关联私钥与证书,导致系统无法识别私钥。

修改后的完整代码

1. 生成兼容传统CSP的RSA密钥对

调整密钥生成逻辑,确保输出PKCS#1格式的私钥(传统CSP仅支持该格式):

EVP_PKEY* GenerateRsaKeyPair(int keyStrength)
{
    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
    if (nullptr == ctx)
    {
        std::cerr << "ERROR: Failed to generate ctx contex for RSA Key pair." << std::endl;
        return nullptr;
    }

    if (EVP_PKEY_keygen_init(ctx) <= 0)
    {
        std::cerr << "ERROR: Failed to intialize ctx contex for RSA Key pair." << std::endl;
        EVP_PKEY_CTX_free(ctx);
        return nullptr;
    }

    // 设置PKCS1填充,确保密钥格式兼容传统CSP
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
    {
        std::cerr << "ERROR: Failed to set RSA padding." << std::endl;
        EVP_PKEY_CTX_free(ctx);
        return nullptr;
    }

    if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keyStrength) <= 0)
    {
        std::cerr << "ERROR: Failed to set bits for ctx contex for RSA Key pair." << std::endl;
        EVP_PKEY_CTX_free(ctx);
        return nullptr;
    }

    EVP_PKEY* pKey = nullptr;
    if (EVP_PKEY_keygen(ctx, &pKey) <= 0)
    {
        std::cerr << "ERROR: Failed to generate RSA Key pair." << std::endl;
        EVP_PKEY_CTX_free(ctx);
        return nullptr;
    }

    EVP_PKEY_CTX_free(ctx);
    return pKey;
}

2. 自签名证书生成(仅修正冗余代码)

原证书生成逻辑存在重复设置公钥的问题,已优化:

X509* GenerateSelfSignedCertificate(EVP_PKEY* pKey, const unsigned char* issuer, int days)
{
    if (nullptr == pKey)
    {
        std::cerr << "ERROR: Failed to generate certificate because RSA Keys are not provided." << std::endl;
        return nullptr;
    }
    if (nullptr == issuer)
    {
        std::cerr << "ERROR: Failed to generate certificate because an issuer is not provided." << std::endl;
        return nullptr;
    }
    X509* x509 = X509_new();
    if (nullptr == x509)
    {
        std::cerr << "Failed to create X509 object." << std::endl;
        return nullptr;
    }
    if (X509_set_version(x509, 2) != 1) // version 3对应内部编号2
    {
        std::cerr << "Failed to set certificate version." << std::endl;
        X509_free(x509);
        return nullptr;
    }
    // 生成随机序列号
    long long r = rand() % 999;
    long long serial = std::time(0) * 1000 + r;
    BIGNUM* bignum = BN_new();
    BN_set_word(bignum, serial);

    ASN1_INTEGER* aserial = BN_to_ASN1_INTEGER(bignum, NULL);
    X509_set_serialNumber(x509, aserial);
    BN_free(bignum);
    ASN1_INTEGER_free(aserial);

    if (X509_set_pubkey(x509, pKey) != 1)
    {
        std::cerr << "Failed to set public key of certificate." << std::endl;
        X509_free(x509);
        return nullptr;
    }
    // 设置证书主体与颁发者
    auto xname = X509_get_subject_name(x509);
    X509_NAME_add_entry_by_txt(xname, "CN", MBSTRING_ASC, issuer, -1, -1, 0);
    X509_set_issuer_name(x509, xname);

    // 设置有效期
    X509_gmtime_adj(X509_get_notBefore(x509), 0);
    X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60 * 24 * days);

    // 添加密钥用法扩展
    X509_EXTENSION* ex;
    X509V3_CTX ctx;
    X509V3_set_ctx_nodb(&ctx);
    X509V3_set_ctx(&ctx, x509, x509, NULL, NULL, 0);
    ex = X509V3_EXT_conf_nid(NULL, &ctx, NID_key_usage, "critical,keyCertSign");
    X509_add_ext(x509, ex, -1);
    X509_EXTENSION_free(ex);

    // 签名证书
    if (X509_sign(x509, pKey, EVP_sha512()) == 0)
    {
        std::cerr << "ERROR: Failed to sign certificate x509." << std::endl;
        X509_free(x509);
        return nullptr;
    }
    return x509;
}

3. 导入PFX到Windows存储并指定CSP

修改导入逻辑,强制使用目标CSP并确保私钥可导出:

void SaveCertKeyPairToPFXWinStore(X509* pCert, EVP_PKEY* pPrivateKey, const std::string& password)
{
    PCCERT_CONTEXT pCertContext = nullptr;
    HCERTSTORE hMemoryStore = nullptr;
    HCERTSTORE hImportedStore = nullptr;
    PKCS12* p12 = nullptr;

    // 创建PKCS12结构,指定默认迭代参数确保兼容性
    p12 = PKCS12_create(password.c_str(), "MyCertificate", pPrivateKey, pCert, NULL, 
                        PKCS12_DEFAULT_ITER, 0, 0, 0, 0);
    if (!p12)
    {
        std::cerr << "Error creating PKCS12 structure: " << std::endl;
        return;
    }

    // 打开本地机器MY存储(读写权限)
    hMemoryStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, 
                                CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READWRITE_FLAG, 
                                L"MY");
    if (hMemoryStore == NULL)
    {
        DWORD errCode = GetLastError();
        std::cerr << "Error opening store: " << errCode << std::endl;
        PKCS12_free(p12);
        return;
    }

    // 转换PKCS12到二进制Blob
    CRYPT_DATA_BLOB pfxBlob;
    pfxBlob.pbData = NULL;
    pfxBlob.cbData = 0;
    int pfxSize = i2d_PKCS12(p12, &pfxBlob.pbData);
    if (pfxSize < 0)
    {
        std::cerr << "Cannot allocate memory for PKCS12 blob: " << std::endl;
        CertCloseStore(hMemoryStore, 0);
        PKCS12_free(p12);
        return;
    }

    const wchar_t* wzPassword = convertToWideChar(password.c_str());
    // 导入PFX,指定导出权限与机器密钥集
    hImportedStore = PFXImportCertStore(&pfxBlob, wzPassword, 
                                       CRYPT_EXPORTABLE | CRYPT_MACHINE_KEYSET);
    if (nullptr == hImportedStore)
    {
        DWORD errCode = GetLastError();
        std::cerr << "Error importing PFX to store: " << errCode << std::endl;
        OPENSSL_free(pfxBlob.pbData);
        CertCloseStore(hMemoryStore, 0);
        PKCS12_free(p12);
        return;
    }

    pCertContext = CertEnumCertificatesInStore(hImportedStore, NULL);
    if (pCertContext == NULL)
    {
        DWORD errCode = GetLastError();
        std::cerr << "Error enumerating certificates in store: " << errCode << std::endl;
        CertCloseStore(hImportedStore, 0);
        CertCloseStore(hMemoryStore, 0);
        OPENSSL_free(pfxBlob.pbData);
        PKCS12_free(p12);
        return;
    }

    // 强制设置私钥使用目标CSP
    CRYPT_KEY_PROV_INFO keyProvInfo = {0};
    keyProvInfo.pwszContainerName = L"MyCertificateContainer";
    keyProvInfo.pwszProvName = L"Microsoft Enhanced Cryptographic Provider v1.0";
    keyProvInfo.dwProvType = PROV_RSA_FULL;
    keyProvInfo.dwKeySpec = AT_KEYEXCHANGE;
    keyProvInfo.dwFlags = CRYPT_EXPORTABLE;

    if (!CertSetCertificateContextProperty(pCertContext, 
                                          CERT_KEY_PROV_INFO_PROP_ID, 
                                          0, 
                                          &keyProvInfo))
    {
        DWORD errCode = GetLastError();
        std::cerr << "Error setting key provider info: " << errCode << std::endl;
        CertFreeCertificateContext(pCertContext);
        CertCloseStore(hImportedStore, 0);
        CertCloseStore(hMemoryStore, 0);
        OPENSSL_free(pfxBlob.pbData);
        PKCS12_free(p12);
        return;
    }

    // 将证书与私钥添加到系统存储
    if (!CertAddCertificateContextToStore(hMemoryStore, pCertContext, 
                                          CERT_STORE_ADD_REPLACE_EXISTING, NULL))
    {
        DWORD errCode = GetLastError();
        std::cerr << "Error adding certificate context to store: " << errCode << std::endl;
        CertFreeCertificateContext(pCertContext);
        CertCloseStore(hImportedStore, 0);
        CertCloseStore(hMemoryStore, 0);
        OPENSSL_free(pfxBlob.pbData);
        PKCS12_free(p12);
        return;
    }

    // 释放资源
    CertFreeCertificateContext(pCertContext);
    CertCloseStore(hImportedStore, 0);
    CertCloseStore(hMemoryStore, 0);
    OPENSSL_free(pfxBlob.pbData);
    PKCS12_free(p12);
}

关键说明

  • PKCS#1格式兼容:通过设置RSA_PKCS1_PADDING,确保生成的私钥符合传统CSP的格式要求。
  • CSP强制指定:使用CertSetCertificateContextProperty明确设置Microsoft Enhanced Cryptographic Provider v1.0,避免系统自动选择CNG提供者。
  • 私钥可导出:导入时添加CRYPT_EXPORTABLE标志,赋予私钥导出权限。

验证方法

导入完成后,执行以下命令检查CSP信息:

certutil -v -store MY <证书指纹>

查看输出中的Provider Name字段,确认显示为Microsoft Enhanced Cryptographic Provider v1.0,此时右键证书选择“管理私钥”即可正常访问。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 05:25:54