You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Windows CAPI中CryptDecrypt调用报错‘密钥不存在’问题排查求助

解决WinCrypt CryptDecrypt报“密钥不存在”的问题

我帮你梳理下代码里的几个关键问题,这些正是导致你遇到错误的核心原因:

1. 最致命的错误:用公钥做解密操作

RSA加密体系的核心逻辑是公钥加密,私钥解密;如果是私钥“加密”(实际是签名操作),那对应的是用公钥验证签名,而不是解密。你的代码里导入的是公钥,却调用CryptDecrypt,这完全不符合RSA的规则,WinCrypt必然会返回“密钥不存在”的错误——因为公钥根本没有解密的权限。

如果你的真实需求是:

  • 解密用OpenSSL公钥加密的数据:那你需要导入对应的私钥到WinCrypt,而不是公钥;
  • 验证用OpenSSL私钥签名的数据:那你应该调用CryptVerifySignature,而不是CryptDecrypt

2. 缺失API错误检查,掩盖了真实问题

你的代码里几乎没有检查WinCrypt API的返回值,比如CryptAcquireContextCryptImportPublicKeyInfo这些调用如果失败,会导致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

火山引擎 最新活动