SHA-256 算法详解
SHA-256 (Secure Hash Algorithm 256) 是 SHA-2 (Secure Hash Algorithm 2) 系列中最常用和最广为人知的加密散列函数之一。它由美国国家标准与技术研究院 (NIST) 于 2001 年发布,作为 MD5 和 SHA-1 的替代品,旨在提供更高的安全强度。SHA-256 能够将任意长度的输入数据(消息)通过哈希运算转换成一个固定长度的 256 位(32 字节)散列值,通常以 64 位十六进制字符串表示。它广泛应用于数字签名、数据完整性校验、密码存储以及区块链技术等领域,是目前主流且被认为安全的哈希算法。
一、加密哈希函数的基本特性
在深入 SHA-256 之前,理解一个安全的加密哈希函数应具备的关键特性至关重要:
- 确定性 (Deterministic):相同的输入消息总是产生相同的哈希值。
- 计算效率 (Computational Efficiency):对于任意输入消息,计算其哈希值是高效的。
- 抗原像性 / 单向性 (Preimage Resistance / One-Way):给定一个哈希值,从计算上不可能找到原始输入消息。
- 抗第二原像性 (Second Preimage Resistance):给定一个输入消息和它的哈希值,从计算上不可能找到另一个不同的输入消息,使其产生相同的哈希值。
- 抗碰撞性 (Collision Resistance):从计算上不可能找到任意两个不同的输入消息,使其产生相同的哈希值。
SHA-256 被设计为满足上述所有特性,尤其是在抗碰撞性方面远超 MD5 和 SHA-1。
二、SHA-256 算法原理
SHA-256 算法基于 Merkle-Damgård 结构,它将输入消息分割成固定大小的块,并对每个块进行迭代处理。整个过程可以概括为以下几个主要步骤:
2.1 填充 (Padding)
首先,原始消息需要进行填充,使其长度(以位为单位)在模 512 之后余 448。也就是说,填充后的消息长度应为 $512n - 64$ 位,其中 $n$ 是某个正整数。
填充过程如下:
- 在消息末尾添加一个
1位。 - 接着添加尽可能多的
0位,直到消息的长度满足 $length \equiv 448 \pmod{512}$。- 即使消息长度已经满足此条件,也需要进行一轮完整的填充(添加一个
1和 511 个0)。
- 即使消息长度已经满足此条件,也需要进行一轮完整的填充(添加一个
2.2 附加长度 (Appending Length)
在填充完毕的消息之后,附加 64 位的原始消息长度(以位为单位)。这个 64 位长度值以大端序 (big-endian) 形式附加。
经过填充和附加长度后,消息的总长度将是 512 位的整数倍。这些 512 位的块将是算法处理的基本单位。
2.3 初始化哈希值 (Initialize Hash Values - IVs)
SHA-256 使用 8 个 32 位的初始哈希值 (Initial Hash Values, IVs) $H_0$ 到 $H_7$。这些值是根据前 8 个素数(2, 3, 5, 7, 11, 13, 17, 19)的平方根的小数部分取前 32 位得到的。
这些常量以十六进制表示为:
- $H_0 = \text{0x6a09e667}$
- $H_1 = \text{0xbb67ae85}$
- $H_2 = \text{0x3c6ef372}$
- $H_3 = \text{0xa54ff53a}$
- $H_4 = \text{0x510e527f}$
- $H_5 = \text{0x9b05688c}$
- $H_6 = \text{0x1f83d9ab}$
- $H_7 = \text{0x5be0cd19}$
2.4 消息处理循环 (Message Processing Loop)
将经过填充和附加长度处理后的消息划分为 N 个 512 位的消息块 $M^{(1)}, M^{(2)}, \dots, M^{(N)}$。算法依次处理每个块。
对于每个 512 位的消息块 $M^{(i)}$,都通过一个压缩函数 (Compression Function) 进行处理。压缩函数接收当前的 512 位消息块和前一个块产生的 256 位哈希值(即当前的 $H_0, \dots, H_7$),并输出一个新的 256 位哈希值。
压缩函数的核心步骤如下:
2.4.1 消息扩展 (Message Schedule)
将输入的 512 位消息块 $M^{(i)}$ 分解成 16 个 32 位的字 $W_0, W_1, \dots, W_{15}$。然后,将这 16 个字扩展成 64 个 32 位的字 $W_0, W_1, \dots, W_{63}$。
对于 $t$ 从 16 到 63:
$$
W_t = \sigma_1(W_{t-2}) + W_{t-7} + \sigma_0(W_{t-15}) + W_{t-16}
$$
其中,+ 表示模 $2^{32}$ 的加法。
两个辅助函数 $\sigma_0$ 和 $\sigma_1$ 定义为:
$$
\sigma_0(x) = \text{ROTR}^7(x) \oplus \text{ROTR}^{18}(x) \oplus \text{SHR}^3(x) \
\sigma_1(x) = \text{ROTR}^{17}(x) \oplus \text{ROTR}^{19}(x) \oplus \text{SHR}^{10}(x)
$$
ROTR^n(x):表示将 32 位字 $x$ 循环右移 $n$ 位。SHR^n(x):表示将 32 位字 $x$ 逻辑右移 $n$ 位。$\oplus$:表示按位异或。
2.4.2 压缩函数主循环 (64 轮)
每个消息块的处理包含 64 轮迭代。每轮迭代都会更新 8 个 32 位的工作变量 (working variables) $a, b, c, d, e, f, g, h$。
初始时,这些工作变量被赋值为当前的哈希值:$a=H_0, b=H_1, \dots, h=H_7$。
在每一轮 $t$ (从 0 到 63) 中,执行以下操作:
- 轮常数 ($K_t$):SHA-256 定义了 64 个 32 位常数 $K_0, K_1, \dots, K_{63}$。这些常数是根据前 64 个素数的立方根的小数部分取前 32 位得到的。
- 逻辑函数:
- 选择函数 (Ch): $Ch(x, y, z) = (x \land y) \oplus (\neg x \land z)$
- 多数函数 (Maj): $Maj(x, y, z) = (x \land y) \oplus (x \land z) \oplus (y \land z)$
- 大写Sigma函数 ($\Sigma_0, \Sigma_1$):
- $\Sigma_0(x) = \text{ROTR}^2(x) \oplus \text{ROTR}^{13}(x) \oplus \text{ROTR}^{22}(x)$
- $\Sigma_1(x) = \text{ROTR}^6(x) \oplus \text{ROTR}^{11}(x) \oplus \text{ROTR}^{25}(x)$
- 计算两个临时变量
temp1和temp2:
$$
temp1 = h + \Sigma_1(e) + Ch(e,f,g) + K_t + W_t \
temp2 = \Sigma_0(a) + Maj(a,b,c)
$$
所有加法都是模 $2^{32}$ 的加法。 - 更新工作变量:
$$
h = g \
g = f \
f = e \
e = d + temp1 \
d = c \
c = b \
b = a \
a = temp1 + temp2
$$
这个循环重复 64 次。
SHA-256 压缩函数单轮示意图 (简化):
graph TD
subgraph Inputs for Round t
A(a) -- Current A --> A_Sigma0("Σ0(a)")
B(b)
C(c)
D(d)
E(e) -- Current E --> E_Sigma1("Σ1(e)")
F(f)
G(g)
H(h)
Kt[Kt]
Wt[Wt]
end
A_Sigma0 --> Maj["Maj(a,b,c)"]
B --> Maj
C --> Maj
E_Sigma1 --> Ch["Ch(e,f,g)"]
F --> Ch
G --> Ch
temp1_calc("h + Σ1(e) + Ch(e,f,g) + Kt + Wt")
temp2_calc("Σ0(a) + Maj(a,b,c)")
H --> temp1_calc
E_Sigma1 --> temp1_calc
Ch --> temp1_calc
Kt --> temp1_calc
Wt --> temp1_calc
A_Sigma0 --> temp2_calc
Maj --> temp2_calc
temp1_calc --> next_e[e = d + temp1]
temp1_calc --> next_a[a = temp1 + temp2]
temp2_calc --> next_a
D --> next_e
A --> next_b[b = a]
B --> next_c[c = b]
C --> next_d[d = c]
E --> next_f[f = e]
F --> next_g[g = f]
G --> next_h[h = g]
next_a --> OutputA[New a]
next_b --> OutputB[New b]
next_c --> OutputC[New c]
next_d --> OutputD[New d]
next_e --> OutputE[New e]
next_f --> OutputF[New f]
next_g --> OutputG[New g]
next_h --> OutputH[New h]
OutputA & OutputB & OutputC & OutputD & OutputE & OutputF & OutputG & OutputH --> Z{Update A-H for next round}
2.4.3 更新哈希值
在对当前 512 位消息块的 64 轮处理完成后,将初始的工作变量 $a, b, \dots, h$ (即本轮开始时的 $H_0, \dots, H_7$) 与本轮结束后的工作变量进行模 $2^{32}$ 的加法,更新哈希值:
$$
H_0 = H_0 + a \
H_1 = H_1 + b \
\dots \
H_7 = H_7 + h
$$
这个更新后的 256 位哈希值将作为下一个消息块处理的输入。
2.5 输出 (Output)
当所有 512 位的消息块都处理完毕后,最终的 8 个 32 位哈希值 $H_0, H_1, \dots, H_7$ 按大端序连接起来,就构成了最终的 256 位(32 字节)SHA-256 消息摘要。
三、SHA-256 的安全性与应用
- 安全性:SHA-256 被认为是安全的加密哈希函数,目前尚未发现任何实际可行的碰撞攻击或原像攻击。其设计复杂度、大哈希值长度和位操作的结合,使其在计算上抵抗各种已知密码分析攻击。
- 应用:
- 数字签名:广泛用于 TLS/SSL 证书、代码签名、文件签名,确保数据来源的真实性和完整性。
- 密码存储:用于存储用户密码的哈希值。为了增强安全性,通常会结合盐值 (Salt) 和密钥派生函数 (KDF),如 PBKDF2、scrypt 或 Argon2,来抵御彩虹表攻击和暴力破解。
- 数据完整性校验:通过比较文件或数据的 SHA-256 哈希值来验证其在传输或存储过程中是否被篡改。
- 区块链和加密货币:比特币的核心工作量证明 (Proof of Work) 机制就严重依赖 SHA-256 算法。
- 伪随机数生成器:可以作为构建安全伪随机数生成器的组件。
四、Go 语言实现示例
Go 语言的 crypto/sha256 包提供了 SHA-256 算法的标准实现。
1 | package main |
五、总结
SHA-256 算法以其 256 位的固定输出长度、强大的抗碰撞性和抗原像性,成为了现代密码学中不可或缺的工具。尽管其内部结构比 MD5 或 SHA-1 更为复杂,但这些复杂性正是其安全性的保证。在需要数据完整性验证、数字签名、密码存储以及区块链等对安全性要求极高的应用中,SHA-256 是一个可靠且广泛推荐的选择。理解其基本原理和正确使用方式,对于构建安全的数字系统至关重要。
