应选用OpenSSL的EVP接口计算MD5,使用EVP_MD_CTX配合EVP_md5()分块读取文件(推荐8KB缓冲区),调用EVP_DigestUpdate增量更新哈希,最后用EVP_DigestFinal_ex获取16字节摘要并转为32字符小写十六进制字符串。

直接用 std::ifstream 一次性读整个文件进内存再算 MD5,对大文件(比如 >1GB)会崩——不是内存溢出就是触发 OOM killer。必须分块读取,配合加密库增量更新哈希值。
选哪个 C++ 加密库?优先用 OpenSSL 的 EVP 接口
别自己实现 MD5,也别用已弃用的 MD5_Init/MD5_Update。OpenSSL 1.1.1+ 推荐用 EVP_MD_CTX + EVP_md5(),它天然支持流式计算,线程安全(每个上下文独立),且跨平台编译稳定。
-
openssl/evp.h是唯一必须包含的头文件 - 链接时加
-lssl -lcrypto(Linux/macOS)或对应 .lib(Windows) - 不用手动管理 MD5 状态数组,
EVP_DigestUpdate自动处理分块边界和 padding
分块大小设多少?8192 字节是实测平衡点
太小(如 512B)会导致系统调用频繁、CPU 缓存失效;太大(如 1MB)可能吃光小内存设备的剩余空间,且无明显速度提升。实测在机械盘、SSD、NVMe 上,8192 字节(8KB)吞吐与稳定性最佳。
- 用
std::vector<char> buffer(8192)</char>预分配,避免反复 new/delete - 读取时用
file.read(buffer.data(), buffer.size()),然后检查file.gcount()获取真实字节数 - 即使最后一块不满 8192,
EVP_DigestUpdate也能正确处理
完整流程:打开 → 分块更新 → 完成摘要 → 转十六进制字符串
关键点是 EVP_DigestFinal_ex 只能调用一次,且必须在所有 EVP_DigestUpdate 之后;输出的 16 字节原始摘要要手工转成 32 字符小写 hex 字符串。
立即学习“C++免费学习笔记(深入)”;
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_md5(), nullptr);
std::ifstream file("input.bin", std::ios::binary);
std::vector<char> buf(8192);
while (file.read(buf.data(), buf.size()) || file.gcount() > 0) {
EVP_DigestUpdate(ctx, buf.data(), file.gcount());
}
unsigned char digest[EVP_MD_size(EVP_md5())];
unsigned int len;
EVP_DigestFinal_ex(ctx, digest, &len); // len == 16
// 转 hex:sprintf_s / std::format(C++20) / 手动查表
EVP_MD_CTX_free(ctx);
- 忘记调用
EVP_MD_CTX_free会泄漏 OpenSSL 内部资源 -
file.gcount()必须在每次read()后立刻读,否则下次read()会覆盖它 - Windows 下若文件路径含中文,用
std::wifstream+_wfopen替代,但 OpenSSL 不接受 wide string,需先转 UTF-8
真正容易卡住的地方是错误处理:OpenSSL 函数返回 0 表示失败,但不会抛异常;file.fail() 和 file.eof() 的组合判断比只看 eof() 更可靠;hex 转换若用 sprintf 要确保目标缓冲区至少 33 字节(32+1 null)。



















