MD5 消息加密算法是一种广泛使用的散列函数,可产生 128 位散列值。MD5 由罗纳德-里维斯特于 1991 年设计,以取代早期的哈希函数 MD4,并于 1992 年作为 RFC 1321 指定。
MD5 可用作校验和,以验证数据的完整性,防止意外损坏。在历史上,MD5 曾被广泛用作加密哈希函数,但后来发现它存在大量漏洞。它仍然适用于其他非加密用途,例如确定分区数据库中特定密钥的分区,而且由于其计算要求低于最新的安全散列算法,因此可能更受青睐。
MD5 摘要已广泛应用于软件领域,为传输的文件是否完好无损提供了某种保证。例如,文件服务器通常会为文件提供一个预先计算好的 MD5(称为 md5sum)校验和,这样用户就可以将下载文件的校验和与之进行比较。大多数基于 unix 的操作系统都在其发行包中包含了 MD5 和实用程序;Windows 用户可以使用随附的 PowerShell 函数 "Get-FileHash"、安装 Microsoft 实用程序或使用第三方应用程序。安卓 ROM 也使用这种类型的校验和。
-
消息分割(Padding): 首先,SparkMD5 会将输入数据(文件或文本)划分为一个或多个 512 位的数据块(64字节),并且填充数据块,以确保其长度是 512 位的整数倍。这个过程称为"填充"。
-
初始化 Hash 值: 然后,SparkMD5 初始化四个 32 位的寄存器(A、B、C 和 D)为特定的常量。这些寄存器将被用于存储中间的和最终的 MD5 散列值。
-
主循环(Main Loop): 主要的计算步骤是 MD5 主循环,它在每个数据块上执行。主循环有64轮,每轮的操作都是相似的,但使用不同的位操作和常数。
- 初始化寄存器: 在MD5算法开始计算之前,需要初始化四个32位的寄存器(A、B、C和D)。这些寄存器用于存储中间结果和最终的MD5散列值。
- 数据块处理: MD5将输入数据分为512位(64字节)的块,并在每个块上执行相同的操作。这个块首先被分为16个32位的子块(每个子块4字节)。然后,主循环对这16个子块进行64轮操作。
- 按位运算: 在每轮中,MD5使用不同的位运算操作来混合数据。这些操作包括逻辑与(AND)、逻辑或(OR)、逻辑异或(XOR)等。这些位运算操作在每轮中以不同的顺序和规则执行。
- 位移操作: MD5还执行了位移操作,将32位寄存器的内容向左或向右移动。这些位移操作也在每轮中以不同的规则执行。
- 加法操作: 在每轮中,MD5使用一个特定的常数值,并将其与寄存器中的值相加,以更新寄存器的值。
- 轮循环: 这些操作在主循环中重复64次,每轮都使用不同的常数值和规则。
-
连接 Hash 值: 最后,将四个寄存器的值连接起来,得到 128 位(16 字节)的 MD5 散列。
-
输出散列值: 最终的 MD5 散列值被返回,供用户使用或存储。
伪代码
定义两个数组 s 和 K,s 数组包含64个32位整数,用于表示每轮的位移量。K 数组包含64个32位整数,用作每轮的常数
var int s[64], K[64]
var int i
定义一个名为 s 的数组,其中包含了每轮中的位移量。在 MD5 算法中,每一轮都需要对数据进行一系列的位运算操作,包括左循环位移(左旋)操作。这些位移操作是 MD5 算法的关键组成部分,它们决定了每一轮中数据如何被重排和混合。
s[ 0..15] := { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 }
s[16..31] := { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 }
s[32..47] := { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 }
s[48..63] := { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }
生成 MD5 算法中每一轮使用的常数值,这些常数值在主循环中用于计算中间结果。MD5 算法的主循环总共有64轮,每一轮使用不同的常数值。
代码使用正弦函数 sin(i + 1) 来生成常数值。这里 i 表示循环的迭代次数,从0到63。abs 函数用于获取绝对值,floor 用于将结果向下取整。通过这种方式,生成了64个常数值,每个都是一个32位整数。
for i from 0 to 63 do
K[i] := floor(232 × abs(sin(i + 1)))
end for
预先计算了常数值的表格形式,分为16组,每组包含4个常数值。这些常数值是固定的
K[ 0.. 3] := { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee }
K[ 4.. 7] := { 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 }
K[ 8..11] := { 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be }
K[12..15] := { 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 }
K[16..19] := { 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa }
K[20..23] := { 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 }
K[24..27] := { 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed }
K[28..31] := { 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a }
K[32..35] := { 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c }
K[36..39] := { 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 }
K[40..43] := { 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 }
K[44..47] := { 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 }
K[48..51] := { 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 }
K[52..55] := { 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 }
K[56..59] := { 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 }
K[60..63] := { 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }
这四个变量是32位整数,它们分别代表MD5算法中的四个寄存器 A、B、C 和 D 的初始值。这些寄存器将用于存储中间的和最终的MD5散列值。
这里的寄存器,可以简单视为用于存储中间计算结果的地方,临时存储计算中的数据,随着算法的执行,它们的值会不断更新,最终用于生成 MD5 散列值。
var int a0 := 0x67452301 // A
var int b0 := 0xefcdab89 // B
var int c0 := 0x98badcfe // C
var int d0 := 0x10325476 // D
首先,在消息的末尾添加一个 "1" 位,以指示消息的结束。这是 MD5 算法的一部分,用于标记消息的末尾。
"消息" 指的是要计算 MD5 散列值的输入数据。在 MD5 算法中,这个输入数据通常是一个二进制消息,可以是任意长度的二进制数据等等。
append "1" bit to message<
接下来,根据规定,添加足够的 "0" 位,直到消息的总位数满足模 512 后余数等于 448。这确保了消息的长度满足 MD5 算法的要求。如果消息的长度已经满足这个条件,那么就不需要填充。
append "0" bit until message length in bits ≡ 448 (mod 512)
最后,将原始消息的长度(以位为单位)对 2^64 取模的结果附加到消息的末尾。这一步保留了消息的原始长度信息,以便在计算中使用。
append original length in bits mod 264 to message
这是MD5算法的核心,它包括64轮的操作,每轮都使用不同的位运算规则。在每轮中,根据不同的条件,选择适当的位运算和常数。这些操作包括逻辑与、逻辑或、逻辑异或、位移操作和加法操作。主循环根据不同的轮数,选择不同的位运算规则和常数,然后更新寄存器的值。
for each 512-bit chunk of padded message do
break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
// Initialize hash value for this chunk:
var int A := a0
var int B := b0
var int C := c0
var int D := d0
// Main loop:
for i from 0 to 63 do
var int F, g
if 0 ≤ i ≤ 15 then
F := (B and C) or ((not B) and D)
g := i
else if 16 ≤ i ≤ 31 then
F := (D and B) or ((not D) and C)
g := (5×i + 1) mod 16
else if 32 ≤ i ≤ 47 then
F := B xor C xor D
g := (3×i + 5) mod 16
else if 48 ≤ i ≤ 63 then
F := C xor (B or (not D))
g := (7×i) mod 16
// Be wary of the below definitions of a,b,c,d
F := F + A + K[i] + M[g] // M[g] must be a 32-bit block
A := D
D := C
C := B
B := B + leftrotate(F, s[i])
end for
// Add this chunk's hash to result so far:
a0 := a0 + A
b0 := b0 + B
c0 := c0 + C
d0 := d0 + D
end for
var char digest[16] := a0 append b0 append c0 append d0 // (Output is in little-endian)