crypto.subtle.digest 返回的是 ArrayBuffer,不是十六进制字符串,这是新手第一个卡点:

async function sha256Hex(text) {
  const buf = new TextEncoder().encode(text);
  const digest = await crypto.subtle.digest('SHA-256', buf);
  return [...new Uint8Array(digest)]
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
}

三个前提条件

  1. 安全上下文:crypto.subtle 只在 HTTPS 或 localhost 下可用。用 file:// 打开页面时它是 undefined——本地双击 HTML 调试时最容易踩这个坑。
  2. 异步:digest 返回 Promise,没法同步拿到结果。
  3. 支持的算法有限:SHA-1、SHA-256、SHA-384、SHA-512。没有 MD5——它不属于 Web Crypto 标准,也不该再用于安全场景。

大文件怎么办

Web Crypto 不支持流式摘要,必须把整个文件读进内存。几百 MB 的文件在移动端会直接崩掉。这种场景应该交给服务端算,或者用支持增量更新的实现(比如 WASM 版的哈希库)。

几十 MB 以内的文件用它完全没问题,读文件用 file.arrayBuffer() 即可:

const buf = await file.arrayBuffer();
const digest = await crypto.subtle.digest('SHA-256', buf);

HMAC 与真正的加密

需要带密钥的签名用 subtle.sign,算法传 { name: 'HMAC', hash: 'SHA-256' },密钥要先 importKey。注意:算哈希不等于加密,前端做的任何校验都能被绕过,它只能用来验证完整性,不能当权限控制。

实际用途

  • 上传前算文件指纹,实现秒传或去重命名。
  • 对比两个文件是否完全一致,比逐字节比对快。
  • 给内容生成稳定的缓存键。
  • 校验下载文件的完整性,和发布方给的校验值比对。
SHA-256 的输出是 64 个十六进制字符。如果你拿到 32 位,那是 MD5;40 位是 SHA-1。长度先对,再谈内容对不对。

哈希计算工具就是用这套原生 API 实现的,可以拿来验证上面这段代码。