背景:网上看了几篇关于MD5加密算法的文章,有些地方不太明白,就去看了维基百科上的英文介绍,逻辑很清晰,所以整理出来。
1、 简介
MD5即Message-Digest Algorithm 5,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经MD2、MD3和MD4发展而来。
MD5将任意长度的“字节串”变换成一个128bit的消息摘要,并且它是一个不可逆的字符串变换算法,典型应用是对一段信息串 (Message)产生所谓的指纹 (fingerprint),以防止被“篡改”。
2、MD5算法逻辑
(1)、消息补位:
对于任意长度的消息,首先需要对消息添加位数,使消息总长度为512位的整数倍。在消息后添加位的方法是第一个添加位是1,其余都是0,使消息长度对512取模得448。然后将真正消息的长度(没有添加位以前的消息长度)对264取模后以64位表示,附加于前面已添加过位的消息后,此时的消息长度正好是512位的倍数。消息补位是必须要有的操作。
(2)、消息分块:
经过添加位数处理的消息,其长度正好为512位的整数倍,然后按512位的长度进行分块,可以划分成m块消息,我们用x1,x2,…,xm表示这些块消息。
对于512位的消息块,将其再分成16个字符串,每个字符串为32位(用十六进制表示),我们使用M[k](k= 0, 1,……15)来表示这16个字符串。
(3)、初始化变量:
初始化变量a、b、c、d对应的十六进制表示为:
a = 0x67452301
b = 0xefcdab89
c = 0x98badcfe
d = 0x10325476
(4)、需要用到的常量(十六进制)
K[i]为232 × abs(sin(i + 1)) 的整数部分,其中i的单位是弧度。
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 }
另外还需要常量s[i]:
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 }
(5)、需要使用的函数:
⊕,∧,∨,¬分别代表异或,与,或,非运算。
(6)、循环加密过程:
对于每一个消息块都有4轮的运算,每一轮包括16次(一共64次),最后产生128位摘要。开始时,初始值为abcd,而在每次处理完单个消息块后,此哈希值将被得到的新哈希值替换,用于下一个消息块的计算。最后一个哈希值等于MD5的输出摘要。
对于每一个消息块,循环加密过程中首先要初始化链接变量A、B、C、D:
接下来进行4轮,共64次的运算,每轮用到的函数F和M的下标g不同:
第一轮:0≤i≤15
F=F
g=i
第二轮:16≤i≤31
F=G
g=(5i+1) mod 16
第三轮:32≤i≤47
F=H
g=(3i+5) mod 16
第四轮:48≤i≤63
F=I
g=7i mod 16
最后,经过4轮操作之后,还要进行加法运算,
a≔a+A
b≔b+B
c≔c+C
d≔d+D
新得到的abcd用作下一个消息块计算的初始值,直到最后一个消息块计算完成输出的abcd,即为经过MD5计算输出的摘要。
循环过程如下图:
(7)、轮内计算过程:
MD5的4轮,共64次的运算使用同一个操作程序,如下:
可以表示为:
F≔F+A+K[i]+M[g]
A≔D
D≔C
C≔B
B≔B + F<<<s[i]
3、维基百科上的伪代码
// : All variables are unsigned 32 bit and wrap modulo 2^32 when calculating var int s[64], K[64] var int i // s specifies the per-round shift amounts (计算过程中会用到位移,移多少位看s[i]) 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 } // Use binary integer part of the sines of integers (Radians) as constants: for i from 0 to 63 do K[i] := floor(2^32 × abs(sin(i + 1))) (K[i]取这个公式的整数部分,下面已经给出) end for // (Or just use the following precomputed table): 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 } // Initialize variables:(初始化变量) var int a0 := 0x67452301 // A var int b0 := 0xefcdab89 // B var int c0 := 0x98badcfe // C var int d0 := 0x10325476 // D (消息补位) // Pre-processing: adding a single 1 bit append "1" bit to message (补1个1) // Notice: the input bytes are considered as bit strings, // where the first bit is the most significant bit of the byte. // Pre-processing: padding with zeros(补0,直到消息长度mod 512 = 448) append "0" bit until message length in bits ≡ 448 (mod 512) // Notice: the two padding steps above are implemented in a simpler way // in implementations that only work with complete bytes: append 0x80 // and pad with 0x00 bytes so that the message length in bytes ≡ 56 (mod 64). append original length in bits mod 264 to message(补上原始消息长度mod 2^64后的结果,以64位表示) (对每一个512位的消息块做如下运算) // Process the message in successive 512-bit chunks: for each 512-bit chunk of padded message do break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15(每个消息块分成16个32位的words) // Initialize hash value for this chunk:(初始化变量ABCD) 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(共64次运算) var int F, g if 0 ≤ i ≤ 15 then(第一轮,函数F,变量g) F := (B and C) or ((not B) and D) g := i else if 16 ≤ i ≤ 31 then(第二轮,函数F,变量g) F := (D and B) or ((not D) and C) g := (5×i + 1) mod 16 else if 32 ≤ i ≤ 47 then(第三轮,函数F,变量g) F := B xor C xor D g := (3×i + 5) mod 16 else if 48 ≤ i ≤ 63 then(第四轮,函数F,变量g) F := C xor (B or (not D)) g := (7×i) mod 16 // Be wary of the below definitions of a,b,c,d(ABCD经过如下变换得到新的值) 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])(leftrotate(F,s[i])表示F循环左移s[i]位) end for // Add this chunk's hash to result so far:(64次运算后,A,B,C,D分别加上最初的a0,b0,c0,d0成为新的a0,b0,c0,d0,用作下一个消息块的初始变量) a0 := a0 + A b0 := b0 + B c0 := c0 + C d0 := d0 + D end for (最后一个消息块运算的结果即为MD5输出的摘要) var char digest[16] := a0 append b0 append c0 append d0 // (Output is in little-endian)