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

使用Win32 API验证RSA签名时遭遇NTE_BAD_SIGNATURE错误求助

RSA签名验证报错NTE_BAD_SIGNATURE排查解决

问题背景

已尝试多种方法仍无法解决问题,现提供完整示例寻求帮助。待签名消息为Hello World,已通过工具完成基于MD5哈希的RSA签名与验证,但自行编写的C++调用Win32 CryptoAPI的签名验证代码执行时,出现NTE_BAD_SIGNATURE错误。代码采用Stack Overflow的PEM公钥导入方案,附完整代码及示例密钥。

示例信息

  • 公钥和私钥仅为此示例生成
  • 签名流程:使用指定RSA私钥对Hello World的MD5哈希进行无填充的RSA签名,结果转为十六进制
  • 验证流程:工具中可正常验证该签名,但CryptoAPI代码验证失败

示例密钥

私钥

-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDCLDny1MwbGVFrjtNXH+HFxZ/4C5Gz0q2b5exrJLHums5YDSFl
6c55QLYNZ4MOlVrahVsAOO0ENkqA5QdswTi+53nP7zJsxmLnpN5J8QEQJgTlkw+k
Ia8+c8j0FWJCRFcZ4vlxcqIymJc/tpBuHfVIXpQpGuPpUTCtyfRmBNuGbQIDAQAB
AoGABH78qoRF45y+92Qbvak105wDW7181rKapYD56/MyEYnRHFXVf6QdzU3zyTSr
4rMPov6ygDtRNadCK2DiPqDsvOiQU+y7ZSt+t0hdUv8nkQOzR167dM8dLAoVMVtm
KgUYO1MUnERvh+pmPKOmYozBAZbJJshOLbmqfGoJH1wUzCECQQDkC1SDsmLbT9uA
8MR6u4sn+kghli5oxP3dnj/1WRAATt3SrQNGUyJsLCCDc70EOPwx/nIY4aDog/3K
ixut8zVdAkEA2fnp0rCOXXikNV69z5Y2lj4OYpLtJiaJQK6NOYF997zso2oln/xX
nddxle0+a5KufdkP4DGP0QSnZKA+6qIf0UQJAf9hOQCrQuwzRDT9tlzTu9bGdoJ62
U+wkOotOZfjRPKr6NvLhxBo1URmH/Mn07JoZ4Nk6E/LiJ5hfvp4wHVwczQJAIDxR
VBNAOpqIzkvAjl6MnBN5VSKdZ7LzQVmPER4RXv3VkSU1gz9yP7/kUiQnqAGph3ft
ywdNLAXgU4hf9mSEwQJAbLV0c7GWXPCSk3k0gwOZCjHKaIxDIBjZudv5cvO9sPQx
sqE17eyl5+ufQq1xOQZL4HL+nUjlOcgT/pIdI9430A==
-----END RSA PRIVATE KEY-----

公钥

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCLDny1MwbGVFrjtNXH+HFxZ/4
C5Gz0q2b5exrJLHums5YDSFl6c55QLYNZ4MOlVrahVsAOO0ENkqA5QdswTi+53nP
7zJsxmLnpN5J8QEQJgTlkw+kIa8+c8j0FWJCRFcZ4vlxcqIymJc/tpBuHfVIXpQp
GuPpUTCtyfRmBNuGbQIDAQAB
-----END PUBLIC KEY-----

签名值(十六进制)

b59815360652ee71a599a59cd433d82c231ace377ceddd77c0d50a2bdafa42f23495d85a488cdbfe047363d9274a41c4380e8272c11351146dbc841a53d15dda45160a2338c41370d8671a16fa9de11743f657e3f33c0910f3601f434356e79e1d751374f8edffdff71c11fc9e3424fbe6f49b6eb7c78f7604eefaa59e0a6ca8

验证代码

#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include "windows.h"
#include "wincrypt.h"

using namespace std;

std::string GetLastErrorAsString() {
    DWORD errorMessageID = ::GetLastError();
    if (errorMessageID == 0) {
        return std::string();
    }

    LPSTR messageBuffer = nullptr;
    size_t size = FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL);

    std::string message(messageBuffer, size);
    LocalFree(messageBuffer);
    return message;
}

