OpenSSL 3.0下ECDSA签名验证:非废弃API导入公钥遇阻
OpenSSL 3.0 ECDSA签名验证:正确使用新API导入EC公钥
问题分析
你的OSSL_DECODER_from_data调用失败主要有几个核心原因:
- 选择标志错误:使用
OSSL_KEYMGMT_SELECT_KEYPAIR | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS是针对密钥对或域参数的,公钥仅需OSSL_KEYMGMT_SELECT_PUBLIC_KEY标志。 - 字符串格式问题:PEM公钥字符串拼接存在语法错误(C++跨多行字符串需确保换行、拼接正确,避免额外空格或无效字符)。
- 错误信息缺失:未捕获OpenSSL错误栈,无法定位具体失败原因。
正确实现代码
以下是基于OpenSSL 3.0新API(无废弃函数)的完整ECDSA签名验证实现,支持从内存PEM字符串或文件读取公钥:
从内存PEM字符串导入公钥并验证签名
#include <openssl/evp.h> #include <openssl/decoder.h> #include <openssl/err.h> #include <iostream> #include <string> bool verifyECDSASignature(const unsigned char* sig, size_t sigLen, const char* msg, size_t msgLen, const std::string& pemPublicKey) { EVP_PKEY* pkey = nullptr; EVP_MD_CTX* mdctx = nullptr; bool result = false; // 初始化解码器上下文 OSSL_DECODER_CTX* dctx = OSSL_DECODER_CTX_new_for_pkey( &pkey, "PEM", nullptr, "EC", OSSL_KEYMGMT_SELECT_PUBLIC_KEY, nullptr, nullptr ); if (dctx == nullptr) { ERR_print_errors_fp(stderr); goto cleanup; } // 从内存数据解码公钥 const unsigned char* data = reinterpret_cast<const unsigned char*>(pemPublicKey.c_str()); size_t datalen = pemPublicKey.size(); if (!OSSL_DECODER_from_data(dctx, &data, &datalen)) { ERR_print_errors_fp(stderr); goto cleanup; } // 初始化签名验证上下文 mdctx = EVP_MD_CTX_new(); if (mdctx == nullptr) { ERR_print_errors_fp(stderr); goto cleanup; } if (EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) != 1) { ERR_print_errors_fp(stderr); goto cleanup; } // 传入待验证数据 if (EVP_DigestVerifyUpdate(mdctx, msg, msgLen) != 1) { ERR_print_errors_fp(stderr); goto cleanup; } // 验证签名 if (EVP_DigestVerifyFinal(mdctx, sig, sigLen) == 1) { result = true; } else { ERR_print_errors_fp(stderr); } cleanup: // 释放所有OpenSSL资源 OSSL_DECODER_CTX_free(dctx); EVP_PKEY_free(pkey); EVP_MD_CTX_free(mdctx); return result; } // 使用示例 int main() { // 格式正确的PEM公钥字符串(确保换行、首尾标记完整) std::string pubKey = "-----BEGIN PUBLIC KEY-----\n" "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy687PNFBHUW3KIYrgrdGtCY5bdDGvnbMj1v/\n" "APR71dBv0mD3UXNULjAKSWVc4ahfIddpfX/i2N9ppMxVljk8BA==\n" "-----END PUBLIC KEY-----"; // 待验证消息与签名(实际场景替换为真实数据) const char* msg = "test message"; size_t msgLen = strlen(msg); // 注意:签名是二进制数据,此处为示例占位 unsigned char sig[] = {/* 你的ECDSA签名字节流 */}; size_t sigLen = sizeof(sig); if (verifyECDSASignature(sig, sigLen, msg, msgLen, pubKey)) { std::cout << "Signature verified successfully" << std::endl; } else { std::cout << "Signature verification failed" << std::endl; } return 0; }
从PEM文件读取公钥的实现
若需从文件加载公钥,替换公钥导入逻辑为:
// 从文件读取公钥 BIO* bio = BIO_new_file("public_key.pem", "r"); if (bio == nullptr) { ERR_print_errors_fp(stderr); goto cleanup; } OSSL_DECODER_CTX* dctx = OSSL_DECODER_CTX_new_for_pkey( &pkey, "PEM", nullptr, "EC", OSSL_KEYMGMT_SELECT_PUBLIC_KEY, nullptr, nullptr ); if (dctx == nullptr) { ERR_print_errors_fp(stderr); BIO_free(bio); goto cleanup; } if (!OSSL_DECODER_from_bio(dctx, bio)) { ERR_print_errors_fp(stderr); BIO_free(bio); goto cleanup; } BIO_free(bio);
关键注意事项
- 签名长度:
EVP_DigestVerifyFinal需要签名的二进制字节长度,而非字符串长度。原代码中strlen((const char*) sig)是错误的,因为二进制签名可能包含\0字符,导致长度计算失效。 - 错误调试:必须调用
ERR_print_errors_fp(stderr)打印OpenSSL错误栈,这是定位问题的核心手段。 - 资源释放:所有OpenSSL对象(
EVP_PKEY、EVP_MD_CTX、OSSL_DECODER_CTX等)必须在使用后释放,避免内存泄漏。 - PEM格式:确保公钥的PEM格式完全正确,包含完整的首尾标记、正确的换行,无多余空格或特殊字符。
内容的提问来源于stack exchange,提问作者NSmn
相关产品推荐
相关产品推荐

