使用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++代码手动验证无填充签名
如果无法修改签名方式,可通过手动解密签名并对比哈希值的方式验证:
- 使用公钥解密签名数据,得到原始哈希值(注意字节序调整)
- 计算待验证数据的MD5哈希值
- 对比两者是否一致
修改后的验证函数示例:
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
相关产品推荐
相关产品推荐

