SHA-256 哈希算法的轮函数设计详解
算法题目描述
SHA-256 是美国国家安全局(NSA)设计的 SHA-2 系列哈希算法之一,其核心是一个迭代压缩函数,而这个压缩函数的核心是轮函数。轮函数是 SHA-256 中进行 64 轮运算的基本单元,每一轮都会处理消息扩展得到的一个 32 位字 \(W_t\) 和一个固定的常数 \(K_t\),并对中间状态(8 个 32 位寄存器 a, b, c, d, e, f, g, h)进行非线性混淆。本题将深入解析 SHA-256 轮函数的结构,包括其中涉及的多个非线性逻辑运算、位运算以及每一步的具体计算顺序和设计原理。
解题过程(讲解)
第一步:回顾 SHA-256 的总体框架
SHA-256 输入一个不超过 \(2^{64}\) 比特的消息,通过填充、分块(每个消息块 512 比特)后,依次处理每个消息块。对于每个消息块,执行以下步骤:
- 初始化:将 8 个 32 位的寄存器(a, b, c, d, e, f, g, h)设置为固定的初始化向量(IV)或前一个消息块的输出。
- 消息扩展:将 512 位的消息块扩展为 64 个 32 位的字 \(W_0, W_1, …, W_{63}\)。
- 64 轮压缩:对于 \(t = 0\) 到 \(63\),执行轮函数。每轮使用 \(W_t\) 和固定常量 \(K_t\) 更新寄存器。
- 结果输出:将该消息块处理后的寄存器值(a, b, …, h)与初始值相加,作为下一个消息块的输入。最终最后一个消息块处理完成后的寄存器值串联起来,就是 256 位的哈希值。
轮函数是第 3 步“64 轮压缩”的核心。
第二步:轮函数的输入与符号定义
在每一轮 \(t\) 开始时,有:
- 中间状态:8 个 32 位寄存器,记作 \(a, b, c, d, e, f, g, h\)。
- 外部输入:
- \(W_t\):第 \(t\) 轮的消息扩展字(32 位)。
- \(K_t\):第 \(t\) 轮的固定常量(32 位),取自前 64 个素数立方根的小数部分前 32 位。
在描述轮函数时,我们使用以下符号表示位运算:
- \(\oplus\):按位异或(XOR)。
- \(\land\):按位与(AND)。
- \(\lor\):按位或(OR)。
- \(\neg\):按位取反(NOT)。
- \(\gg\):逻辑右移(高位补 0)。
- \(\ggg\):循环右移(或称“右旋转”)。
第三步:轮函数内部的辅助函数
轮函数的非线性特性主要由两个“选择”函数和一个“多数”函数,以及两个求和函数来体现。这些函数对 32 位字 \(x\) 进行操作。
- 选择函数 \(\text{Ch}(x, y, z)\)
\[ \text{Ch}(x, y, z) = (x \land y) \oplus (\neg x \land z) \]
* **功能**:如果 $ x $ 的某一位是 1,则输出 $ y $ 的对应位;如果是 0,则输出 $ z $ 的对应位。就像一个多路选择器,由 $ x $ 控制选择 $ y $ 或 $ z $。
* **作用**:引入非线性,其输出依赖于 $ x $ 的每一位。
- 多数函数 \(\text{Maj}(x, y, z)\)
\[ \text{Maj}(x, y, z) = (x \land y) \oplus (x \land z) \oplus (y \land z) \]
* **功能**:对 $ x, y, z $ 的每一位,输出该位上占多数的值(即,如果有两个或三个 1,则输出 1;否则输出 0)。
* **作用**:增加扩散和混淆,使得多个寄存器的值能相互影响。
- 求和函数 \(\Sigma_0(x)\) 和 \(\Sigma_1(x)\)
\[ \Sigma_0(x) = (x \ \ggg\ 2) \oplus (x\ \ggg\ 13) \oplus (x\ \ggg\ 22) \]
\[ \Sigma_1(x) = (x\ \ggg\ 6) \oplus (x\ \ggg\ 11) \oplus (x\ \ggg\ 25) \]
* **功能**:通过对输入 $ x $ 进行特定距离的循环右移后异或,实现对输入比特的充分扩散。这些特定的偏移量(2,13,22 和 6,11,25)是精心选择的,以最大化比特的重新排列和扩散效果。
* **作用**:在不使用 S 盒的情况下,提供类似置换的效果,增强算法的雪崩效应。
第四步:轮函数的详细计算步骤
假设进入第 \(t\) 轮时,寄存器的值为 \(a, b, c, d, e, f, g, h\)。一轮计算后,新值记为 \(a', b', c', d', e', f', g', h'\)。计算过程如下:
- 计算中间变量 \(T_1\) 和 \(T_2\)
这是轮函数的“心脏”,将当前状态、消息字、常数以及上述辅助函数结合起来。
\[ T_1 = h + \text{Ch}(e, f, g) + \Sigma_1(e) + W_t + K_t \]
\[ T_2 = \Sigma_0(a) + \text{Maj}(a, b, c) \]
* 注意:这里的“+”是模 $ 2^{32} $ 加法。它混合了非线性函数($\text{Ch}$ 和 $\text{Maj}$)、扩散函数($\Sigma_0$ 和 $\Sigma_1$)、消息数据($W_t$)和固定常量($K_t$)。
* $ T_1 $ 的计算包含了 $ e, f, g, h, W_t, K_t $,反映了状态的后半部分和外部输入。
* $ T_2 $ 的计算只涉及状态的前半部分 $ a, b, c $。
- 更新寄存器 \(h\) 到 \(d\)
寄存器被“向下”平移,其中 \(d\) 的新值被替换。
\[ h' = g \]
\[ g' = f \]
\[ f' = e \]
\[ e' = d + T_1 \]
* 注意:$ e' $ 是 $ d $ 和 $ T_1 $ 的模 $ 2^{32} $ 和。这里,$ d $ 参与了 $ e' $ 的更新,实现了从状态前半部分到后半部分的“馈入”。
- 更新寄存器 \(c\) 到 \(a\)
继续平移,并计算新的 \(a\) 值。
\[ d' = c \]
\[ c' = b \]
\[ b' = a \]
\[ a' = T_1 + T_2 \]
* 注意:新的最高位寄存器 $ a' $ 是 $ T_1 $ 和 $ T_2 $ 的模 $ 2^{32} $ 和。这结合了前半部分和后半部分计算的结果。
一轮之后,新的寄存器状态为 \((a', b', c', d', e', f', g', h')\)。
第五步:轮函数的设计逻辑与安全考量
- 非线性性:由 \(\text{Ch}\) 和 \(\text{Maj}\) 函数提供。它们是比特级的非线性布尔函数,能抵抗线性密码分析。
- 扩散性:由 \(\Sigma_0\) 和 \(\Sigma_1\) 函数以及模 \(2^{32}\) 加法提供。循环右移和异或操作能将单个输入比特的影响扩散到多个输出比特。模加法也提供了良好的比特扩散。
- 数据依赖性:每轮的 \(W_t\) 不同,且来自原始消息,这确保了哈希输出对输入消息的高度敏感。
- 常量注入:每轮加入不同的 \(K_t\),可以消除输入消息块结构可能导致的内部对称性或弱性,并防止某些类型的攻击(如固定点攻击)。
- 流水线结构:寄存器更新的方式(移位和加法)类似于一个线性反馈移位寄存器(LFSR)的推广,但加入了强烈的非线性。这种结构易于硬件实现,并确保状态在每一轮都得到彻底搅动。
第六步:总结
SHA-256 的轮函数是一个精心设计的非线性状态更新函数。它通过 \(\text{Ch}\) 和 \(\text{Maj}\) 实现非线性混淆,通过 \(\Sigma_0\) 和 \(\Sigma_1\) 实现比特扩散,通过模加法和与 \(W_t\)、\(K_t\) 的混合实现数据依赖和抗对称性。经过 64 轮这样的迭代,即使输入消息有微小差异,也会导致最终寄存器的状态(即哈希值)产生巨大、不可预测的变化,从而满足密码学哈希函数的要求:原像抗性、第二原像抗性和抗碰撞性。