硅基俊宁.
← 返回文章

SHA-256:从原理到构建、发布与运行时完整性

俊宁 · 日期未详
文章目录

区分内容摘要、数字签名与平台签名,梳理 hash 对象、编码、可信摘要来源、集合闭包与失败恢复的设计检查。

本文讨论通用机制和设计判断;示意不对应任何具体产品的已交付状态。源码阅读坐标沿用原稿,本次整理不构成新的运行时验收。

一条 SHA-256 内容指纹贯穿 定制浏览器 的源码输入、构建准备、制品封装与渠道交付

(此版本省略该配图。)

一句话总结

职责回答的问题典型对象
输入完整性下载或安装到本地的是不是约定输入Sparkle archive、Bun runtime、native CLI、sigclient
制品内容身份这个 Runtime、CRX、DMG、Installer 或 manifest 到底是哪份字节per-file hash、crxSha256、manifestSha256、DMG SHA-256
发布回读CDN/对象存储实际提供的内容是否等于刚上传的内容Sparkle artifact/appcast、Windows installer/CRX/descriptor
运行时校验与协议启动、安装或协议交互时是否拿到契约指定内容Runtime materialization、managed plugin、CUP、PKCE、CRX3
可复现证据两次评测、缓存或生成任务是否属于同一输入范围performance scope、artifact baseline、obfuscation seed、Toolbox content address

同一个算法可以出现在安全边界、缓存键和实验标识中。判断一处 SHA-256 是否真的构成安全控制,关键不在算法名字,而在“期望摘要从哪里来、谁能修改它、校验失败会不会阻断主流程”。

SHA-256 到底是什么

SHA-256 属于 SHA-2 家族,是一种 cryptographic hash function。它接收任意长度的消息,输出固定 256 bit,也就是 32 byte 的 message digest。常见十六进制编码每个字符表示 4 bit,因此完整摘要正好是 64 个 hex 字符。

以 UTF-8 字符串 hello 的字节为输入(不含结尾换行),输出为:

2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Node.js 中对应的最小写法是:

import { createHash } from 'node:crypto';

const digest = createHash('sha256')
  .update(Buffer.from('hello'))
  .digest('hex');

它有四个对工程系统很实用的性质:

  1. 同一串字节始终产生同一摘要。
  2. 输入只要发生微小变化,输出通常就会完全不同。
  3. 从摘要反推原始输入在计算上不可行。
  4. 找到两份不同输入却产生同一摘要,在现实工程规模下不可行。

NIST 在 FIPS 180-4 中把这类 digest 的用途概括为检测消息在摘要生成后是否发生变化。注意,这句话隐含了一个前提:比较使用的 expected digest 本身必须可信。

SHA-256 不是什么

它不是加密

加密的目标是“有密钥就能恢复明文”;hash 没有解密操作。对同一输入重复计算,只会再次得到相同摘要。

它不是数字签名

裸 SHA-256 没有私钥。任何人都能对任意文件计算摘要。如果攻击者可以同时替换文件和旁边记录的 SHA-256,那么比较仍会通过。

它不是 code signing、notarization 或 Gatekeeper

macOS App 的 Developer ID、nested code、entitlements、notarization 与 Gatekeeper 是平台信任链。DMG 的 SHA-256 相同,只能说明 DMG 字节相同,不能自动证明 App 签名有效、公证完成或 updater 能执行。

先建立一个正确的工程心智模型

一个完整的 hash contract 至少包含五个部分:

最常见的错误是只记录“用了 SHA-256”,却没有说明 hash 的是哪一层。例如下面四个对象逻辑上都可以叫“Runtime 1.0.0”,但它们不是同一串字节:

对象SHA-256 语义
artifact-metadata.jsonmetadata 文件的原始字节
runtime-manifest.json最终 manifest 文件的原始字节
manifest 中的 file.sha256package 内源文件字节
manifest 中的 file.outputSha256Brotli 解压后的运行时文件字节

