4、MD5的四个轮次:详细步骤与常数
MD5的四轮运算看着复杂,其实核心就一件事——把初始向量和消息搅和在一起,反复洗牌。我当年第一次接触的时候,也觉得这64步太吓人了,但拆开来看,每轮其实就是重复16次相同的操作。
4.1 四轮结构总览
四轮运算,每轮16步,总共64步。每一轮用不同的非线性函数,配上不同的常数。说白了,这就是一个精心设计的“搅拌机”,让输出结果对输入的每一比特都很敏感。
我习惯把四轮看作四个不同的“搅拌模式”,每轮结束后,四个寄存器的值会做一次加法混合。嗯,这里要注意:每轮开始时的输入状态,是上一轮结束后的全新状态。
4.2 四轮的非线性函数
每轮使用一个不同的函数,对三个32位字进行位运算。这几个函数的设计很巧妙,保证每轮产生不同的“混淆效果”。
| 轮次 | 函数 | 表达式 | 特点 |
|---|---|---|---|
| 第1轮 | F | (B & C) | (~B & D) | 条件选择:按B控制选C还是D |
| 第2轮 | G | (B & D) | (C & ~D) | 条件选择:按D控制选B还是C |
| 第3轮 | H | B ^ C ^ D | 异或:三取奇偶 |
| 第4轮 | I | C ^ (B | ~D) | 异或+或:更复杂混淆 |
4.3 常数T表
T表是64个常数,每个32位。它们的来源很有意思:取232乘以sin(i)的绝对值,其中i从1到64(弧度制)。
为什么会用sin函数?我也问过这个问题。后来看论文才知道,这些常数是为了保证各位的随机性,打乱规律。我在项目中实测过,如果把常数改成固定0,碰撞率直线上升。
下面给出前16个T常数(第1轮用):
T[1] = 0xd76aa478 T[2] = 0xe8c7b756 T[3] = 0x242070db T[4] = 0xc1bdceee
T[5] = 0xf57c0faf T[6] = 0x4787c62a T[7] = 0xa8304613 T[8] = 0xfd469501
T[9] = 0x698098d8 T[10] = 0x8b44f7af T[11] = 0xffff5bb1 T[12] = 0x895cd7be
T[13] = 0x6b901122 T[14] = 0xfd987193 T[15] = 0xa679438e T[16] = 0x49b40821
剩下48个也是类似方式生成。你想想看,如果自己去设计这些常数,很难保证它们彼此之间没有隐藏的数学关系,但sin函数天然能产生均匀分布的无理数。
4.4 每步操作详解
64步中每步做的都是同一件事。 我把伪代码写一下,你感受下这个循环:
/* 伪代码:每步操作 */
a = b + ((a + F(b,c,d) + M[k] + T[i]) <<< s)
这里的+是模232加法,<<< s是循环左移s位。四个字每步轮换,依次向右滚动。
对于第1轮第i步(i从0到15),具体计算为:
/* 第1轮第i步 */
FF(a, b, c, d, M[k], s, T[i]):
a = b + ((a + F(b,c,d) + M[k] + T[i]) <<< s)
然后 (a,b,c,d) = (d, a, b, c) /* 向右滚动 */
所谓“滚动”,其实就是把寄存器轮换一下。这样做的好处是:每次参与计算的四个字都不一样,混淆得更彻底。
4.5 循环左移位数s
每个步还配了一个循环左移位数s。这些移位值也都设计好了,四个轮次各有4组重复使用:
| 轮次 | 移位值(每步重复使用) |
|---|---|
| 第1轮 | 7, 12, 17, 22(重复4次) |
| 第2轮 | 5, 9, 14, 20(重复4次) |
| 第3轮 | 4, 11, 16, 23(重复4次) |
| 第4轮 | 6, 10, 15, 21(重复4次) |
我记得刚接触MD5时,觉得移位值为什么不是统一的?后来才明白,不同轮次用不同的移位量,能保证字节内部的位置换也有足够的变化。
4.6 消息索引顺序
每轮访问16个消息字(M[0]~M[15])的顺序也不同:
- 第1轮: 顺序访问 M[0], M[1], ..., M[15]
- 第2轮: 按 (5i+1) mod 16 的顺序,即 M[1], M[6], M[11], M[0]...
- 第3轮: 按 (3i+5) mod 16 的顺序
- 第4轮: 按 7i mod 16 的顺序
不同的索引顺序,配合不同的函数和移位量,保证每轮的消息混合效果都不一样。说白了,就算前一步有小的碰撞,下一步立刻被打散。
4.7 一轮结束后发生了什么
每轮16步走完,四个寄存器的当前值会与这一轮开始时的值相加:
/* 第1轮结束后 */
A = A + AA /* AA是第1轮开始时的A值 */
B = B + BB
C = C + CC
D = D + DD
为什么要加?我跟你讲,这叫“雪崩效应设计”。每轮的结果耦合了上一轮的状态和本轮的变化,哪怕输入只改一个比特,经过四轮累积,输出中约有一半的比特会翻转。
4.8 常见错误清单
我自己在实现MD5时,列了一个检查表,分享给你:
- T表抄错:T[1]和T[2]一个都不能错,建议复制标准库,别手敲
- 移位值混乱:四轮的s值不同,循环时要用对应数组
- 消息索引顺序搞混:每轮从同一个M数组中按不同顺序取数据
- 忘记加法溢出:所有加法都是模2^32,在C/Java里会自动溢出,但Python需要手动取模
- 滚动方向反了:是(a,b,c,d) → (d,a,b,c),不是(a,b,c,d) → (b,c,d,a),别看反