int test4(const vector<unsigned char> &signature, const vector<unsigned char> &data_to_verify) {
    string rsaKey = "-----BEGIN PUBLIC KEY-----\n"
                           "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCLDny1MwbGVFrjtNXH+HFxZ/4\n"
                           "C5Gz0q2b5exrJLHums5YDSFl6c55QLYNZ4MOlVrahVsAOO0ENkqA5QdswTi+53nP\n"
                           "7zJsxmLnpN5J8QEQJgTlkw+kIa8+c8j0FWJCRFcZ4vlxcqIymJc/tpBuHfVIXpQp\n"
                           "GuPpUTCtyfRmBNuGbQIDAQAB\n"
                           "-----END PUBLIC KEY-----";
    char derPubKey[2048];
    size_t derPubKeyLen = 2048;
    CERT_PUBLIC_KEY_INFO *publicKeyInfo;
    int publicKeyInfoLen;
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;

    if (!CryptStringToBinaryA(rsaKey.data(), 0, CRYPT_STRING_BASE64HEADER, reinterpret_cast<BYTE *>(derPubKey),
                              reinterpret_cast<DWORD *>(&derPubKeyLen), NULL, NULL)) {
        fprintf(stderr, "CryptStringToBinary failed. Err: %d\n", GetLastError());
    }

    if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, reinterpret_cast<const BYTE *>(derPubKey),
                             derPubKeyLen,
                             CRYPT_ENCODE_ALLOC_FLAG, NULL, &publicKeyInfo,
                             reinterpret_cast<DWORD *>(&publicKeyInfoLen))) {
        fprintf(stderr, "CryptDecodeObjectEx 1 failed. Err: %p\n", GetLastError());
        return -1;
    }

    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
        printf("CryptAcquireContext failed - err=0x%x.\n", GetLastError());
        return -1;
    }

    if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey)) {
        fprintf(stderr, "CryptImportPublicKeyInfo failed. error: %d\n", GetLastError());
        return -1;
    }
    LocalFree(publicKeyInfo);

    HCRYPTHASH hHash = 0;
    if (!CryptCreateHash(hProv, CALG_MD5, NULL, 0, &hHash)) {
        printf("CryptCreateHash failed with error 0x%.8lX\n", GetLastError());
        return -1;
    }

    if (!CryptHashData(hHash, data_to_verify.data(), data_to_verify.size(), 0)) {
        printf("CryptHashData failed with error 0x%.8lX\n", GetLastError());
        return -1;
    }

    if (!CryptVerifySignature(hHash, signature.data(), signature.size(), hKey, nullptr, 0)) {
        printf("Signature verification failed with error: ");
        cout << GetLastErrorAsString() << endl;
        return false;
    } else {
        printf("Signature verification succeeded.\n");
        return true;
    }

    return 0;
}

std::string to_hex(const std::vector<unsigned char> &v) {
    std::string result;
    char hex[] = "0123456789abcdef";
    for (unsigned char c : v) {
        result += hex[c >> 4];
        result += hex[c & 15];
    }
    return result;
}

int main() {
    vector<unsigned char> signature = {0xb5, 0x98, 0x15, 0x36, 0x06, 0x52, 0xee, 0x71, 0xa5, 0x99, 0xa5, 0x9c, 0xd4,
                                       0x33, 0xd8, 0x2c, 0x23, 0x1a, 0xce, 0x37, 0x7c, 0xed, 0xdd, 0x77, 0xc0, 0xd5,
                                       0x0a, 0x2b, 0xda, 0xfa, 0x42, 0xf2, 0x34, 0x95, 0xd8, 0x5a, 0x48, 0x8c, 0xdb,
                                       0xfe, 0x04, 0x73, 0x63, 0xd9, 0x27, 0x4a, 0x41, 0xc4, 0x38, 0x0e, 0x82, 0x72,
                                       0xc1, 0x13, 0x51, 0x14, 0x6d, 0xbc, 0x84, 0x1a, 0x53, 0xd1, 0x5d, 0xda, 0x45,
                                       0x16, 0x0a, 0x23, 0x38, 0xc4, 0x13, 0x70, 0xd8, 0x67, 0x1a, 0x16, 0xfa, 0x9d,
                                       0xe1, 0x17, 0x43, 0xf6, 0x57, 0xe3, 0xf3, 0x3c, 0x09, 0x10, 0xf3, 0x60, 0x1f,
                                       0x43, 0x43, 0x56, 0xe7, 0x9e, 0x1d, 0x75, 0x13, 0x74, 0xf8, 0xed, 0xff, 0xdf,
                                       0xf7, 0x1c, 0x11, 0xfc, 0x9e, 0x34, 0x24, 0xfb, 0xe6, 0xf4, 0x9b, 0x6e, 0xb7,
                                       0xc7, 0x8f, 0x76, 0x04, 0xee, 0xfa, 0xa5, 0x9e, 0x0a, 0x6c, 0xa8};
    string message = "Hello World";
    vector<unsigned char> data = vector<unsigned char>(message.begin(), message.end());

    cout << "Signature: " << to_hex(signature) << endl;
    cout << "Message: " << message << endl;

    test4(signature, data);
}

