必须用 evp_md_ctx_new() 分配上下文,因其为不透明结构体且含动态字段;栈上声明会导致未定义行为甚至崩溃;需配对使用 evp_md_ctx_free() 防内存泄漏。

直接用 OpenSSL 的 EVP 接口,别碰 MD5_Init 或 SHA256_Init 这类老接口——它们线程不安全、已弃用,且在 OpenSSL 3.x 中可能被彻底移除。
为什么必须用 EVP_MD_CTX_new 而不是栈上分配 EVP_MD_CTX
OpenSSL 的 EVP_MD_CTX 是不透明结构体,内部有动态分配字段。栈上声明 EVP_MD_CTX ctx; 会导致未定义行为,尤其在 OpenSSL 3.x 下大概率 crash。必须用 EVP_MD_CTX_new() 分配,用完后调 EVP_MD_CTX_free() 释放。
-
EVP_MD_CTX *ctx = EVP_MD_CTX_new();是唯一安全的初始化方式 - 忘记
EVP_MD_CTX_free(ctx)会泄漏内存,尤其在循环校验多个文件时明显 - 传入
nullptr给EVP_DigestInit_ex第三个参数(engine)是安全的,不用额外查文档
EVP_sha256() 和 EVP_md5() 不能写成字符串
OpenSSL 不认 "sha256" 或 "md5" 这种字符串。必须用函数指针:EVP_sha256() 或 EVP_md5()。传错类型(比如把 EVP_md5() 传给 SHA256 流程)会导致 EVP_DigestInit_ex 返回 0,但很多人忽略返回值检查,结果哈希值全错。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 错误写法:
EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sha256"), nullptr);—— 不可靠,EVP_get_digestbyname在 FIPS 模式下可能失败 - 正确写法:
EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr); - 两个算法共用同一套流程,只换 digest 函数指针即可,无需重写逻辑
读文件必须用二进制模式 + 分块 + 检查每次 fread 返回值
Windows 下用文本模式打开文件,\r\n 会被转成 \n,哈希值立刻失效;一次性 seekg(0, end) 再 new 大缓冲区,2GB 文件直接 std::bad_alloc 或触发 OOM killer。
- 打开方式必须是:
std::ifstream file(path, std::ios::binary)或 C 风格fopen(path, "rb") - 缓冲区设为
8192(8KB),太小系统调用频繁,太大无收益 -
len = fread(buf, 1, sizeof(buf), fp)后必须判断:len == 0是 EOF,len 才是错误;<code>ferror(fp)可进一步确认 - 每次
EVP_DigestUpdate(ctx, buf, len)前检查len > 0,避免传nullptr导致返回 0
输出哈希值时每个字节必须用 %02x 格式化
SHA256 结果是 32 字节二进制数据,MD5 是 16 字节。直接用 printf("%x", hash[i]) 会丢前导零、大小端混乱、还可能截断高位字节。
- 正确做法:循环对每个字节用
std::sprintf(tmp, "%02x", hash[i])或std::hex - 别用
std::string((char*)hash)构造字符串——里面含 \0,std::string会提前截断 - 最终结果是 64 位小写十六进制字符串(SHA256)或 32 位(MD5),大小写要统一,校验时才不会因大小写不匹配失败
最易被忽略的是三处返回值检查:EVP_DigestInit_ex、每次 EVP_DigestUpdate、EVP_DigestFinal_ex——任一失败都意味着哈希无效,但错误静默发生,不打印日志根本发现不了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










