EVP_PKEY_sign失败的根本原因是密钥类型不匹配或上下文未正确初始化;必须用EVP_PKEY封装私钥,显式调用EVP_PKEY_CTX_new并设置填充模式(如RSA_PKCS1_PSS_PADDING)和摘要算法(如EVP_sha256),且不可跳过EVP_PKEY_sign_init。

openssl EVP接口签名时为什么总是报EVP_PKEY_sign失败?
根本原因通常是密钥类型不匹配或上下文未正确初始化。RSA签名必须用EVP_PKEY封装私钥,且EVP_PKEY_CTX需显式设置填充模式——OpenSSL 1.1.1+默认不设EVP_PKEY_CTX_set_rsa_padding会直接返回-1。
实操要点:
- 加载私钥后必须调用
EVP_PKEY_CTX_new并传入EVP_PKEY,不能复用加密用的ctx - 必须显式设置
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING)或RSA_PKCS1_OAEP_PADDING(PSS更安全) - 哈希摘要要用
EVP_sha256()等明确指定,不能依赖ctx默认行为 - 签名前调用
EVP_PKEY_sign_init,再调用EVP_PKEY_sign;若跳过init,错误码常为0x0406908C("invalid operation for context")
验证文件完整性时如何避免EVP_PKEY_verify返回0?
验证失败不等于“签名错”,更可能是数据预处理不一致。RSA验证本质是比对签名解密后的摘要和当前文件摘要,两者必须完全同源同算法。
关键检查点:
立即学习“C++免费学习笔记(深入)”;
- 签名时用
RSA_PKCS1_PSS_PADDING,验证时ctx也必须设相同padding,否则EVP_PKEY_verify直接返回0(无错误信息) - 文件读取必须用二进制模式(
std::ios::binary),Windows下文本模式会悄悄替换\r\n,导致摘要错 - 验证用的公钥必须与签名私钥配对,用
openssl rsa -pubout -in priv.key -out pub.key导出,别直接用PEM_read_bio_RSA_PUBKEY读原始RSA结构 - 摘要计算要分块(尤其大文件),用
EVP_DigestUpdate循环喂数据,别一次性read整个文件到内存
用std::ifstream读文件做签名,为什么小文件OK大文件失败?
不是内存问题,而是std::ifstream::read在遇到EOF或错误时不会清空failbit,后续gcount()返回0,导致摘要计算中断。OpenSSL本身不报错,但最终签名值无效。
稳妥做法:
- 每次
read后检查if (!file) break;,而非只看gcount() - 用
file.read(buf, sizeof(buf))+file.gcount()组合,gcount()才是实际读字节数 - 对超大文件(>2GB),避免
file.seekg(0, std::ios::end)获取长度(32位off_type溢出),改用循环读+计数 - 摘要上下文(
EVP_MD_CTX)必须在读文件前EVP_DigestInit_ex,读完立即EVP_DigestFinal_ex,别跨函数生命周期持有
生成的签名怎么存?验证时怎么加载?
签名是二进制字节流,不能当字符串处理。Base64编码是唯一靠谱的跨平台存储方式,否则Windows记事本打开就乱码,Linuxcat显示异常字符。
操作建议:
- 签名后立刻用
EVP_EncodeBlock转Base64,写入.sig文件(纯文本,可传输) - 验证时先用
EVP_DecodeBlock还原二进制签名,注意该函数要求输入长度是4的倍数,末尾补=需提前截断 - 不要把签名和公钥硬编码进C++源码——编译后二进制里明文暴露私钥风险;用外部文件或环境变量注入
- 公钥文件建议用
-----BEGIN PUBLIC KEY-----格式(PKIX),别用-----BEGIN RSA PUBLIC KEY-----(旧PKCS#1,EVP_PKEY可能不识别)
ERR_clear_error(),否则后续ERR_get_error()拿到的可能是上一次的残留。


















