Windows CAPI中CryptDecrypt调用报错‘密钥不存在’问题排查求助
解决WinCrypt CryptDecrypt报“密钥不存在”的问题
我帮你梳理下代码里的几个关键问题,这些正是导致你遇到错误的核心原因:
1. 最致命的错误:用公钥做解密操作
RSA加密体系的核心逻辑是公钥加密,私钥解密;如果是私钥“加密”(实际是签名操作),那对应的是用公钥验证签名,而不是解密。你的代码里导入的是公钥,却调用CryptDecrypt,这完全不符合RSA的规则,WinCrypt必然会返回“密钥不存在”的错误——因为公钥根本没有解密的权限。
如果你的真实需求是:
- 解密用OpenSSL公钥加密的数据:那你需要导入对应的私钥到WinCrypt,而不是公钥;
- 验证用OpenSSL私钥签名的数据:那你应该调用
CryptVerifySignature,而不是CryptDecrypt。
2. 缺失API错误检查,掩盖了真实问题
你的代码里几乎没有检查WinCrypt API的返回值,比如CryptAcquireContext、CryptImportPublicKeyInfo这些调用如果失败,会导致hKey为空,后续调用CryptDecrypt自然会报错。补全错误检查能帮你快速定位哪一步出了问题,比如:
// 在init_crypto里添加CryptAcquireContext的错误检查 if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { printf("CryptAcquireContext failed: %d\n", GetLastError()); return -1; } // 同样检查CryptImportPublicKeyInfo if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey)) { printf("CryptImportPublicKeyInfo failed: %d\n", GetLastError()); CryptReleaseContext(hProv, 0); return -1; }
3. 字节序转换的前提不对
你提到了OpenSSL和WinCrypto的字节序差异,这个认知是对的——OpenSSL RSA输出是大端字节序,WinCrypto是小端。但字节序转换只有在用私钥解密OpenSSL公钥加密的数据时才需要做,而且要确保转换的是和密钥长度匹配的数据段(比如1024位密钥对应128字节),不是整个文件的长度(如果是分段加密的话)。
修正后的私钥导入示例
假设你需要解密用OpenSSL公钥加密的数据,这里给你一个导入PEM格式私钥的代码片段,替换原来的init_crypto函数:
char default_priv_key[] = "-----BEGIN RSA PRIVATE KEY-----" // 替换成你自己的私钥内容 "MIIEpAIBAAKCAQEAv..." "-----END RSA PRIVATE KEY-----"; HCRYPTPROV hProv = NULL; HCRYPTKEY hKey = NULL; DWORD dwKeySize = 0; int init_crypto_with_private_key() { LPBYTE pbBuffer; DWORD dw_priv_key_len = 0; RSA_PRIVATE_KEY_INFO* privateKeyInfo; DWORD dwKeyBlob = 0; // 把PEM格式私钥转成二进制 if (!CryptStringToBinaryA(default_priv_key, 0, CRYPT_STRING_ANY, NULL, &dw_priv_key_len, NULL, NULL)) { printf("CryptStringToBinary failed: %d\n", GetLastError()); return -1; } pbBuffer = (LPBYTE)GlobalAlloc(GPTR, dw_priv_key_len); if (!pbBuffer) { printf("内存分配失败\n"); return -1; } if (!CryptStringToBinaryA(default_priv_key, 0, CRYPT_STRING_ANY, pbBuffer, &dw_priv_key_len, NULL, NULL)) { printf("CryptStringToBinary failed: %d\n", GetLastError()); GlobalFree(pbBuffer); return -1; } // 解码私钥信息 if (!CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_PRIVATE_KEY_INFO, pbBuffer, dw_priv_key_len, 0, NULL, NULL, &dwKeyBlob)) { printf("CryptDecodeObjectEx failed: %d\n", GetLastError()); GlobalFree(pbBuffer); return -1; } privateKeyInfo = (RSA_PRIVATE_KEY_INFO*)GlobalAlloc(GPTR, dwKeyBlob); if (!privateKeyInfo) { printf("内存分配失败\n"); GlobalFree(pbBuffer); return -1; } if (!CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_PRIVATE_KEY_INFO, pbBuffer, dw_priv_key_len, 0, NULL, privateKeyInfo, &dwKeyBlob)) { printf("CryptDecodeObjectEx failed: %d\n", GetLastError()); GlobalFree(privateKeyInfo); GlobalFree(pbBuffer); return -1; } // 获取加密上下文 if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { printf("CryptAcquireContext failed: %d\n", GetLastError()); GlobalFree(privateKeyInfo); GlobalFree(pbBuffer); return -1; } // 导入私钥 if (!CryptImportPrivateKeyInfo(hProv, X509_ASN_ENCODING, privateKeyInfo, &hKey)) { printf("CryptImportPrivateKeyInfo failed: %d\n", GetLastError()); CryptReleaseContext(hProv, 0); GlobalFree(privateKeyInfo); GlobalFree(pbBuffer); return -1; } // 获取密钥长度 DWORD dwParamSize = sizeof(DWORD); if (!CryptGetKeyParam(hKey, KP_KEYLEN, (BYTE*)&dwKeySize, &dwParamSize, 0)) { printf("CryptGetKeyParam failed: %d\n", GetLastError()); CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); GlobalFree(privateKeyInfo); GlobalFree(pbBuffer); return -1; } dwKeySize /= 8; // 清理临时内存 GlobalFree(privateKeyInfo); GlobalFree(pbBuffer); return 0; }
最后补充
- 在main函数里调用
init_crypto_with_private_key替代原来的init_crypto; - 确保字节序转换是在导入私钥之后进行;
- 记得在程序结束时清理资源:
CryptDestroyKey(hKey)、CryptReleaseContext(hProv, 0),避免内存泄漏。
内容的提问来源于stack exchange,提问作者keshavnair101




