SparkMD5是如何计算md5的🤔️

459 阅读9分钟

MD5 消息加密算法是一种广泛使用的散列函数,可产生 128 位散列值。MD5 由罗纳德-里维斯特于 1991 年设计,以取代早期的哈希函数 MD4,并于 1992 年作为 RFC 1321 指定。

MD5 可用作校验和,以验证数据的完整性,防止意外损坏。在历史上,MD5 曾被广泛用作加密哈希函数,但后来发现它存在大量漏洞。它仍然适用于其他非加密用途,例如确定分区数据库中特定密钥的分区,而且由于其计算要求低于最新的安全散列算法,因此可能更受青睐。

MD5 摘要已广泛应用于软件领域,为传输的文件是否完好无损提供了某种保证。例如,文件服务器通常会为文件提供一个预先计算好的 MD5(称为 md5sum)校验和,这样用户就可以将下载文件的校验和与之进行比较。大多数基于 unix 的操作系统都在其发行包中包含了 MD5 和实用程序;Windows 用户可以使用随附的 PowerShell 函数 "Get-FileHash"、安装 Microsoft 实用程序或使用第三方应用程序。安卓 ROM 也使用这种类型的校验和。

image.png


  • 消息分割(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) 

代码来源:en.wikipedia.org/wiki/MD5