OpenSSL命令行签名验证通过但C++代码验证失败求排查
OpenSSL API实现PSS签名验证失败排查
需求目标
需要用C++代码实现以下OpenSSL命令的功能:
openssl dgst -sha256 -verify public_key.pem -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -signature signature.sig sw-description
现有代码
用户编写的C++代码如下:
#include <openssl/core_names.h> #include <openssl/evp.h> #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/types.h> #include <iostream> #include <fstream> #include <vector> #include <iomanip> bool verify_signature(const std::string& signature_file, const std::string& public_key_file, const std::string& data_file) { // Read the signature file std::ifstream sig_file(signature_file, std::ios::binary); if (!sig_file.is_open()) { std::cerr << "Failed to open signature file: " << signature_file << std::endl; return false; } std::vector<unsigned char> signature((std::istreambuf_iterator<char>(sig_file)), std::istreambuf_iterator<char>()); sig_file.close(); BIO* publicKeyBio = BIO_new_file(public_key_file.c_str(), "rb"); if (!publicKeyBio) { perror("Failed to open public key file"); return false; } EVP_PKEY* pkey = PEM_read_bio_PUBKEY(publicKeyBio, nullptr, nullptr, nullptr); if (!pkey) { ERR_print_errors_fp(stderr); std::cout<<"22"; return false; } // Read the data file std::ifstream data_file_stream(data_file, std::ios::binary); if (!data_file_stream.is_open()) { std::cerr << "Failed to open data file: " << data_file << std::endl; return false; } std::vector<unsigned char> data((std::istreambuf_iterator<char>(data_file_stream)), std::istreambuf_iterator<char>()); data_file_stream.close(); // Create a digest of the data using SHA-256 EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); if (!md_ctx) { std::cerr << "Failed to create message digest context" << std::endl; return false; } if (EVP_DigestInit_ex(md_ctx, EVP_sha256(), nullptr) != 1) { std::cerr << "Failed to initialize message digest context" << std::endl; EVP_MD_CTX_free(md_ctx); return false; } if (EVP_DigestUpdate(md_ctx, data.data(), data.size()) != 1) { std::cerr << "Failed to update message digest" << std::endl; EVP_MD_CTX_free(md_ctx); return false; } unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_len; if (EVP_DigestFinal_ex(md_ctx, digest, &digest_len) != 1) { std::cerr << "Failed to finalize message digest" << std::endl; EVP_MD_CTX_free(md_ctx); return false; } EVP_MD_CTX_free(md_ctx); // Verify the signature using the public key and PSS padding scheme EVP_MD_CTX* verify_ctx = EVP_MD_CTX_new(); if (!verify_ctx) { std::cerr << "Failed to create signature verification context" << std::endl; EVP_PKEY_free(pkey); return false; } if (EVP_DigestVerifyInit(verify_ctx, nullptr, EVP_sha256(), nullptr, pkey) != 1) { std::cerr << "Failed to initialize signature verification context" << std::endl; EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); return false; } // Verify the signature if(EVP_DigestVerifyFinal(verify_ctx, signature.data(), signature.size()) != 1) { ERR_print_errors_fp(stderr); std::cerr << "Signature verification failed" << std::endl; EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); return false; } EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); std::cout << "Signature verification succeeded" << std::endl; return true; } int main() { std::string signature_file = "signature_direct.sig"; std::string public_key_file = "public_key.pem"; std::string data_file = "sw.swu"; if (!verify_signature(signature_file, public_key_file, data_file)) { std::cerr << "Signature verification failed" << std::endl; return 1; } return 0; }
问题现象
使用上述OpenSSL命令验证签名时结果正常,但用相同文件通过该代码验证时失败。
问题原因及修复
代码存在两个关键问题:
- 错误预计算数据摘要:
EVP_DigestVerify系列函数会自动对输入数据计算哈希并验证签名,无需手动提前计算摘要。手动计算摘要后直接调用EVP_DigestVerifyFinal相当于对摘要的哈希进行验证,和命令行逻辑不符。 - 未配置PSS签名选项:命令行中指定了
rsa_padding_mode:pss和rsa_pss_saltlen:-1(盐长度等于哈希长度),但代码中未设置这些参数,导致默认使用PKCS#1 v1.5 padding,和签名时的PSS模式不匹配。
修复后的代码
#include <openssl/core_names.h> #include <openssl/evp.h> #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/types.h> #include <iostream> #include <fstream> #include <vector> #include <iomanip> bool verify_signature(const std::string& signature_file, const std::string& public_key_file, const std::string& data_file) { // Read the signature file std::ifstream sig_file(signature_file, std::ios::binary); if (!sig_file.is_open()) { std::cerr << "Failed to open signature file: " << signature_file << std::endl; return false; } std::vector<unsigned char> signature((std::istreambuf_iterator<char>(sig_file)), std::istreambuf_iterator<char>()); sig_file.close(); BIO* publicKeyBio = BIO_new_file(public_key_file.c_str(), "rb"); if (!publicKeyBio) { perror("Failed to open public key file"); return false; } EVP_PKEY* pkey = PEM_read_bio_PUBKEY(publicKeyBio, nullptr, nullptr, nullptr); BIO_free(publicKeyBio); // 补充释放漏处理的BIO资源 if (!pkey) { ERR_print_errors_fp(stderr); return false; } // Read the data file std::ifstream data_file_stream(data_file, std::ios::binary); if (!data_file_stream.is_open()) { std::cerr << "Failed to open data file: " << data_file << std::endl; EVP_PKEY_free(pkey); return false; } std::vector<unsigned char> data((std::istreambuf_iterator<char>(data_file_stream)), std::istreambuf_iterator<char>()); data_file_stream.close(); // 创建验证上下文并配置PSS选项 EVP_MD_CTX* verify_ctx = EVP_MD_CTX_new(); if (!verify_ctx) { std::cerr << "Failed to create signature verification context" << std::endl; EVP_PKEY_free(pkey); return false; } // 设置PSS填充模式和盐长度参数 OSSL_PARAM params[3] = {0}; params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_RSA_PADDING_MODE, "pss", 0); params[1] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_RSA_PSS_SALTLEN, const_cast<int*>(&(int){-1})); params[2] = OSSL_PARAM_construct_end(); // 初始化验证上下文 if (EVP_DigestVerifyInit(verify_ctx, nullptr, EVP_sha256(), nullptr, pkey) != 1) { std::cerr << "Failed to initialize signature verification context" << std::endl; EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); return false; } // 应用PSS参数配置 if (EVP_MD_CTX_set_params(verify_ctx, params) != 1) { std::cerr << "Failed to set PSS verification parameters" << std::endl; EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); return false; } // 传入原始数据进行哈希计算和验证 if (EVP_DigestVerifyUpdate(verify_ctx, data.data(), data.size()) != 1) { std::cerr << "Failed to update verification context with data" << std::endl; EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); return false; } // 完成签名验证 int verify_result = EVP_DigestVerifyFinal(verify_ctx, signature.data(), signature.size()); if (verify_result != 1) { if (verify_result == 0) { std::cerr << "Signature verification failed: invalid signature" << std::endl; } else { ERR_print_errors_fp(stderr); std::cerr << "Signature verification encountered an error" << std::endl; } EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); return false; } EVP_MD_CTX_free(verify_ctx); EVP_PKEY_free(pkey); std::cout << "Signature verification succeeded" << std::endl; return true; } int main() { std::string signature_file = "signature_direct.sig"; std::string public_key_file = "public_key.pem"; std::string data_file = "sw.swu"; if (!verify_signature(signature_file, public_key_file, data_file)) { std::cerr << "Signature verification failed" << std::endl; return 1; } return 0; }
关键修改点说明
- 删除了手动计算数据摘要的代码块,改用
EVP_DigestVerifyUpdate直接传入原始数据,由OpenSSL自动处理哈希计算。 - 添加了
OSSL_PARAM结构体配置PSS填充模式和盐长度参数,并通过EVP_MD_CTX_set_params应用到验证上下文。 - 补充了资源释放(比如BIO对象的释放),避免内存泄漏。
- 细化了验证失败的错误信息区分(无效签名和内部错误)。
内容的提问来源于stack exchange,提问作者TANYA Malik
相关产品推荐
相关产品推荐

