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

OpenSSL 3下RSA签名验证EVP_PKEY实现错误修正请求

修正OpenSSL 3中基于EVP_PKEY的RSA签名验证代码

问题背景

原本使用OpenSSL的RSA_*系列API实现RSA公钥签名验证,因这些API在OpenSSL 3中被弃用,改用EVP_PKEY相关接口重构代码,但新实现始终返回验证失败(result=0)。旧代码及对应的Python验证逻辑均能正常通过测试,需修正新的EVP实现。

旧实现(基于已弃用的RSA_* API)

bool verify(unsigned char md[SHA256_DIGEST_LENGTH], std::string public_key, std::string signature) {
    // Parse the public key data into an RSA structure
    const auto* p = (const unsigned char*)public_key.c_str();
    RSA* rsa = d2i_RSA_PUBKEY(nullptr, &p, (long)public_key.size());
    if (!rsa) {
        return false;
    }

    int result = RSA_verify(NID_sha256, md, SHA256_DIGEST_LENGTH, (const unsigned char *)signature.c_str(), signature.size(), rsa);
    RSA_free(rsa);

    return result == 1;
}

新实现(基于EVP_PKEY,但验证失败)

bool verify(unsigned char md[SHA256_DIGEST_LENGTH], std::string public_key, std::string signature) {
    const auto* p = (const unsigned char*)public_key.c_str();
    EVP_PKEY* evp_key = d2i_PUBKEY(nullptr, &p, (long)public_key.size());
    if (!evp_key) {
        return false;
    }

    EVP_MD_CTX* ctx = EVP_MD_CTX_create();
    if (!ctx) {
        EVP_PKEY_free(evp_key);
        return false;
    }
    if (EVP_DigestVerifyInit(ctx, nullptr, EVP_sha256(), nullptr, evp_key) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(evp_key);
        return false;
    }

    if (EVP_DigestVerifyUpdate(ctx, md, SHA256_DIGEST_LENGTH) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(evp_key);
        return false;
    }

    int result = EVP_DigestVerifyFinal(ctx, (const unsigned char*)signature.c_str(), signature.size());

    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(evp_key);

    return result == 1;
}

测试用例

std::string rsa_signature("\x8b\x2b\x21\x37\xcb\x1e\x99\x3a\x5f\x61\x1c\x47\x4c\xd2\xf0\x92\xeb\xe1\x3e\x9c\xb9\x93\x00\x2d\xd2\x94\x57\xb8\x68\x03\xec\xf9\x01\x41\x1c\x1a\x71\x23\x0e\x11\xd0\x1c\x30\x92\xdc\xb6\xb3\xe4\xcd\x39\x70\x45\x20\x05\x52\xf5\xbb\x35\xef\xf8\x37\x73\x67\x6e\xeb\x4b\x65\x9f\x87\x37\xc5\x28\x8c\x53\xa5\xa4\x30\x74\xb3\x5c\xad\x17\x52\x7a\x00\xb0\x5e\x14\xfb\xb6\x82\x39\xb8\xad\xef\x62\xd2\xeb\x0d\xfc\xe5\x45\x96\x98\xa9\x44\xf2\x36\x44\x8e\xd3\x78\xf3\x9f\x52\x09\x53\xa4\x39\xaa\x7c\x77\xab\x61\x4f\xb4\x40\xa7\xa3\xbf\xd3\xa4\xba\x22\x3c\xc4\xc2\x77\xa2\x52\x83\x1a\x27\x9b\x02\xe5\x01\xd3\x60\x2d\x5f\x8d\xc2\x22\x42\xf9\x87\x13\x44\x36\x54\x03\x51\x29\x55\x5a\xbf\x38\x81\x0e\x0a\x39\xce\x8b\xd7\x60\x1b\x37\x33\x77\x2a\x91\x51\x09\x83\xca\x70\x46\x62\xe2\x4f\x3f\xd3\x1c\x7b\x30\xe9\xdd\x5e\x72\xf7\x3c\xb3\x21\xf6\xfa\x6e\x59\xce\xaa\xf1\x29\x7f\x1c\xa6\x21\x3d\xd2\x79\x54\xe6\x19\xe3\x1b\x17\x53\x1c\x69\xbb\x8a\x29\x10\x80\x11\x24\xda\x03\x7f\x6d\x32\xbd\x91\x0d\xad\x16\x70\xcb\xba\x3e\x1c\x11\xca\xc0\x7c\x44\x4d", 256);

