必须用std::ios::binary模式打开文件,分块读取并调用EVP_DigestUpdate流式计算SHA256,每次read后用gcount()检查实际字节数,最后用EVP_DigestFinal_ex获取哈希值并转为小写十六进制字符串比对。

下载后立刻用 std::ifstream 读二进制算 SHA256,别用文本模式
Windows 下默认 std::ifstream 是文本模式,会把 \r\n 转成 \n,导致哈希值错乱。必须显式指定 std::ios::binary 标志:
std::ifstream file("downloaded.bin", std::ios::binary);
if (!file) { /* 处理打开失败 */ }
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
<p>std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
// 后续喂给 SHA256 实现(如 OpenSSL 或 mbedtls)
- 不要用
file >> buffer或getline,它们是文本语义 - 大文件别一次性读进内存;可分块(如 8KB)调用
EVP_DigestUpdate(OpenSSL)或自定义哈希更新逻辑 - 用
file.gcount()检查实际读取字节数,防止 EOF 提前截断
用 OpenSSL 的 EVP_sha256() 做流式哈希,避免自己实现
自己写 SHA256 容易出错,且无优化。OpenSSL 提供成熟的流式接口,支持增量计算:
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
<p>std::ifstream file("downloaded.bin", std::ios::binary);
std::vector<uint8_t> buf(8192);
while (file.read(reinterpret_cast<char*>(buf.data()), buf.size())) {
EVP_DigestUpdate(ctx, buf.data(), file.gcount());
}
// 处理剩余不足一整块的部分
if (file.gcount() > 0) {
EVP_DigestUpdate(ctx, buf.data(), file.gcount());
}</p><p>unsigned char hash[EVP_MD_size(EVP_sha256())];
unsigned int len;
EVP_DigestFinal_ex(ctx, hash, &len);
EVP_MD_CTX_free(ctx);
- 务必检查
EVP_MD_CTX_new()返回是否为nullptr(OOM 或初始化失败) - 别漏掉最后的
file.gcount()分支:当文件大小不是 8192 整数倍时,read()仍成功但gcount()小于buf.size() - OpenSSL 1.1.1+ 支持
EVP_MD_fetch,但若只校验 SHA256,直接用EVP_sha256()更轻量
下载过程就边下边算哈希,省一次磁盘 IO
如果用 libcurl 下载,可在 CURLOPT_WRITEFUNCTION 回调里同步喂数据给哈希上下文,不落地也能校验:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
struct HashContext {
EVP_MD_CTX* ctx;
// ... 其他状态
};
<p>size_t write_callback(void<em> ptr, size_t size, size_t nmemb, void</em> userp) {
auto<em> hc = static_cast<HashContext</em>>(userp);
size_t total = size * nmemb;
EVP_DigestUpdate(hc->ctx, ptr, total);
// 同时写入文件流(如果需要保存)
return total;
}</p><p>// 使用时:
HashContext hc{EVP_MD_CTX_new()};
EVP_DigestInit_ex(hc.ctx, EVP_sha256(), nullptr);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &hc);
- 这个方式能规避“下载完再读文件”带来的重复 IO 和临时磁盘占用
- 注意回调中不要做耗时操作(如日志、锁),否则拖慢下载
- 若下载中断,哈希上下文可保留并恢复(需你自己管理状态),但多数场景只需失败重试
比对哈希字符串时注意编码和大小写,别用 == 直接比较
服务端返回的哈希可能是小写 hex(如 "a1b2c3..."),而 OpenSSL 输出的 raw bytes 转 hex 默认大写("A1B2C3...")。直接字符串比对会失败:
立即学习“C++免费学习笔记(深入)”;
// 错误示例:假设 expected_hash 是小写字符串
std::string hex_hash = bytes_to_hex(hash, len); // 若实现返回大写,则不匹配
if (hex_hash == expected_hash) { ... } // 可能永远为 false
- 统一转小写再比对:
std::transform(hex_hash.begin(), hex_hash.end(), hex_hash.begin(), ::tolower) - 更稳妥的是把预期哈希也解析成 byte 数组,用
std::memcmp(hash, expected_bytes, len)比二进制 - 别忽略末尾换行或空格:HTTP 响应头或 JSON 字段里可能带
\r\n或空格,用std::string::find_first_not_of(" \t\r\n")清理
校验逻辑本身不难,难在边界:文件读取的二进制语义、OpenSSL 上下文生命周期、网络回调与哈希更新的耦合点、以及哈希字符串格式的隐式约定——这些地方错一个,校验就形同虚设。


















