两个文件看起来一模一样,SHA-256 为什么不同?用 Web Crypto 实现浏览器本地文件校验

举报
yd_241537900 发表于 2026/11/05 15:24:51 2026/11/05
【摘要】 前言在下载软件安装包、备份文件或传输资料时,我们经常会看到一串 SHA-256 校验值。例如:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad这串字符看起来很复杂,但它其实可以用来检查文件内容是否发生变化。最近在研究浏览器原生 API 时,我想到一个问题:能不能不安装任何依赖、不上传文件到服务器,直接在网页中计算...

前言

在下载软件安装包、备份文件或传输资料时,我们经常会看到一串 SHA-256 校验值。

例如:

ba7816bf8f01cfea414140de5dae2223
b00361a396177a9cb410ff61f20015ad

这串字符看起来很复杂,但它其实可以用来检查文件内容是否发生变化。

最近在研究浏览器原生 API 时,我想到一个问题:

能不能不安装任何依赖、不上传文件到服务器,直接在网页中计算文件的 SHA-256?

答案是可以的。

现代浏览器提供了 Web Crypto API,可以在客户端完成 SHA-256 哈希计算。

本文从基本原理出发,使用 HTML 和 JavaScript 实现一个简单的本地文件校验工具,同时分析几个开发中容易遇到的问题。

一、SHA-256 是什么?

SHA-256 是 SHA-2 家族中的一种密码学哈希算法。

它可以将任意长度的输入数据转换为固定长度的摘要。

SHA-256 的输出为 256 位,通常使用 64 个十六进制字符表示。

例如,字符串 abc 按 UTF-8 编码后的 SHA-256 为:

ba7816bf8f01cfea414140de5dae2223
b00361a396177a9cb410ff61f20015ad

哈希算法有几个重要特点:

第一,相同的字节内容会产生相同的哈希值。

第二,只要输入字节发生变化,哈希值通常就会明显不同。

第三,无法通过 SHA-256 摘要直接还原原始文件。

因此,SHA-256 常用于文件完整性校验、内容寻址和其他需要密码学摘要的场景。

不过需要注意:SHA-256 可以帮助验证文件是否与预期内容一致,但它本身不能证明文件来源可信,也不等于病毒扫描。

二、为什么两个看起来一样的文件,SHA-256 会不同?

这是一个很容易遇到的问题。

假设我们有两个文本文件。

第一个内容是:

Hello World

第二个内容看起来也是:

Hello World

但是计算 SHA-256 后,结果却不一样。

原因可能并不是哈希算法出了问题,而是文件实际字节不同。

常见情况包括:

原因 说明
换行符不同 Windows 常见 CRLF,Linux 常见 LF
文件编码不同 UTF-8、UTF-16 等编码产生不同字节
UTF-8 BOM 有无 BOM 会影响文件内容
末尾空格或换行 不容易被肉眼发现
图片重新保存 编码参数或元数据可能发生变化

例如:

const a = new TextEncoder().encode("Hello");
const b = new TextEncoder().encode("Hello\n");

这两段数据只差一个换行符,但它们的 SHA-256 会不同。

所以,文件校验时真正比较的是字节,而不是屏幕上显示的文字。

三、浏览器如何计算 SHA-256?

现代浏览器提供了 crypto.subtle.digest() 方法。

它可以对输入数据计算密码学摘要。

例如:

async function sha256(text) {
  const data = new TextEncoder().encode(text);

  const buffer = await crypto.subtle.digest(
    "SHA-256",
    data
  );

  return Array.from(new Uint8Array(buffer))
    .map(byte => byte.toString(16).padStart(2, "0"))
    .join("");
}

sha256("abc").then(console.log);

运行后,可以得到前面提到的标准 SHA-256 测试值。

其中:

TextEncoder 用于把字符串编码为 UTF-8 字节。

crypto.subtle.digest() 负责计算 SHA-256。

Uint8Array 用于读取摘要字节。

最后将字节转换为十六进制字符串,方便显示和比较。

整个过程不需要额外安装 JavaScript 加密库。

四、实战:做一个本地文件 SHA-256 校验器

接下来实现一个简单网页。

功能包括选择文件、计算 SHA-256、填写预期哈希值并进行比较。

为了便于演示,这里把 HTML 和 JavaScript 写在同一个文件中。

创建 index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1">
  <title>SHA-256 文件校验</title>
