最常见原因是签名数据和原始数据的哈希方式不匹配,如用SHA256签名却用EVP_sha1()验签;需确保EVP_verify_init等函数指定的摘要算法严格一致,并传入原始未哈希数据、DER编码的二进制签名及正确加载的PEM公钥。

用OpenSSL验证RSA签名时,为什么总是提示“bad signature”?
最常见原因是签名数据和原始数据的哈希方式不匹配。比如你用 SHA256 对原文做了哈希,但验签时却用 EVP_verify_init 初始化了 EVP_sha1(),结果必然失败。OpenSSL不会自动推断哈希算法,必须严格一致。
实操要点:
- 签名前用什么哈希函数(
SHA256、SHA512等),验签时EVP_PKEY_CTX_set_signature_md或EVP_verify_init就得传对应的EVP_sha256()等 - 确保验签时传入的是原始未哈希数据(不是你自己再哈希一遍)——
EVP_verify_update接收的就是明文,OpenSSL内部会按指定MD自动哈希 - 签名是DER编码的二进制块,不能当文本读取;若从Base64字符串加载,务必用
EVP_DecodeBlock转成原始字节,而非简单去掉换行符
如何用C++加载PEM格式公钥并验证?
别直接用 fopen + fread 读公钥文件然后喂给 PEM_read_PUBKEY——如果文件末尾有BOM或多余空格,PEM_read_PUBKEY 会静默失败,返回 nullptr 却不报错。
稳妥做法:
立即学习“C++免费学习笔记(深入)”;
- 用
std::ifstream以二进制模式打开,读取全部内容到std::vector<uint8_t>,确认末尾无多余字节 - 用
BIO_new_mem_buf包装内存块,再调PEM_read_bio_PUBKEY,比直接文件操作容错性高 - 检查返回的
EVP_PKEY*是否为nullptr,且用EVP_PKEY_base_id确认类型是EVP_PKEY_RSA,避免误用EC公钥验RSA签名
验签失败但错误码是0,怎么定位问题?
EVP_verify_final 返回0只说明“无效”,不告诉你哪里错了。OpenSSL默认不输出详细原因,得主动查错误栈。
调试步骤:
- 在
EVP_verify_final返回0后,立刻调ERR_print_errors_fp(stderr),通常会打印类似error:0407008A:rsa routines:RSA_padding_check_PKCS1_type_1:invalid padding - 如果没输出,说明错误栈为空——大概率是前面某步(如
EVP_PKEY_CTX_new)失败了但你没检查返回值 - 注意:
ERR_print_errors_fp会清空错误栈,重复调用无输出;一次失败后需重新触发验签流程才能再捕获
是否必须用OpenSSL?有没有更轻量的替代方案?
如果只处理标准PKCS#1 v1.5签名且不需要X.509证书链验证,libtomcrypt 的 rsa_verify_hash 更简洁,头文件少、无全局错误栈、API直白。但它不支持PSS填充,也不处理ASN.1封装的签名——如果你的签名是 SEQUENCE { digest, oid } 结构(常见于Java或.NET生成的签名),还是得用OpenSSL解析。
选型关键点:
- 签名来源是否可控?若来自浏览器WebCrypto API,它默认用PSS,必须用OpenSSL的
EVP_PKEY_CTX_set_rsa_padding显式设为RSA_PKCS1_PSS_PADDING - 部署环境是否允许动态链接?OpenSSL 3.0+ 默认启用FIPS模式时,某些旧签名算法会被禁用,需提前确认合规要求
真正容易被忽略的是:验签前对输入数据长度不做校验——RSA密钥长度为2048位时,PKCS#1 v1.5签名固定为256字节,若传入的签名只有255字节,OpenSSL可能不报错但返回0。务必用 static_cast<int>(sig_len) 传入,别依赖自动类型转换。


















