SHA-2 哈希函数家族原理详解:从 Merkle-Damgård 结构到安全性证明
概述
SHA-2(Secure Hash Algorithm 2)是由美国国家标准与技术研究院(NIST)于 2001 年发布、2012 年在 FIPS 180-4 中标准化的密码学哈希函数家族。它包含六种输出长度不同的变体:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256,是目前全球应用最广泛的哈希算法标准。
SHA-2 的设计是对 SHA-1(FIPS 180-1)安全弱点的直接回应。2005 年王小云团队提出的碰撞攻击证明 SHA-1 的实际安全强度远低于理论值(2⁶¹ 次运算而非 2⁸⁰),NIST 随即推动 SHA-2 作为替代方案。与 SHA-3(Keccak)采用全新的海绵结构不同,SHA-2 延续了经典的 Merkle-Damgård 迭代结构,但在消息扩展和压缩函数层面做了大幅增强。
SHA-2 的关键参数
| 变体 | 输出长度 | 字长 | 轮数 | 块大小 | 初始值来源 |
|---|---|---|---|---|---|
| SHA-224 | 224 bit | 32 bit | 64 | 512 bit | SHA-256 IV 截断 |
| SHA-256 | 256 bit | 32 bit | 64 | 512 bit | 质数平方根小数部分 |
| SHA-384 | 384 bit | 64 bit | 64 | 1024 bit | SHA-512 IV 截断 |
| SHA-512 | 512 bit | 64 bit | 64 | 1024 bit | 质数平方根小数部分 |
注:SHA-512/224 和 SHA-512/256 使用与 SHA-512 相同的字长和轮数,但采用不同的初始值(IV),输出截断为 224 和 256 位。
Merkle-Damgård 结构
SHA-2 建立在 Merkle-Damgård 迭代范式之上,这是绝大多数传统哈希函数(MD5、SHA-1、SHA-2、SM3)的通用框架。
迭代范式
m = "Hello" (任意长度)
│
├── 消息填充(Padding)
│ m' = m || 1 || 0^k || len(m)
│ 其中 k 使总长度 ≡ 0 (mod block_size)
│
├── 分块(Blocking)
│ M = M₁ || M₂ || ... || Mₙ
│ 每块 512 bit (SHA-256) 或 1024 bit (SHA-512)
│
├── 迭代压缩(Iterative Compression)
│ H₀ = IV (初始化向量)
│ Hᵢ = Compress(Hᵢ₋₁, Mᵢ) for i = 1..n
│
└── 输出
Hₙ (最终哈希值)Merkle-Damgård 强化
Merkle-Damgård 结构本身并非在任意压缩函数下都是安全的,它满足以下性质:
- 抗碰撞性传递:若压缩函数抗碰撞,则整个哈希函数抗碰撞
- 长度编码安全性:最终块中编码消息长度,防止长度扩展攻击(但 SHA-2 实际仍受此问题影响,见"相关实践")
消息扩展(Message Schedule)
SHA-256 将 512-bit 消息块(16 个 32-bit 字)扩展为 64 个字,供后续 64 轮压缩使用:
W[0..15] = M 的 16 个 32-bit 字(直接复制)
for t = 16 to 63:
σ₀(x) = ROTR⁷(x) ⊕ ROTR¹⁸(x) ⊕ SHR³(x)
σ₁(x) = ROTR¹⁷(x) ⊕ ROTR¹⁹(x) ⊕ SHR¹⁰(x)
W[t] = σ₁(W[t-2]) + W[t-7] + σ₀(W[t-15]) + W[t-16]SHA-512 使用类似的扩展算法,但轮数为 80 轮,字长 64 bit:
for t = 16 to 79:
σ₀(x) = ROTR¹(x) ⊕ ROTR⁸(x) ⊕ SHR⁷(x)
σ₁(x) = ROTR¹⁹(x) ⊕ ROTR⁶¹(x) ⊕ SHR⁶(x)
W[t] = σ₁(W[t-2]) + W[t-7] + σ₀(W[t-15]) + W[t-16]关键设计:消息扩展的非线性引入了雪崩效应——输入消息的微小变化会导致 64 个扩展字完全不同。
压缩函数(Compression Function)
SHA-256 的压缩函数是 64 轮迭代,每轮使用不同的常数 K[t] 和消息字 W[t]:
轮函数结构
初始工作变量(来自上一轮哈希值):
a, b, c, d, e, f, g, h
每轮更新:
Σ₀(a) = ROTR²(a) ⊕ ROTR¹³(a) ⊕ ROTR²²(a)
Σ₁(e) = ROTR⁶(e) ⊕ ROTR¹¹(e) ⊕ ROTR²⁵(e)
Ch(e,f,g) = (e ∧ f) ⊕ (¬e ∧ g)
Maj(a,b,c) = (a ∧ b) ⊕ (a ∧ c) ⊕ (b ∧ c)
T₁ = h + Σ₁(e) + Ch(e,f,g) + K[t] + W[t]
T₂ = Σ₀(a) + Maj(a,b,c)
新状态:
h = g
g = f
f = e
e = d + T₁
d = c
c = b
b = a
a = T₁ + T₂轮常数 K[0..63]
SHA-256 的前 64 个质数的立方根小数部分,取前 32 位:
K[0] = 0x428a2f98 (2 的立方根)
K[1] = 0x71374491 (3 的立方根)
K[2] = 0xb5c0fbcf (5 的立方根)
K[3] = 0xe9b5dba5 (7 的立方根)
...
K[63] = 0xc67178f2 (311 的立方根)SHA-512 使用 64 位字长,轮常数为前 80 个质数的立方根小数部分的前 64 位。
初始值 IV
SHA-256 的初始值来自前 8 个质数的平方根小数部分的前 32 位:
H[0] = 0x6a09e667 (√2)
H[1] = 0xbb67ae85 (√3)
H[2] = 0x3c6ef372 (√5)
H[3] = 0xa54ff53a (√7)
H[4] = 0x510e527f (√11)
H[5] = 0x9b05688c (√13)
H[6] = 0x1f83d9ab (√17)
H[7] = 0x5be0cd19 (√19)设计哲学:使用数学常数(质数平方根/立方根)作为"nothing up my sleeve numbers",避免隐藏后门嫌疑。
安全性分析
抗碰撞性
SHA-256 的理论抗碰撞强度为 2¹²⁸(生日攻击边界),SHA-512 为 2²⁵⁶。截至 2026 年,对完整轮数的 SHA-256 尚未找到低于理论值的碰撞攻击。
已知攻击
| 攻击类型 | 目标 | 复杂度 | 状态 |
|---|---|---|---|
| 碰撞攻击 | 完整 SHA-256 | 2¹²⁸ | 理论最优 |
| 碰撞攻击 | 24 轮简化版 | 2¹¹² | 学术研究 |
| 预像攻击 | 完整 SHA-256 | 2²⁵⁶ | 理论最优 |
| 多碰撞攻击 | 完整轮 | 2¹²⁸ + k·2⁶⁴ | Joux 2004 |
| 长度扩展攻击 | SHA-256 | 已知技术可行 | 实际风险 |
长度扩展攻击
SHA-256 作为纯 Merkle-Damgård 结构,天然存在长度扩展攻击:已知 H(m) = h,攻击者可以构造 H(m || pad || m') 而无需知道 m。这会影响某些直接使用哈希构造 MAC 的方案(如 HMAC(secret, msg) 是安全构造,但 H(secret || msg) 不安全)。
SHA-2 vs 国密 SM3 对比
SM3 是中国国家密码管理局发布的密码杂凑算法标准(GM/T 0004-2012),与 SHA-256 同为 256-bit 输出,但结构设计有重要差异。
结构对比
| 维度 | SHA-256 | SM3 |
|---|---|---|
| 迭代结构 | Merkle-Damgård | Merkle-Damgård |
| 字长 | 32 bit | 32 bit |
| 轮数 | 64 轮 | 64 轮 |
| 消息扩展 | 非线性扩展(σ₀, σ₁) | 线性+非线性混合(异或+循环移位) |
| 压缩函数 | 8 变量迭代,Ch/Maj 布尔函数 | 3 变量迭代,异或/与/或混合 |
| 消息块大小 | 512 bit | 512 bit |
| 初始值来源 | 质数平方根 | 固定常数 |
| 标准化 | NIST FIPS 180-4 | GM/T 0004-2012 |
SM3 消息扩展特点
W[0..15] = M 的 16 个字
for j = 16 to 67:
W[j] = P₁(W[j-16] ⊕ W[j-9] ⊕ ROTL³(W[j-3])) ⊕ ROTL¹⁵(W[j-13]) ⊕ W[j-6]
W'[j] = W[j] ⊕ W[j+4] (用于轮函数)SM3 的消息扩展比 SHA-256 更强地混合了非线性(P₁ 置换)和线性(异或+移位)操作。
SM3 压缩函数
ABCDEFGH ← Vᵢ₋₁
for j = 0 to 16:
SS₁ = ROTL¹²((ROTL¹²(A) + E + ROTLʲ(Tⱼ))
SS₂ = SS₁ ⊕ ROTL¹²(A)
TT₁ = FFⱼ(A,B,C) + D + SS₂ + W'[j]
TT₂ = GGⱼ(E,F,G) + H + SS₁ + W[j]
D=C, C=ROTL⁹(B), B=A, A=TT₁
G=F, F=ROTL¹⁹(E), E=P₀(TT₂), H=TT₂
for j = 17 to 63:
(结构相同;区别在于 FFⱼ/GGⱼ 切换为第二组布尔函数)
(FF₀..₁₆ 用 XOR,FF₁₇..₆₃ 用 AND-OR 混合;GG 同理)
Vᵢ = ABCDEFGH ⊕ Vᵢ₋₁关键差异:SM3 使用 64 轮迭代(SHA-256 也是 64 轮),但每轮使用两个独立的变量更新路径(SS 和 TT),增加了并行分析的难度。
安全强度对比
| 安全属性 | SHA-256 | SM3 | 说明 |
|---|---|---|---|
| 抗碰撞 | 2¹²⁸ | 2¹²⁸ | 理论值相同 |
| 抗原像 | 2²⁵⁶ | 2²⁵⁶ | 理论值相同 |
| 实际碰撞攻击 | 无有效攻击 | 无有效攻击 | 2026 年现状 |
| 长度扩展 | 存在 | 存在 | 两者均受 Merkle-Damgård 结构影响 |
工程实现要点
Python 标准库调用
import hashlib
# SHA-256 哈希
data = b"Hello, World!"
hash_256 = hashlib.sha256(data).hexdigest()
print(f"SHA-256: {hash_256}")
# SHA-512 哈希
hash_512 = hashlib.sha512(data).hexdigest()
print(f"SHA-512: {hash_512}")
# 增量哈希(大文件场景)
hasher = hashlib.sha256()
with open("large_file.bin", "rb") as f:
while chunk := f.read(8192):
hasher.update(chunk)
print(f"File hash: {hasher.hexdigest()}")HMAC-SHA256(安全的 MAC 构造)
import hmac
import hashlib
import os
# HMAC 是长度扩展攻击的安全防护
secret_key = os.urandom(32)
message = b"authenticated data"
mac = hmac.new(secret_key, message, hashlib.sha256).digest()
print(f"HMAC-SHA256: {mac.hex()}")标准引用
| 标准编号 | 名称 | 说明 |
|---|---|---|
| NIST FIPS 180-4 | Secure Hash Standard (SHS) | SHA-2 主标准 |
| NIST FIPS 202 | SHA-3 Standard | SHA-3 补充 |
| GM/T 0004-2012 | SM3 密码杂凑算法 | 国密杂凑标准 |
| RFC 6234 | US Secure Hash Algorithms | 实现参考 |
相关实践
- SM4-GCM 认证加密模式原理 — SM4 在 GCM 模式中的应用
- SHA-3 Keccak 海绵结构 — SHA-3 的下一代哈希设计
- 密码学基础概念 — 对称/非对称/哈希的基本框架
- NIST FIPS 180-4: https://csrc.nist.gov/publications/detail/fips/180-4/final
- NIST SP 800-107: https://csrc.nist.gov/publications/detail/sp/800-107/rev-1/final
参考来源:
- NIST FIPS 180-4: Secure Hash Standard (SHS)
- NIST SP 800-107 Rev.1: Recommendation for Using Approved Hash Algorithms
- GM/T 0004-2012: SM3 密码杂凑算法
- RFC 6234: US Secure Hash Algorithms (SHA and SHAHMAC)