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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:35:59