我有一个Python代码,它根据哈希字符串作为数据生成签名。它使用密码学库根据哈希值计算签名。它使用私钥文件 .pem,其中也包含私钥和公钥。它使用serialization.load_pem_private_key() API通过读取.pem文件来获取私钥。然后使用这个私钥,使用以下 python 加密方法生成签名。
privateKey.sign(hashData,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
utils.Prehashed(hashes.SHA256()))
如果这里输入的 hashData 相同,它总是生成一个新的签名。 然后,它通过从同一 .pem 文件获取公钥并调用验证 API 来验证生成的签名,如下所示
publicKey.verify(
generated_signature,
hashData,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=32
),
utils.Prehashed(hashes.SHA256())
)
在python中验证成功。我尝试使用版本 3.4.0 的 OpenSSL API 在 C++ 中模仿相同的逻辑。签名生成成功,但是当我尝试使用这个基于 python 的工具验证它时,验证因错误而失败
failed to verify with private key from key file 'key.pem'.
以下是我为模仿相同逻辑而编写的示例 C++ 代码
#define _CRT_SECURE_NO_WARNINGS
#include "openssl/pem.h"
#include "openssl/err.h"
#include "openssl/evp.h"
#include "openssl/rsa.h"
#include "openssl/sha.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <vector>
#include "ms/applink.c"
void handleOpenSSLErrors()
{
ERR_print_errors_fp(stderr);
abort();
}
// Load RSA private key from a PEM file
EVP_PKEY* loadPrivateKey(const std::string& filePath)
{
FILE* keyFile = nullptr;
errno_t r_code = fopen_s(&keyFile, filePath.c_str(), "rb");
if (!keyFile) {
std::cerr << "Unable to open private key file: " << filePath << std::endl;
return nullptr;
}
EVP_PKEY* pkey = PEM_read_PrivateKey(keyFile, nullptr, nullptr, nullptr);
fclose(keyFile);
if (!pkey)
{
std::cerr << "Unable to read private key from file: " << filePath << std::endl;
}
int keyType = EVP_PKEY_base_id(pkey);
switch (keyType) {
case EVP_PKEY_RSA:
std::cout << "Key Type: RSA" << std::endl;
break;
case EVP_PKEY_EC:
std::cout << "Key Type: EC (Elliptic Curve)" << std::endl;
break;
case EVP_PKEY_DSA:
std::cout << "Key Type: DSA" << std::endl;
break;
default:
std::cout << "Key Type: Unknown" << std::endl;
break;
}
return pkey;
}
std::vector<unsigned char> signData(EVP_PKEY* privateKey, const std::vector<unsigned char>& hashData) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if (!ctx) handleOpenSSLErrors();
if (EVP_DigestSignInit(ctx, nullptr, EVP_sha256(), nullptr, privateKey) <= 0) {
handleOpenSSLErrors();
}
EVP_PKEY_CTX_set_rsa_padding(EVP_MD_CTX_get_pkey_ctx(ctx), RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_mgf1_md(EVP_MD_CTX_get_pkey_ctx(ctx), EVP_sha256());
EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_MD_CTX_get_pkey_ctx(ctx), -2);
size_t sigLen = 0;
if (EVP_DigestSign(ctx, nullptr, &sigLen, hashData.data(), hashData.size()) <= 0) {
handleOpenSSLErrors();
}
std::vector<unsigned char> signature(sigLen);
if (EVP_DigestSign(ctx, signature.data(), &sigLen, hashData.data(), hashData.size()) <= 0) {
handleOpenSSLErrors();
}
EVP_MD_CTX_free(ctx);
return signature;
}
std::string vectorToHexString(const std::vector<unsigned char>& bytes)
{
std::ostringstream hexStream;
// Convert each byte to a two-character hexadecimal representation
for (unsigned char byte : bytes) {
hexStream << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
return hexStream.str();
}
std::vector<unsigned char> hexStringToBinary(const std::string& hex) {
if (hex.length() % 2 != 0) {
throw std::invalid_argument("Hex string length must be even");
}
std::vector<unsigned char> binary;
binary.reserve(hex.length() / 2);
for (size_t i = 0; i < hex.length(); i += 2) {
std::string byteString = hex.substr(i, 2); // Get two characters
unsigned char byte = static_cast<unsigned char>(std::stoi(byteString, nullptr, 16));
binary.push_back(byte);
}
return binary;
}
int main()
{
const std::string privateKeyPath = "private_key.pem";
// Example hash data
std::string data = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
std::vector<unsigned char> hashBinary = hexStringToBinary(data);
for (unsigned char byte : hashBinary)
{
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
// Verify size
if (hashBinary.size() != 32) {
std::cerr << "Invalid hash size! Expected 32 bytes (SHA-256)." << std::endl;
return 1;
}
// Load keys
EVP_PKEY* privateKey = loadPrivateKey(privateKeyPath);
if (!privateKey) return 1;
if (EVP_PKEY_base_id(privateKey) == EVP_PKEY_RSA)
{
std::cout << "RSA key loaded successfully." << std::endl;
}
else
{
std::cerr << "Invalid key type! RSA key required." << std::endl;
EVP_PKEY_free(privateKey);
return 1;
}
// Generate signature
std::vector<unsigned char> signature = signData(privateKey, hashBinary);
std::cout << "Signature generated successfully." << std::endl;
for (unsigned char byte : signature)
{
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
}
// Clean up
EVP_PKEY_free(privateKey);
return 0;
}
在 C++ 代码中,如果我加载公钥并尝试使用 OpenSSL API 进行验证,则验证会成功。但是当尝试使用基于Python的工具验证时,生成的签名验证失败!
我应该如何调试它以及如何解决这个问题? 我在 C++ 代码中遗漏了什么吗?
EVP_DigestSignXXX()
函数,因此 C 代码总体哈希两次。您可能认为情况并非如此,因为还有 EVP_SignXXX()
函数。但这些也隐式散列。顺便说一句,前者更年轻,应该在较新的应用程序中使用(请参阅此处)。EVP_PKEY_signXXX()
,它不隐式散列,例如这里(为了简单起见,没有异常处理):
...
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(privateKey, NULL);
EVP_PKEY_sign_init(ctx);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256());
//EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()); // for this use case optional
//EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, RSA_PSS_SALTLEN_DIGEST); // for this use case optional
size_t sigLen = 0;
EVP_PKEY_sign(ctx, NULL, &sigLen, hashData.data(), hashData.size());
std::vector<unsigned char> signature(sigLen);
EVP_PKEY_sign(ctx, signature.data(), &sigLen, hashData.data(), hashData.size());
EVP_PKEY_CTX_free(ctx);
...
为了与Python代码兼容,请注意以下事项:
EVP_PKEY_CTX_set_signature_md()
设置为 SHA256,默认为 SHA1。EVP_PKEY_CTX_set_rsa_mgf1_md()
完成的。RSA_PSS_SALTLEN_DIGEST
(这对应于 -1
)而不是 -2
。 -2
对应于RSA_PSS_SALTLEN_AUTO
。签名时,后者与使用最大盐长度的 RSA_PSS_SALTLEN_MAX
(值为 -3
)没有区别。由于 RSA_PSS_SALTLEN_DIGEST
是签名的默认值,因此在这种情况下不需要显式设置盐长度,请参阅此处。