std::string rsa_pubkey("\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\x8f\xfb\xbf\x5c\x37\x63\x94\x3c\xb0\xee\x01\xc4\xb5\xa6\x9a\xb1\x9f\x46\x74\x6f\x16\x38\xa0\x32\x27\x35\xdd\xf0\x71\x6b\x0e\xdc\xf6\x25\xcb\xb2\xed\xea\xfb\x32\xd5\xaf\x1e\x03\x43\x03\x46\xf0\xa7\x39\xdb\x23\x96\x1d\x65\xe5\x78\x51\xf0\x84\xb0\x0e\x12\xac\x0e\x5b\xdc\xc9\xd6\x4c\x7c\x00\xd5\xb8\x1b\x88\x33\x3e\x2f\xda\xeb\xaa\xf7\x1a\x75\xc2\xae\x3a\x54\xde\x37\x8f\x10\xd2\x28\xe6\x84\x79\x4d\x15\xb4\xf3\xbd\x3f\x56\xd3\x3c\x3f\x18\xab\xfc\x2e\x05\xc0\x1e\x08\x31\xb6\x61\xd0\xfd\x9f\x4f\x3f\x64\x0d\x17\x93\xbc\xad\x41\xc7\x48\xbe\x00\x27\xa8\x4d\x70\x42\x92\x05\x54\xa6\x6d\xb8\xde\x56\x6e\x20\x49\x70\xee\x10\x3e\x6b\xd2\x7c\x31\xbd\x1b\x6e\xa4\x3c\x46\x62\x9f\x08\x66\x93\xf9\x2a\x51\x31\xa8\xdb\xb5\x9d\xb9\x0f\x73\xe8\xa0\x09\x32\x01\xe9\x7b\x2a\x8a\x36\xa0\xcf\x17\xb0\x50\x70\x9d\xa2\xf9\xa4\x6f\x62\x4d\xb6\xc9\x31\xfc\xf3\x08\x12\xff\x93\xbd\x62\x31\xd8\x1c\xea\x1a\x9e\xf5\x81\x28\x7f\x75\x5e\xd2\x27\x7a\xc2\x96\xf5\x9d\xdb\x18\xfc\x76\xdc\x46\xf0\x57\xc0\x58\x34\xc8\x22\x2d\x2a\x65\x75\xa7\xd9\x08\x62\xcd\x02\x03\x01\x00\x01", 294);

std::string md = "\x29\x55\xb0\xa4\x40\x7f\xcd\x40\x8e\x7d\xbc\xd9\x46\x21\xa7\x4d\x7f\x61\xb1\xdc\x0a\xe5\x9f\x7c\x52\x29\x73\xb4\x33\x79\x3a\x4c";
std::string bad_md = "\x28\x55\xb0\xa4\x40\x7f\xcd\x40\x8e\x7d\xbc\xd9\x46\x21\xa7\x4d\x7f\x61\xb1\xdc\x0a\xe5\x9f\x7c\x52\x29\x73\xb4\x33\x79\x3a\x4c";

测试执行

EXPECT_TRUE(verify(md_byteArray, rsa_pubkey, rsa_signature));
EXPECT_FALSE(verify(bad_md_byteArray, rsa_pubkey, rsa_signature));

验证结果

旧实现可通过所有测试,但新实现始终返回false。同时用Python代码验证了旧实现和测试值的正确性:

pub_key = serialization.load_der_public_key(public_key)
pub_key.verify(signature, md, padding.PKCS1v15(), utils.Prehashed(hashes.SHA256()))
# 该代码行为与旧C++实现一致

问题原因及修正代码

问题核心在于:旧的RSA_verify直接接受预计算的哈希值,而默认的EVP_DigestVerify流程会对输入数据重新计算哈希。你的场景是已拥有预计算好的SHA256哈希值,需显式配置EVP接口进入预哈希验证模式。

修正后的代码如下:

bool verify(unsigned char md[SHA256_DIGEST_LENGTH], std::string public_key, std::string signature) {
    const auto* p = (const unsigned char*)public_key.c_str();
    EVP_PKEY* evp_key = d2i_PUBKEY(nullptr, &p, (long)public_key.size());
    if (!evp_key) {
        return false;
    }

    // OpenSSL 3推荐使用EVP_MD_CTX_new替代已弃用的EVP_MD_CTX_create
    EVP_MD_CTX* ctx = EVP_MD_CTX_new();
    if (!ctx) {
        EVP_PKEY_free(evp_key);
        return false;
    }

    EVP_PKEY_CTX* pctx = nullptr;
    // 初始化时获取pctx,用于配置验证参数
    if (EVP_DigestVerifyInit(ctx, &pctx, EVP_sha256(), nullptr, evp_key) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(evp_key);
        return false;
    }

    // 指定RSA填充模式为PKCS1v15,与旧实现默认行为一致
    if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(evp_key);
        return false;
    }

    // 关键配置:告知接口传入的md是已计算好的SHA256哈希值
    if (EVP_PKEY_CTX_set_rsa_signature_md(pctx, EVP_sha256()) != 1) {
        EVP_MD_CTX_free(ctx);
        EVP_PKEY_free(evp_key);
        return false;
    }

    // 预哈希模式下,直接在Final中传入预计算的哈希值,无需调用Update
    int result = EVP_DigestVerifyFinal(ctx, 
                                       (const unsigned char*)signature.c_str(), signature.size(),
                                       md, SHA256_DIGEST_LENGTH);

    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(evp_key);

    return result == 1;
}

修正要点说明

  1. 替换上下文创建函数:使用OpenSSL 3推荐的EVP_MD_CTX_new替代已弃用的EVP_MD_CTX_create。
  2. 显式配置填充模式:指定PKCS1v15填充,与旧RSA_verify的默认行为对齐。
  3. 启用预哈希模式:通过EVP_PKEY_CTX_set_rsa_signature_md告知接口,无需重新计算哈希,直接使用传入的md值。
  4. 调整验证流程:预哈希模式下无需调用EVP_DigestVerifyUpdate,直接在EVP_DigestVerifyFinal中传入预计算的哈希值即可。

内容的提问来源于stack exchange,提问作者Rachel91

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 18:26:59