</head>
<body>
  <h1>SHA-256 文件校验助手</h1>

  <label for="fileInput">选择文件</label>
  <input type="file" id="fileInput">

  <button id="calculateBtn" type="button">
    计算 SHA-256
  </button>

  <p>计算结果:</p>
  <output id="hashResult"></output>

  <p>
    <label for="expectedHash">
      预期 SHA-256
    </label>
  </p>

  <input id="expectedHash"
         placeholder="输入64位十六进制校验值">

  <button id="compareBtn" type="button">
    比较校验值
  </button>

  <p id="status" role="status"></p>

  <script>
    const fileInput =
      document.getElementById("fileInput");

    const hashResult =
      document.getElementById("hashResult");

    const expectedHash =
      document.getElementById("expectedHash");

    const status =
      document.getElementById("status");

    let currentHash = "";

    fileInput.addEventListener("change", () => {
      currentHash = "";
      hashResult.textContent = "";
      status.textContent = "";
    });

    document.getElementById("calculateBtn")
      .addEventListener("click", async () => {
        const file = fileInput.files[0];

        if (!file) {
          status.textContent = "请先选择文件";
          return;
        }

        if (!globalThis.crypto?.subtle) {
          status.textContent =
            "当前环境不支持 Web Crypto,请使用 HTTPS 或 localhost";
          return;
        }

        if (file.size > 20 * 1024 * 1024) {
          status.textContent =
            "示例限制文件大小不超过20MB";
          return;
        }

        status.textContent = "正在计算...";
        currentHash = "";
        hashResult.textContent = "";

        try {
          const data = await file.arrayBuffer();

          const digest = await crypto.subtle.digest(
            "SHA-256",
            data
          );

          currentHash = Array.from(
            new Uint8Array(digest)
          ).map(
            b => b.toString(16).padStart(2, "0")
          ).join("");

          hashResult.textContent = currentHash;
          status.textContent = "计算完成";
        } catch {
          status.textContent = "计算失败,请重试";
        }
      });

    document.getElementById("compareBtn")
      .addEventListener("click", () => {
        const expected =
          expectedHash.value.trim().toLowerCase();

        if (!currentHash) {
          status.textContent = "请先计算文件哈希";
          return;
        }

        if (!/^[0-9a-f]{64}$/.test(expected)) {
          status.textContent =
            "请输入有效的64位SHA-256值";
          return;
        }

        status.textContent =
          currentHash === expected
            ? "校验通过:哈希值一致"
            : "校验失败:哈希值不一致";
      });
  </script>
</body>
</html>

这个示例使用浏览器本地读取文件,并没有把文件上传到远程服务器。

为了避免演示程序一次读取过大的文件,代码将文件大小限制为 20MB。

实际项目可以根据使用场景调整限制,但不应该直接认为浏览器适合一次性处理任意大小的文件。

五、为什么 Web Crypto 有时会提示不可用?

在实际开发中,还有一个容易遇到的问题:

本地测试时 SHA-256 可以正常计算,换一个访问地址后却报错。

其中一个重要原因是 Web Crypto 的安全上下文要求。

crypto.subtle 通常只在安全上下文中可用。

例如:

https://example.com

通常可以使用。

本地开发时,以下地址一般也可以:

http://localhost:3000
http://127.0.0.1:3000

但如果通过普通 HTTP 的远程地址访问,浏览器可能不提供对应 API。

因此,开发这类工具时,建议使用 HTTPS 或浏览器认可的本地开发地址。

不要通过引入不可信的外部哈希服务绕过这个问题,否则反而可能破坏本地文件处理的隐私优势。

六、大文件计算还有什么限制?

上面的示例使用:

const data = await file.arrayBuffer();

这会把整个文件读入内存。

对于小型文件,使用起来很方便。

但如果用户选择一个几 GB 的文件,浏览器就可能面临较大的内存压力。

另外,标准 Web Crypto 的 subtle.digest() 并不提供通用的增量式流式哈希接口。

如果业务需要处理超大文件,可以考虑成熟的增量哈希实现,并结合 Web Worker 避免长时间占用主线程。

但对于普通小型文件校验工具,直接使用浏览器原生 Web Crypto 通常已经足够。

技术方案应当根据真实需求选择,而不是为了复杂而复杂。

七、SHA-256 一致,就代表文件绝对安全吗?

这是另一个需要特别澄清的问题。

假设我们下载了一个安装包。

下载页面提供了 SHA-256 值。

我们计算后发现两个值完全一致。

这能够说明:在 SHA-256 的安全假设下,下载文件与该参考摘要对应的内容一致。

但是,如果下载页面本身遭到篡改,攻击者同时替换了文件和页面上的哈希值,那么简单的哈希比较仍然可能显示一致。

因此,对于安全要求较高的软件分发场景,除了完整性校验,还需要关注:

  • 哈希值是否来自可信渠道。

  • 下载来源是否可靠。

  • 软件是否提供可信的数字签名。

  • 安装包是否经过必要的安全检查。

SHA-256 解决的是内容完整性问题,不是全部的软件安全问题。

八、总结

通过浏览器原生 Web Crypto API,我们可以很方便地实现一个不依赖后端的文件 SHA-256 校验工具。

这个过程也让我们理解了几个重要的开发知识点:

第一,SHA-256 比较的是文件实际字节,而不是文件名称或视觉内容。

第二,文本编码、换行符和文件元数据变化都可能导致哈希不同。

第三,Web Crypto 可以完成浏览器本地哈希计算,但需要满足安全上下文要求。

第四,对于大文件,应考虑内存占用和分块处理方案。

第五,哈希值一致可以提供完整性验证,但不能单独证明文件来源安全。

我认为,对于一些功能明确的小工具,优先利用浏览器原生 API,往往能够减少依赖安装、构建环境和后期维护的复杂度。

一个好的开发方案,不一定需要复杂框架。有时候,浏览器已经提供了完成任务所需要的全部能力。


【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。