应使用流式哈希计算:os.Open + io.Copy + sha256.New(),避免os.ReadFile导致OOM;必须检查io.Copy的error和n值,校验时用bytes.Equal比较原始字节数组。

crypto/sha256 是最稳妥的起点,别用 md5 做完整性校验——它只适合内部快速比对,生产环境必须用 sha256。
为什么不能直接 os.ReadFile + sha256.Sum
大文件(比如 500MB 的日志或镜像)会直接 OOM:os.ReadFile 把整个文件塞进内存,而 sha256.Sum 是为小字节切片设计的,不是流式接口。你看到的“能跑通”,只是小文件侥幸没崩。
- 正确路径是:用
os.Open打开文件,再通过io.Copy流式写入sha256.New()实例 -
sha256.New()返回的是hash.Hash接口,支持Write和Sum,天然适配流 - 别复用同一个
hasher实例去算多个文件——结果会串,每次都要新调sha256.New()
io.Copy 必须检查错误,不能只看 err == nil
io.Copy 的第二个返回值 error 容易被忽略,但磁盘突然拔掉、权限变更、NFS 挂载中断时,它会静默截断数据并返回 nil 错误(实际是 io.EOF 或其他底层错误),导致你算出一个“合法但错的”哈希值。
- 必须显式判断:
n, err := io.Copy(hasher, f);若err != nil,立刻返回带路径的错误,例如fmt.Errorf("read %q: %w", path, err) - 还要检查
n == 0:空文件会生成有效哈希,但业务上往往要拒绝(比如配置文件不能为空) - 别在
defer里关文件后还继续用f——defer f.Close()要紧挨着os.Open放
校验时别用字符串比较 ==,用 bytes.Equal
服务端给的哈希如果是十六进制字符串(如 "a1b2c3..."),你得先用 hex.DecodeString 解码成 []byte,再和本地计算出的 hasher.Sum(nil) 结果比对。
- 解码前必须检查
hex.DecodeString的error:长度非偶数、含非法字符(比如空格或换行)都会失败 - 比对时用
bytes.Equal(sum1[:], sum2[:]),不是hex.EncodeToString(sum1[:]) == hex.EncodeToString(sum2[:]) - 前者直接比原始字节数组,零分配、防篡改、抗时序攻击;后者多两次内存分配,且字符串可能被中间人注入空白符
小文件也建议走流式,统一逻辑更安全
哪怕只有几 KB,也别为了“省事”切到 os.ReadFile + sha256.Sum。代码路径一旦分裂,上线后某天遇到一个 2GB 文件就挂了,而且排查成本远高于一开始写对。
立即学习“go语言免费学习笔记(深入)”;
- 流式路径(
os.Open→io.Copy→hasher.Sum(nil))对任意大小都恒定内存占用(约 32KB 缓冲区) - 所有错误类型一致:打开失败、读取中断、空文件,都能在同一个
if err != nil分支里处理 - 如果真需要进度回调(比如上传大文件时显示百分比),才手动分块
f.Read(buf),否则io.Copy内部已优化好缓冲策略
io.Copy 的返回值不检查,等于把校验当装饰品。


















