SHA-256 与 SHA-3 的对比,是 SHA-2 家族的主力算法与 NIST 于2015年发布的基于 Keccak 的标准之间的比较。两者都没有被攻破,也远未接近被攻破,并且在 256 位输出长度下提供相同的名义安全性。它们的区别在于内部设计,这会影响长度扩展、速度,以及它们在较新算法内部的使用方式。
概览
| SHA-256 | SHA3-256 | |
|---|---|---|
| 标准 | FIPS 180-4(该家族发布于2001年,2015年修订) | FIPS 202(2015年) |
| 构造 | Merkle–Damgård 结构,64 轮 32 位压缩函数 | 基于 1600 位 Keccak-f 置换的海绵结构,24 轮 |
| 输出 | 256 位 | 256 位 |
| 抗碰撞性 / 抗原像性 | 128 / 256 位 | 128 / 256 位 |
| 长度扩展 | 存在,hash(secret ‖ m) 不是 MAC | 不存在 |
| 可扩展输出的同族函数 | 无 | SHAKE128、SHAKE256 |
| 吞吐量(16 KiB 输入,OpenSSL 3.6.5) | 约 2.85 GB/s | 约 1.01 GB/s |
| 吞吐量(16 字节输入) | 约 0.17 GB/s | 约 0.07 GB/s |
| CPU 指令 | Intel 和 AMD 的 SHA 扩展、ARMv8 SHA2 | ARMv8.2 SHA3 指令(EOR3、RAX1、XAR、BCAX) |
| 必须使用它的场合 | TLS、X.509、JOSE S256 和 RS256 等、比特币、Git(SHA-256 仓库) | ML-KEM、ML-DSA(SHAKE 和 SHA3)、SLH-DSA 的 SHAKE 参数集、Ed448(SHAKE256) |
| 量子计算的影响 | Grover 算法:求原像的工作量最多降为原来的平方根;仍获批准使用 | 相同 |
吞吐量在 Apple M4 Max 的单个核心上用 openssl speed -evp 测得,该芯片对两个家族都有硬件支持。
设计
SHA-256 以 64 字节为一块处理消息,把每一块与前一个 256 位状态一起送入压缩函数;最终状态就是摘要。由于摘要就是完整的内部状态,任何知道 SHA-256(x) 和 x 长度的人,都能在不知道 x 的情况下继续计算哈希,得出 SHA-256(x ‖ padding ‖ y)。这种长度扩展对签名和 HMAC 无害,却会攻破 SHA-256(key ‖ message) 这类简单拼接的 MAC。
SHA-3 采用海绵结构:它通过 Keccak-f 置换把输入吸收进 1600 位的状态,再从状态的一部分中挤出输出。对 SHA3-256 而言,状态中的 1088 位构成速率(rate)部分,512 位构成容量(capacity)部分,容量部分从不输出,因此不可能发生长度扩展,SHA3-256(key ‖ message) 也是一种可靠的 MAC(NIST 为此标准化了 KMAC)。同一海绵结构还产生了可扩展输出函数 SHAKE128 和 SHAKE256,后量子算法用它们把种子扩展为矩阵和噪声。
NIST 于2012年选定 Keccak,此前的竞赛历时五年;竞赛的起因是针对 MD5 和 SHA-1 的攻击让人担心结构相似的 SHA-2 也会出问题。这些担忧并未成真:针对 SHA-256 的最佳碰撞攻击只能达到 64 步中的约 31 步,完整的函数没有显示出任何弱点。因此,SHA-3 是一种后备保障,而不是替代品。
速度
在 Apple M4 Max 上,两个家族都有专用指令,但对于长输入,SHA-256 的速度仍接近 SHA3-256 的三倍:在 OpenSSL 3.6.5 中,SHA-256 约为 2.85 GB/s,SHA3-256 为 1.01 GB/s,SHA3-512 为 0.53 GB/s。在支持 SHA 扩展的 x86 CPU 上,情况与此类似;在没有哈希指令的处理器上,SHA-3 与 SHA-256 的差距较小,而 BLAKE2 或 BLAKE3 比两者都快。对大多数应用而言,两者都远快于周边的 I/O;只有在对超大文件、Merkle 树和区块链计算哈希时,速度差异才有意义。
SHA3-256 不是 Keccak-256
以太坊在 FIPS 202 定稿之前就采用了 Keccak,而 FIPS 202 随后修改了填充规则(加入了域分离位)。两个函数使用相同的置换,得出的摘要却毫无关联。Solidity 的 keccak256、web3_sha3 RPC,以及每个以太坊地址、函数选择器和 EIP-712 摘要,使用的都是 Keccak-256。在以太坊代码中调用某个库的 SHA3-256,会得到错误的结果,而且不会报任何错误。
import { createHash } from "node:crypto";
import { keccak_256 } from "@noble/hashes/sha3.js";
const input = Buffer.from("abc");
console.log("SHA-256 ", createHash("sha256").update(input).digest("hex"));
console.log("SHA3-256 ", createHash("sha3-256").update(input).digest("hex"));
console.log("Keccak-256", Buffer.from(keccak_256(input)).toString("hex"));
SHA-256 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
SHA3-256 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532
Keccak-256 4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
SHA-256 和 SHA3-256 的值与 FIPS 180-4 和 FIPS 202 中“abc”的示例一致。
如何选择
- 协议互操作(TLS、证书、JWT、SSH、Git、包管理器、比特币):SHA-256,因为对端计算的就是它。
- 带密钥的哈希:HMAC-SHA256 这类构造适用于任何 SHA-2 哈希;如果你更喜欢简单的带密钥构造,KMAC 或带密钥的 SHA-3 可以避免 HMAC 的两遍计算。
- 种子扩展、确定性随机流、可变长度输出:SHAKE256 或 SHAKE128。
- 后量子算法:使用标准在内部规定的哈希;ML-KEM 和 ML-DSA 使用 SHA3 和 SHAKE,因此实现它们的库已经包含 SHA-3。
- 以太坊:Keccak-256,切勿使用 FIPS SHA3-256。
- 用于纵深防御的第二种、结构不同的哈希(例如对长期归档同时计算两种哈希):SHA-3 与 SHA-256 并用。
- 没有 FIPS 要求、只追求速度:在没有哈希指令的 CPU 上使用 BLAKE2 或 BLAKE3。
迁移说明
更换哈希函数会改变每一个摘要,因此这是一次格式变更,而不是简单地换一个库。
- 给标识符加上版本:在每个摘要旁记录所用算法(
sha256:、sha3-256:、multihash、OID),镜像仓库和 SRI 等内容寻址系统就是这样做的。 - 过渡期间同时发布两种摘要:计算两种摘要,按使用方能识别的那种进行验证,待使用方都升级后再停用旧摘要。
- 检查对长度扩展的假设:如果代码以
hash(secret ‖ message)的方式构造 MAC,改用 SHA-3 可以解决问题,而改用 HMAC 则在继续使用 SHA-256 的情况下也能解决问题。 - 注意截断和编码:摘要以十六进制、base64 或 base64url 形式传输;编码不一致比算法不一致更常见。
- 不要仅仅出于量子计算的考虑而迁移:面对 Grover 算法,两个函数都至少保有 128 位的抗原像安全性,而且 NIST 的过渡计划草案仍保留两者继续使用。