必须用readAsArrayBuffer()或file.arrayBuffer()计算MD5,因readAsText()会解码字符导致字节失真;大文件需File.slice()+FileReader分块读取并增量哈希,避免内存溢出。

浏览器里不能靠文件路径或名字算哈希,必须读取二进制内容——File.arrayBuffer() 是最简路径,但大文件会爆内存;真要稳,得用 File.slice() + FileReader.readAsArrayBuffer() 分块读。
为什么不能直接用 readAsText() 算 MD5
因为 readAsText() 会做字符解码:自动吞掉 UTF-8 BOM、把 CRLF 转 LF、甚至乱码替换,字节流早就不一样了。哈希值必然错。MD5/SHA-256 都是对原始字节算的,差一个字节,结果全变。
- 必须用
readAsArrayBuffer()或file.arrayBuffer() -
reader.result是 ArrayBuffer,别调.toString()——那得到的是[object ArrayBuffer],不是数据 - js-md5 的
md5.arrayBuffer()、Web Crypto 的crypto.subtle.digest()都只认 ArrayBuffer
小文件(
代码最短,兼容性好,适合表单上传前快速校验。注意两个硬性前提:
- 页面必须跑在 HTTPS 或
localhost下,否则crypto.subtle报SecurityError - 别在事件回调里写同步逻辑,
await file.arrayBuffer()是异步的 - 示例(SHA-256):
async function getHash(file) { const buffer = await file.arrayBuffer(); const hashBuf = await crypto.subtle.digest('SHA-256', buffer); return Array.from(new Uint8Array(hashBuf)) .map(b => b.toString(16).padStart(2, '0')) .join(''); }
大文件(>100MB)必须分片读取
一次性 arrayBuffer() 会把整个文件塞进内存,Chrome 可能直接 kill tab。正确做法是切片 + 流式哈希,但 Web Crypto 不支持增量 digest,所以得换库:
立即学习“前端免费学习笔记(深入)”;
- 用
spark-md5:它支持SparkMD5.ArrayBuffer.hash(buffer),比手动 add 更快更省内存 - 主线程用
file.slice(start, end)切块,每块用FileReader.readAsArrayBuffer()读,读完立刻发给 Web Worker 计算 - Worker 里别存所有 ArrayBuffer,算完就
postMessage回去,主线程收到后设buffer = null帮助 GC - 并发控制很重要:别同时开 10 个 FileReader,建议限制在 2~3 个活跃读取
真正容易被忽略的是服务端校验环节——前端算出的哈希只是参考,服务端必须用相同方式(比如 fs.readFileSync(path, 'binary') + Node.js crypto.createHash('sha256'))重算一遍,否则攻击者可绕过前端 JS 直接发请求伪造哈希。



















