SHA-3 (Keccak) 海绵结构中的 \(\iota\) (Iota) 步骤详解
1. 题目描述
我们将深入讲解 SHA-3 哈希算法(及其底层结构 Keccak)中核心的轮函数 Keccak-f[b] 的五个步骤之一:\(\iota\) 步骤。在 Keccak-f[b] 轮函数中,状态数组需要经过 R 轮迭代(对于 SHA3-256,b=1600 位,R=24轮),每一轮由五个步骤按顺序构成:\(\theta\) (Theta), \(\rho\) (Rho), \(\pi\) (Pi), \(\chi\) (Chi), \(\iota\) (Iota)。\(\iota\) 是最后一小步,其作用是将一个与轮次相关的、非常稀疏的、预先计算好的常量(称为“轮常数”, round constant)加到状态数组的特定位置(一个特定的“车道”, lane)。这一步的目的是破坏结构的对称性,确保每一轮变换都是唯一的,从而增强算法的抗碰撞和抗密码分析能力。我们的目标是理解 \(\iota\) 步骤的具体操作、其设计动机、常量的生成规则及其在整体算法中的作用。
2. 解题过程:循序渐进的理解
第一步:回忆 Keccak 的状态表示
Keccak 的内部状态 \(b\) 表示为一个三维比特数组 \(a[x][y][z]\),但我们更常用一个 5x5 的二维“车道”(lane)视角来理解 \(\iota\) 步骤。状态 \(S\) 可以看作是 5x5 个“车道”的集合,每个“车道”是一个长度为 \(w\) 比特的字,其中 \(b = 25 * w\)。例如,在 SHA3-256 中,\(b=1600, w=64\),所以状态是 5x5 个 64 比特的字。
- 坐标:\(x\) 和 \(y\) 的取值范围是 0 到 4,定位一个车道(\(lane[x][y]\))。
- 注意:在一些描述中,状态也被表示为 \(A[x, y]\) 或 \(S[x][y]\),代表一个 \(w\) 比特的车道。
第二步:\(\iota\) 步骤的数学描述
\(\iota\) 步骤的数学定义非常简单,只修改一个特定车道的一个比特。其操作如下:
\[A[0, 0] \leftarrow A[0, 0] \oplus RC[i_r] \]
其中:
- \(A[0, 0]\) 是状态数组在坐标 \((x=0, y=0)\) 处的车道(一个 \(w\) 比特的字)。
- \(i_r\) 是当前的轮次索引,从 0 开始计数(对于 SHA3-256,\(i_r \in {0, 1, …, 23}\))。
- \(RC[i_r]\) 是与轮次 \(i_r\) 对应的、长度为 \(w\) 比特的轮常数。注意,\(RC[i_r]\) 中,只有很少的比特位(通常只有 6 或 7 个位)被设为 1,其他位都是 0。它不是一个完整的、复杂的 \(w\) 比特常量,而是一个极其稀疏的常量。
- \(\oplus\) 表示按位异或(XOR)操作。
关键点:
- \(\iota\) 步骤只修改一个车道:\(A[0, 0]\)。
- 修改方式是与一个轮常数进行按位异或。
- 这是整个轮函数中唯一与轮次相关的操作,\(\theta\), \(\rho\), \(\pi\), \(\chi\) 都是与轮次无关的、固定的置换或线性/非线性操作。这使得 \(\iota\) 步骤成为破坏迭代对称性的关键。
第三步:轮常数 \(RC[i_r]\) 的生成
轮常数 \(RC\) 的设计目标是在保证足够“随机”和不可预测的同时,又非常简洁、易于硬件实现。它不是从大的查找表中读取,而是通过一个简单的线性反馈移位寄存器(LFSR)逻辑在算法内部即时生成的。
轮常数 \(RC\) 是一个与状态宽度 \(w\) 相关的 \(w\) 比特值。但其非零比特只出现在最低的 \(l\) 位,其中 \(l = \log_2(w)\)。对于 \(b=1600, w=64\),则 \(l=6\)。所以 \(RC[i_r]\) 的非零比特只会出现在其最低 6 个比特位中。对于小于 64 比特的 \(w\),常数会被截断到相应的 \(w\) 比特。
生成算法:
- 设 \(RC[j]\) 表示轮常数的第 \(j\) 个比特(\(j\) 从 0 开始)。
- 对于每一轮 \(i_r\),我们生成一个新的 \(w\) 比特的 \(RC\) 值,但只计算其最低的 \(l\) 位(因为高位总是 0)。
- 设 \(rc(t)\) 是一个生成 8 位值的简单函数,其中 \(t\) 是一个整数,用于计算每一轮的轮常数。
- 对于每一轮 \(i_r\),我们计算 \(t = i_r\),但生成规则基于一个 8 位的 LFSR。
- 具体的计算规则如下(Keccak 规范):
a. 初始化一个 8 位寄存器 \(rc = 0x01\)(即二进制00000001)。
b. 对于 \(j\) 从 0 到 \(l-1\)(即 \(j\) 从 0 到 5 对于 \(w=64\)):
如果 \(rc\) 的第 0 位(最低有效位)是 1,则 \(RC[j] = 1\),否则 \(RC[j] = 0\)。
c. 更新 \(rc\) 寄存器:将 \(rc\) 左移 1 位,如果移出的最高位是 1,则与 0x71 进行异或。这是一种在 GF(2^8) 上基于本原多项式 \(x^8 + x^6 + x^5 + x^4 + 1\) 的线性反馈。
d. 重复 b 和 c 步骤 \(i_r\) 轮(即,对于第 0 轮,我们直接取初始 \(rc\) 生成的 6 个比特;对于第 1 轮,我们更新 \(rc\) 一次,再用新 \(rc\) 生成 6 个比特,以此类推)。
简化理解:我们可以将每一轮 \(i_r\) 对应的 \(RC[i_r]\) 看作是一个预先计算好的 64 比特常量,但其值非常小。例如,前几轮的 \(RC\) 值(以十六进制表示,最低 6 位有效):
- 轮 0: RC = 0x0000000000000001
- 轮 1: RC = 0x0000000000008082
- 轮 2: RC = 0x800000000000808A
- ...
注意,这些常量是公开的,可以在标准中找到(NIST FIPS 202)。在实际实现中,为了效率,通常会直接查表使用这 24 个常量,而不是每次计算 LFSR。
第四步:\(\iota\) 步骤的作用与安全目的
- 破坏对称性:Keccak 的 \(\theta\), \(\rho\), \(\pi\), \(\chi\) 步骤都是对称的、与轮次无关的。如果没有 \(\iota\) 步骤,那么无论进行多少轮,每一轮的变换都是一模一样的。这种对称性可能导致弱密钥或固定点攻击。通过每一轮异或一个不同的常数 \(RC[i_r]\) 到 \(A[0,0]\) 车道,我们确保了每一轮的变换都是独特的,从而打破了这种对称性。
- 提供非线性:虽然异或本身是线性操作,但由于 \(RC\) 是常数,它与状态异或可以看作是向系统注入了一个“扰动”。这个扰动经过后续的 \(\chi\)(非线性)和 \(\theta\)(线性扩散)等步骤,会扩散到整个状态,增加了状态演变的复杂性和不可预测性。
- 简化设计:\(\iota\) 步骤的设计非常经济和优雅。它只修改一个车道的一个比特位(实际上常数中只有几个位是1,所以只修改这几个位),计算开销极小,但对安全性的贡献巨大。它避免了使用复杂的轮密钥调度算法(如 AES),使得 Keccak 结构非常简洁,易于分析和硬件实现。
第五步:整体流程中的位置
回顾一下 Keccak-f[b] 一轮的完整顺序:
- \(\theta\) (Theta): 基于列奇偶性进行扩散。
- \(\rho\) (Rho): 对每个车道进行循环移位。
- \(\pi\) (Pi): 重新排列车道的位置(一个固定置换)。
- \(\chi\) (Chi): 非线性变换,基于相邻比特。
- \(\iota\) (Iota): 与轮常数异或。
\(\iota\) 步骤是每一轮的最后一步。完成 \(\iota\) 后,该轮结束,状态进入下一轮(重复上述 5 步)或作为最终的挤压输出。
总结:
\(\iota\) 步骤是 Keccak 轮函数中一个简单但至关重要的步骤。它通过将每一轮唯一的、稀疏的轮常数 \(RC[i_r]\) 与状态的一个特定车道 \(A[0,0]\) 进行异或,破坏轮函数的对称性,为算法提供了必要的非线性扰动和轮间差异性。其常数生成基于一个简单的 LFSR,易于实现。虽然操作本身很简单,但它是确保 SHA-3 哈希函数安全性的关键设计之一,防止了因结构对称性可能导致的密码学弱点。