错误原因

报错NTE_BAD_SIGNATURE的核心原因是签名填充方式不匹配:

  • 工具生成的是无填充(Raw)的RSA签名:直接对Hello World的MD5哈希值进行RSA签名,未添加PKCS#1 v1.5标准的填充结构。
  • Win32 CryptoAPI的CryptVerifySignature函数默认使用PKCS#1 v1.5填充进行验证,会期望签名数据包含固定的ASN.1前缀(MD5对应的前缀为3020300c06082a864886f70d020505000410)+ 哈希值的结构,因此无法验证无填充的签名。

解决方案

方案1:修改签名方式(推荐)

在签名工具中选择PKCS#1 v1.5填充生成签名,这样无需修改C++代码,CryptVerifySignature即可正常验证。

方案2:修改C++代码手动验证无填充签名

如果无法修改签名方式,可通过手动解密签名并对比哈希值的方式验证:

  1. 使用公钥解密签名数据,得到原始哈希值(注意字节序调整)
  2. 计算待验证数据的MD5哈希值
  3. 对比两者是否一致

修改后的验证函数示例:

int test4(const vector<unsigned char> &signature, const vector<unsigned char> &data_to_verify) {
    // 公钥导入代码保持不变
    string rsaKey = "-----BEGIN PUBLIC KEY-----\n"
                           "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCLDny1MwbGVFrjtNXH+HFxZ/4\n"
                           "C5Gz0q2b5exrJLHums5YDSFl6c55QLYNZ4MOlVrahVsAOO0ENkqA5QdswTi+53nP\n"
                           "7zJsxmLnpN5J8QEQJgTlkw+kIa8+c8j0FWJCRFcZ4vlxcqIymJc/tpBuHfVIXpQp\n"
                           "GuPpUTCtyfRmBNuGbQIDAQAB\n"
                           "-----END PUBLIC KEY-----";
    char derPubKey[2048];
    size_t derPubKeyLen = 2048;
    CERT_PUBLIC_KEY_INFO *publicKeyInfo;
    int publicKeyInfoLen;
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;

    if (!CryptStringToBinaryA(rsaKey.data(), 0, CRYPT_STRING_BASE64HEADER, reinterpret_cast<BYTE *>(derPubKey),
                              reinterpret_cast<DWORD *>(&derPubKeyLen), NULL, NULL)) {
        fprintf(stderr, "CryptStringToBinary failed. Err: %d\n", GetLastError());
    }

    if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, reinterpret_cast<const BYTE *>(derPubKey),
                             derPubKeyLen,
                             CRYPT_ENCODE_ALLOC_FLAG, NULL, &publicKeyInfo,
                             reinterpret_cast<DWORD *>(&publicKeyInfoLen))) {
        fprintf(stderr, "CryptDecodeObjectEx 1 failed. Err: %p\n", GetLastError());
        return -1;
    }

    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
        printf("CryptAcquireContext failed - err=0x%x.\n", GetLastError());
        return -1;
    }

    if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey)) {
        fprintf(stderr, "CryptImportPublicKeyInfo failed. error: %d\n", GetLastError());
        return -1;
    }
    LocalFree
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 06:10:33