所以,看到某个摘要时,第一句不应问“hash 对不对”,而应问“它绑定的是哪一层字节”。

设计一个新的 SHA-256 contract

第一步:先写清楚被 hash 的对象

不要只命名 hash。优先使用能表达层级的字段,例如 archiveSha256、manifestSha256、outputSha256、unsignedSha256。在 contract 注释中写清:

  1. 输入是原始 bytes 还是 UTF-8 string。
  2. JSON 是否 canonicalize,采用什么排序规则。
  3. 目录如何排序 path,是否加入 file type、executable bit、symlink target。
  4. 压缩前还是解压后。
  5. 签名前还是签名后。

第二步:固定编码与 schema

结构化总摘要应把 schemaVersion 放进被 hash 内容,避免未来更换 canonicalization 或目录遍历规则后,新旧算法产生无法解释的相同字段名。

第三步:给 expected digest 建立可信来源

安全校验的关键不是 createHash(),而是 expected digest 的控制面:

expected digest 来源可信度判断
与文件同目录、同权限、同时下载的 .sha256只能发现偶发损坏,不能抵抗同源篡改
Git review 后进入 Kernel manifest/lock能绑定受审查源码状态
被 private key 签名的 descriptor同时提供内容与发布者身份
用户手工复制的聊天文本容易抄错、过期,缺少自动审计

第四步:同时校验 size、path 与集合闭包

  1. 路径必须相对且不能逃逸 owner root。
  2. 只允许 regular file,按场景拒绝 symlink。
  3. size 必须一致。
  4. manifest 中不能有重复 path。
  5. 实际集合不能缺文件或多文件。
  6. executable bit 在需要时必须一致。

这样可以在计算大文件摘要前快速失败,也防止“hash 正确但文件落到了错误位置”等结构问题。

第五步:定义失败语义与恢复动作

安全、完整性和签名门禁失败必须阻断后续动作;cache/hash 只用于优化时,则可以删除 cache 并重建。不要把二者混成 false 后静默继续。

一条好的错误至少要包含对象、expected、actual 和恢复动作,但避免泄露 private key、token、用户路径或敏感 URL query。

第六步:测试正反两条路径

至少覆盖:正确 bytes、单 byte 变化、size 变化、非法编码、uppercase/lowercase 约束、缺失/多余文件、symlink/路径逃逸、压缩前后不一致、expected digest 被替换,以及平台不支持优化时的安全降级。

常见误区

误区正确判断
“SHA-256 一样,所以文件可信”只能说明与某个 expected digest 一致;先问 expected digest 是否可信
“文件名和版本号一样,可以复用 hash”hash 绑定 bytes,不绑定逻辑名称
“签名后 hash 不应该变”code signing、notarization、staple、封装都会改变 bytes
“JSON 内容看起来一样,hash 就应该一样”raw byte hash 受空格、换行和 key 顺序影响;只有明确 canonicalization 才是语义 hash
“目录里每个文件都 hash 了,目录身份就稳定”还要固定 path 排序、分隔符、metadata、symlink 与集合闭包
“比较前 8/16 位已经够了”只适合目录名/显示;安全门禁比较完整 digest
“有 SHA-256,就不需要数字签名”裸 hash 没有发布者身份,不能替代 Ed25519/RSA/ECDSA 或平台 code signing
“hash 通过即可宣布上线”source、package、signing、upload、channel 与客户端验收是独立状态

最后记住三句话

第一,SHA-256 是内容摘要,不是加密,也不是发布者身份。

第三,排障时永远同时问:hash 的是哪串字节、expected digest 从哪里来、失败后当前流程是否真的停止。

参考资料

  1. NIST FIPS 180-4:Secure Hash Standard:SHA-2 家族与 message digest 的正式定义。
  2. RFC 7636:Proof Key for Code Exchange by OAuth Public Clients:PKCE S256 的计算和安全边界。
  3. Chromium Cookie DB v24 change:domain SHA-256 prefix 的上游格式背景。

继续阅读