如何在OpenSSL中切换加密提供程序并生成可导出的Windows证书密钥
问题解决与代码修改方案
问题核心原因
- OpenSSL默认生成PKCS#8格式的RSA私钥,Windows导入时会自动选用CNG架构的
Microsoft Software Key Storage Provider,而非传统CSP的Microsoft Enhanced Cryptographic Provider v1.0。 - 原导入流程未显式关联私钥与证书,导致系统无法识别私钥。
修改后的完整代码
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
相关产品推荐
相关产品推荐

