使用PKCS#11模拟令牌,基于WinAPI读取证书私钥(含PFX)的方法咨询
解决方案:通过WinAPI获取证书对应的私钥句柄
你已经走在了正确的路上——通过CertGetCertificateContextProperty获取CRYPT_KEY_PROV_INFO是定位私钥的核心第一步,这个结构体里包含了密钥容器名、加密提供者名称、密钥用途等所有关键信息。接下来我们分两种主流场景(传统CSP和现代CNG)完成私钥的获取:
1. 先梳理你现有代码的核心成果
你的代码已经成功完成了以下关键步骤:
- 打开当前用户的
MY证书存储 - 找到指定主题的证书上下文
- 获取到证书对应的
CRYPT_KEY_PROV_INFO结构体(其中pwszProvName是加密提供者名称,pwszContainerName是密钥容器名,dwKeySpec标记密钥用于签名或密钥交换)
接下来只需基于这个结构体的信息,调用对应API获取私钥句柄即可。
2. 场景1:使用传统CryptoAPI(CSP)获取私钥句柄
如果你的PKCS#11模拟令牌注册为传统加密服务提供者(CSP),可以用CryptAcquireContext获取CSP上下文,再通过CryptGetUserKey拿到私钥句柄:
// 接你现有代码,在获取到pKeyInfo后添加: HCRYPTPROV hCryptProv = NULL; DWORD dwFlags = CRYPT_SILENT; // 静默模式,避免弹框 // 获取CSP上下文,使用pKeyInfo里的提供者类型和名称 if (CryptAcquireContextW( &hCryptProv, pKeyInfo->pwszContainerName, pKeyInfo->pwszProvName, pKeyInfo->dwProvType, dwFlags )) { printf("Successfully acquired CSP context.\n"); HCRYPTKEY hPrivateKey = NULL; if (CryptGetUserKey(hCryptProv, pKeyInfo->dwKeySpec, &hPrivateKey)) { printf("Successfully got private key handle.\n"); // 这里可执行私钥操作,比如签名、解密 // ... CryptDestroyKey(hPrivateKey); // 释放私钥句柄 } else { printf("CryptGetUserKey failed: %lu\n", GetLastError()); } CryptReleaseContext(hCryptProv, 0); // 释放CSP上下文 } else { printf("CryptAcquireContext failed: %lu\n", GetLastError()); }
3. 场景2:使用CNG(Cryptography Next Generation)API获取私钥句柄
现在大多数PKCS#11模拟令牌会注册为CNG的密钥存储提供者(KSP),这种场景下优先使用CNG系列API:
// 接你现有代码,在获取到pKeyInfo后添加: NCRYPT_PROV_HANDLE hNcryptProv = NULL; NTSTATUS status = NCryptOpenStorageProvider( &hNcryptProv, pKeyInfo->pwszProvName, 0 // 默认标志位 ); if (status == ERROR_SUCCESS) { printf("Successfully opened CNG KSP.\n"); NCRYPT_KEY_HANDLE hPrivateKey = NULL; status = NCryptOpenKey( hNcryptProv, &hPrivateKey, pKeyInfo->pwszContainerName, pKeyInfo->dwKeySpec, NCRYPT_SILENT_FLAG // 静默模式,需PIN验证可移除 ); if (status == ERROR_SUCCESS) { printf("Successfully got private key handle.\n"); // 这里可执行私钥操作,比如用NCryptSignHash签名 // ... NCryptFreeObject(hPrivateKey); // 释放私钥句柄 } else { printf("NCryptOpenKey failed: 0x%08X\n", status); } NCryptFreeObject(hNcryptProv); // 释放KSP句柄 } else { printf("NCryptOpenStorageProvider failed: 0x%08X\n", status); }
关键注意事项
- 错误处理:示例简化了错误处理,实际开发中要严格检查每个API返回值,通过
GetLastError()(CryptoAPI)或status(CNG)排查问题。 - 资源释放:所有获取的句柄必须在使用后释放,避免内存泄漏。
- 令牌适配:如果不确定是CSP还是CNG,可优先尝试CNG API(现代PKCS#11驱动大多支持),失败后再 fallback 到传统CryptoAPI。
- 用户交互:若令牌需要PIN验证,可移除
CRYPT_SILENT或NCRYPT_SILENT_FLAG,让系统弹出验证窗口。
完整整合示例代码
把你的代码和上述步骤整合后,完整函数如下:
#include <windows.h> #include <wincrypt.h> #include <ncrypt.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "ncrypt.lib") #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) static void ImportPrivateKey(const char* fileName, const char* password) { HCERTSTORE hSystemStore; // 打开证书存储 if((hSystemStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"))) { printf("Opened the MY system store. \n"); } else { printf( "Could not open the MY system store. Error: %lu\n", GetLastError()); return; } PCCERT_CONTEXT pDesiredCert = NULL; LPWSTR lpszCertSubject = L"7172gka"; // 目标证书主题 // 搜索证书 if((pDesiredCert = CertFindCertificateInStore( hSystemStore, MY_ENCODING_TYPE, 0, CERT_FIND_SUBJECT_STR_W, lpszCertSubject , NULL))) { printf("The desired certificate was found. \n"); } else { printf("Could not find the desired certificate. Error: %lu\n", GetLastError()); CertCloseStore(hSystemStore, 0); return; } DWORD dwSize = 0; CRYPT_KEY_PROV_INFO* pKeyInfo = NULL; // 第一次调用获取内存大小 if(!CertGetCertificateContextProperty( pDesiredCert, CERT_KEY_PROV_INFO_PROP_ID, NULL, &dwSize)) { printf("Error getting key property size. Error: %lu\n", GetLastError()); CertFreeCertificateContext(pDesiredCert); CertCloseStore(hSystemStore, 0); return; } pKeyInfo = (CRYPT_KEY_PROV_INFO*)malloc(dwSize); if(!pKeyInfo) { printf("Error allocating memory for pKeyInfo.\n"); CertFreeCertificateContext(pDesiredCert); CertCloseStore(hSystemStore, 0); return; } // 第二次调用获取实际数据 if(!CertGetCertificateContextProperty( pDesiredCert, CERT_KEY_PROV_INFO_PROP_ID, pKeyInfo, &dwSize)) { printf("The second call to CertGetCertificateContextProperty failed. Error: %lu\n", GetLastError()); free(pKeyInfo); CertFreeCertificateContext(pDesiredCert); CertCloseStore(hSystemStore, 0); return; } // 打印提供者信息 wprintf(L"Provider Name: %s\n", pKeyInfo->pwszProvName); if(pKeyInfo->dwKeySpec == AT_SIGNATURE) { printf("Key is for signature.\n"); } else if(pKeyInfo->dwKeySpec == AT_KEYEXCHANGE) { printf("Key is for key exchange.\n"); } // 优先尝试CNG API NCRYPT_PROV_HANDLE hNcryptProv = NULL; NTSTATUS status = NCryptOpenStorageProvider( &hNcryptProv, pKeyInfo->pwszProvName, 0 ); if (status == ERROR_SUCCESS) { printf("Successfully opened CNG KSP.\n"); NCRYPT_KEY_HANDLE hPrivateKey = NULL; status = NCryptOpenKey( hNcryptProv, &hPrivateKey, pKeyInfo->pwszContainerName, pKeyInfo->dwKeySpec, NCRYPT_SILENT_FLAG ); if (status == ERROR_SUCCESS) { printf("Successfully acquired private key via CNG.\n"); NCryptFreeObject(hPrivateKey); } else { printf("NCryptOpenKey failed: 0x%08X\n", status); // CNG失败,尝试传统CryptoAPI HCRYPTPROV hCryptProv = NULL; if (CryptAcquireContextW( &hCryptProv, pKeyInfo->pwszContainerName, pKeyInfo->pwszProvName, pKeyInfo->dwProvType, CRYPT_SILENT )) { printf("Successfully acquired CSP context.\n"); HCRYPTKEY hKey = NULL; if (CryptGetUserKey(hCryptProv, pKeyInfo->dwKeySpec, &hKey)) { printf("Successfully acquired private key via CryptoAPI.\n"); CryptDestroyKey(hKey); } else { printf("CryptGetUserKey failed: %lu\n", GetLastError()); } CryptReleaseContext(hCryptProv, 0); } else { printf("CryptAcquireContext failed: %lu\n", GetLastError()); } } NCryptFreeObject(hNcryptProv); } else { printf("NCryptOpenStorageProvider failed: 0x%08X\n", status); // 直接尝试传统CryptoAPI HCRYPTPROV hCryptProv = NULL; if (CryptAcquireContextW( &hCryptProv, pKeyInfo->pwszContainerName, pKeyInfo->pwszProvName, pKeyInfo->dwProvType, CRYPT_SILENT )) { printf("Successfully acquired CSP context.\n"); HCRYPTKEY hKey = NULL; if (CryptGetUserKey(hCryptProv, pKeyInfo->dwKeySpec, &hKey)) { printf("Successfully acquired private key via CryptoAPI.\n"); CryptDestroyKey(hKey); } else { printf("CryptGetUserKey failed: %lu\n", GetLastError()); } CryptReleaseContext(hCryptProv, 0); } else { printf("CryptAcquireContext failed: %lu\n", GetLastError()); } } // 释放所有资源 free(pKeyInfo); CertFreeCertificateContext(pDesiredCert); CertCloseStore(hSystemStore, 0); }
内容的提问来源于stack exchange,提问作者Alex Breshniv
相关产品推荐
相关产品推荐

