用openssl的evp_md_ctx可安全计算大文件md5:需分块读取(如bufsiz=8192)、二进制模式打开文件、正确管理ctx生命周期、snprintf转小写32位hex字符串。

用 OpenSSL 的 EVP_MD_CTX 做增量计算,别直接读全文件
MD5 是哈希,不是加密,不支持“解密”或“还原”,只支持对任意长度数据流做一次性摘要。大文件(比如 >100MB)如果用 fread 一次性载入内存,会触发 OOM 或明显拖慢程序。OpenSSL 提供的 EVP 接口专为流式计算设计,必须分块喂给它。
常见错误是把整个文件读进 std::string 或 std::vector<uint8_t></uint8_t> 再传给 EVP_DigestUpdate——这等于自己绕过了流式优势,还多一次内存拷贝。
- 每次用
fread(buf, 1, BUFSIZ, fp)读取(推荐BUFSIZ = 8192,兼顾系统页大小和 cache 局部性) - 每读一块就调
EVP_DigestUpdate(ctx, buf, len),len是实际读到的字节数(注意不是BUFSIZ) - 文件末尾可能不满一块,
fread返回值必须检查,不能假设每次都读满
EVP_md5() 初始化和 EVP_DigestFinal_ex() 收尾的固定写法
OpenSSL 1.1.1+ 要求显式管理 EVP_MD_CTX 生命周期,不能用老式 MD5_Init/MD5_Update(已废弃且线程不安全)。漏掉 EVP_MD_CTX_free(ctx) 会导致内存泄漏;提前调 EVP_DigestFinal_ex 两次会 crash。
- 初始化必须用
EVP_MD_CTX_new()+EVP_DigestInit_ex(ctx, EVP_md5(), nullptr),第二个参数不能传nullptr - 收尾时
unsigned char md[EVP_MAX_MD_SIZE]足够存 MD5(16 字节),但要用unsigned int md_len接收真实长度,不能硬写16 - 收尾后必须立刻调
EVP_MD_CTX_free(ctx),哪怕在函数 return 前——RAII 包装器容易漏析构逻辑
输出 32 位十六进制字符串:别手写 for 循环格式化
MD5 摘要本质是 16 字节二进制,转成小写 32 位 hex 字符串是惯例。手动用 sprintf(buf + i*2, "%02x", md[i]) 容易越界或大小写不一致;用 std::hex 流又引入 iostream 依赖和临时对象开销。
- 推荐用静态字符数组 +
snprintf一次性写入:snprintf(hex, sizeof(hex), "%02x%02x%02x%02x...", md[0], md[1], ...) - 更稳妥是循环 +
snprintf(dst + i*2, 3, "%02x", md[i]),确保每轮写 2 字节且 null-terminated - 注意
%02x输出小写,%02X是大写,多数工具(如md5sum)默认小写,保持一致避免校验失败
Windows 下打开文件必须用 "rb" 模式
文本模式("r")在 Windows 会把 \r\n 自动转成 \n,导致二进制内容被篡改,MD5 值必然错。Linux/macOS 虽然没这问题,但跨平台代码必须统一用二进制模式。
-
fopen(path.c_str(), "rb")是唯一安全写法,path为std::string时注意编码(UTF-8 路径在 Windows 需_wfopen+MultiByteToWideChar) - 如果用
std::ifstream,必须加std::ios::binary标志:std::ifstream f(path, std::ios::binary) - 错误现象典型:同一文件在 Linux 算出的 MD5 和 Windows 不一致,十有八九是 fopen 模式错了
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











