SHA-3 (Keccak) 海绵结构中的吸收与挤压过程详解
题目描述
我们今天要讲解的,是SHA-3哈希算法家族的核心——Keccak海绵结构中的关键两步:吸收(Absorbing)和挤压(Squeezing)。SHA-3是美国国家标准与技术研究院(NIST)在2012年选定的新一代密码哈希标准,其安全性建立在海绵结构(Sponge Construction)之上。与之前基于Merkle-Damgård结构的哈希算法(如SHA-256)不同,海绵结构提供了更大的灵活性,不仅可用于哈希,还可用于伪随机数生成、认证加密等。
简单来说,海绵结构就像一个真正的海绵:
- 吸收:将输入的消息“水分”全部吸入海绵内部,填满其内部状态。
- 挤压:然后将海绵“拧干”,按需输出任意长度的结果(如哈希值)。
理解这两个过程,是掌握SHA-3工作原理的关键。我们将一步步拆解这个过程,从最基础的概念开始。
解题过程(原理讲解)
第一步:理解海绵结构的基本参数
在讲解过程前,必须先了解Keccak使用的几个核心参数。Keccak内部有一个状态(State),它是一个三维的比特数组,尺寸为5×5×w,其中w是2的幂(在SHA3-256中,w=64)。但我们更常从速率和容量的角度来看它:
- 状态宽度(b):状态的总比特数。b = 5 * 5 * w。SHA3标准中,b∈{25, 50, 100, 200, 400, 800, 1600}。最常用的是b=1600(对应w=64)。
- 速率(r):在每次“吸收”操作中,与输入消息进行混合的比特数。它控制了数据吞吐的速度。
- 容量(c):状态中不与输入直接混合的比特数。c = b - r。它决定了算法的安全等级,抵抗碰撞和原像攻击的安全强度约为c/2。
例如,对于SHA3-256:
- b = 1600 bits
- r = 1088 bits
- c = 512 bits
这意味着它的安全强度是256位(c/2=256)。
初始状态:在开始处理消息前,一个5×5×w的全零数组被初始化。
第二步:吸收(Absorbing)阶段详解
吸收阶段的目标,是将任意长度的输入消息M,分块填充到海绵的内部状态中。
-
填充(Padding):
- 首先,输入消息M需要进行填充,以确保其总长度是速率r的整数倍。Keccak使用一个简单的填充规则
pad10*1。 - 这个规则是:在消息末尾先添加一个比特
1,然后添加若干个比特0(可能为0个),最后再添加一个比特1。添加的0的个数,要使得填充后的消息总长度是r的整数倍。 - 举例:假设r=8 bits(仅为示例简化),消息M=
0110 001(7 bits)。我们需要填充到8的倍数。填充过程:M || 1 || 000... || 1。计算:0110 001+1=0110 0011,长度已是8,所以不需要添加0,直接在末尾再加最后一个1。最终填充后消息为0110 0011 1,长度为9,不是8的倍数?等等,这里计算有误,我们重新规范一下。 - 规范示例:
pad10*1确保填充后的比特串长度为r的整数倍。填充内容为:1, 然后是0*(任意多个,包括0个),最后是1。更准确的描述是,在消息后添加一个比特1,然后添加最少个数的比特0,使得总长度等于(n * r) - 1,其中n是某个整数,然后再添加一个比特1。这样,最终长度就是n * r。对于上面的例子(M=0110001,7 bits, r=8),我们需要填充到8的倍数(比如8)。7 bits的消息,要变成8 bits,需要加1 bit。添加的这1 bit,按照规则,就是1(它既是开头的1,也是结尾的1,因为没有空间放中间的0了)。所以填充后是0110001 1,长度8 bits,正好是r=8的1倍。
- 首先,输入消息M需要进行填充,以确保其总长度是速率r的整数倍。Keccak使用一个简单的填充规则
-
分块处理:
- 填充后的消息P被分割成若干个长度为r比特的数据块:P = P0, P1, ..., Pk-1。
-
吸收循环:
- 海绵结构初始化时,内部状态S是一个5x5xw的全零数组。
- 对于第一个分块P0:将P0(r bits)与状态S的最前面的r个比特(对应于速率部分)进行按位异或(XOR) 操作。状态的后面c个比特(容量部分)保持不变。
- 应用置换函数f:然后,对整个状态S(b bits)应用Keccak的核心置换函数
f(在b=1600时,称为Keccak-f[1600])。这个函数非常复杂,由12+2l轮(l=log2(w),对于w=64,l=6,共24轮)的θ、ρ、π、χ、ι五个步骤组成,目的是对状态进行充分的混淆和扩散。 - 对于后续分块Pi (i>0):重复上述过程。将下一个分块Pi与经过上一轮置换后的状态的前r个比特进行XOR,然后再对整个状态应用置换函数
f。 - 这个过程一直持续到所有消息分块都被“吸收”进海绵。
简单记忆:吸收 = (XOR一个分块 -> 对整个状态应用f) , 重复直到所有分块处理完。
吸收阶段结束后,海绵的内部状态已经包含了整个输入消息的信息(被充分搅拌混淆)。
第三步:挤压(Squeezing)阶段详解
挤压阶段的目标,是从已经“吸饱”消息的海绵状态中,输出我们最终需要的哈希值。
-
初始化输出:在吸收完最后一个消息分块后,我们得到了一个最终状态S_final。
-
输出循环:
- 首次输出:从当前状态S_final中,直接取出前r个比特(速率部分),作为输出的第一部分Z0。
- 检查输出长度:我们需要的哈希输出长度是d bits(例如SHA3-256中d=256)。如果r >= d,那么Z0的前d个比特就是最终的哈希值,挤压阶段立即结束。
- 需要更多输出:如果d > r(例如在需要很长输出时),则:
- 对当前状态再次应用置换函数
f,得到一个新状态。 - 从这个新状态中,取出前r个比特,作为输出的下一部分Z1。
- 将Z0, Z1, ... 连接起来,直到总长度至少为d bits。
- 对当前状态再次应用置换函数
- 截断:最后,将所有输出的比特块连接后,截取前d个比特,作为最终的哈希值。
简单记忆:挤压 = 读取前r位作为输出 -> 如果需要更多,就应用f再读取前r位 -> 重复 -> 最后截断到所需长度d。
总结与图示
整个海绵结构的过程可以用一个清晰的流程图表示:
输入消息M
|
V
填充:M -> P (P = M || pad10*1,长度为 r 的整数倍)
|
V
分割:P = P0 || P1 || ... || Pk-1
|
V
初始化状态 S = 0^b
|
V
[吸收阶段开始]
对于 i 从 0 到 k-1:
S = S ⊕ (Pi || 0^c) // 将Pi与S的前r位XOR,c位容量部分与0 XOR(即不变)
S = f(S) // 对完整的b位状态应用置换函数
[吸收阶段结束]
|
V
[挤压阶段开始]
Z = 空字符串
while (输出Z的长度 < d):
Z = Z || (S的前r个比特) // 从当前状态读取r位
S = f(S) // 应用置换函数获取下一个状态
[挤压阶段结束]
|
V
最终哈希值 = Z 的前 d 个比特
核心要点:
- 吸收是“吃进去并搅拌”,通过XOR混合消息,通过
f函数扩散混淆。 - 挤压是“挤出来”,直接读取状态的一部分作为输出,如果不够就再“拧”(应用
f)一下,再读。 - 容量c是关键:它从未被直接输出,像是一个秘密的“内存”,保证了攻击者无法轻易地反向计算或构造碰撞。这是海绵结构安全性的基石。
通过以上步骤,SHA-3海绵结构就能够以简洁而安全的方式,将任意长度的输入,变换为固定长度的哈希输出。这种结构的美妙之处在于其统一性和灵活性,速率r和容量c的参数化使其能适配不同的安全和性能需求。