MD5算法原理及实现

MD5算法原理及实现

散列函数

散列函数,也称作哈希函数,消息摘要函数,单向函数或者杂凑函数。散列函数主要用于验证数据的完整性。通过散列函数,可以创建消息的“数字指纹”,消息接收方可以通过校验消息的哈希值来验证消息的完整性,防止消息被篡改。散列函数具有以下特性:

  1. 散列函数的运算过程是不可逆的,这个称为散列函数的单向性。
  2. 对于一个已知的消息及其散列值,要找到另外一个消息使其获得相同的散列值是不可能的,这个特性称为散列函数的弱碰撞性。这个特性可以用来防止消息伪造。
  3. 任意两个不同消息的散列值一定不同。
  4. 对原始消息长度没有限制。

任何消息经过散列函数处理后,都会产生一个唯一的散列值,这个散列值可以用来验证消息的完整性。计算消息散列值的过程被称为“消息摘要”,计算消息散列值的算法被称为消息摘要算法。常使用的消息摘要算法有:MD—消息摘要算法,SHA—安全散列算法,MAC—消息认证码算法。本文主要来了解MD算法。

MD5算法

MD5算法是典型的消息摘要算法,它是由MD4,MD3和MD2算法演变而来。无论是哪一种MD算法,其原理都是接受一个任意长度的消息并产生一个128位的消息摘要。如果把得到的消息摘要转换成十六进制字符串,则会得到一个32字节长度的字符串,我们平常见到的大部分MD数字指纹就是一个长度为32的十六进制字符串。

MD5算法原理

假设原始消息长度是b(以bit为单位),注意这里b可以是任意长度,并不一定要是8的整数倍。计算该消息MD5值的过程如下:

1.填充信息

在计算消息的MD5值之前,首先对原始信息进行填充,这里的信息填充分为两步。
第一步,对原始信息进行填充,填充之后,要求信息的长度对512取余等于448。填充的规则如下:假设原始信息长度为b bit,那么在信息的b+1 bit位填充1,剩余的位填充0,直到信息长度对512取余为448。这里有一点需要注意,如果原始信息长度对512取余正好等于448,这种情况仍然要进行填充,很明显,在这时我们要填充的信息长度是512位,直到信息长度对512取余再次等于448。所以,填充的位数最少为1,最大为512。
第二步,填充信息长度,我们需要把原始信息长度转换成以bit为单位,然后在第一步操作的结果后面填充64bit的数据表示原始信息长度。第一步对原始信息进行填充之后,信息长度对512取余结果为448,这里再填充64bit的长度信息,整个信息恰好可以被512整除。其实从后续过程可以看到,计算MD5时,是将信息分为若干个分组进行处理的,每个信息分组的长度是512bit。

2.信息处理

在进行MD5值计算之前,我们先来做一些定义。

  • 信息分组定义
    原始信息经过填充之后,最终得到的信息长度(bit)是512的整数倍,我们先对信息进行分组,每512bit为一个分组,然后再将每个信息分组(512bit)再细分为16个小的分组,每个小分组的长度为32bit。规定如下
    Mp 代表经过填充之后的信息
    LM 表示Mp的长度(以bit为单位)
    N 表示分组个数,N = LM/512
    M[i] 表示将原始信息进行分组后的第i个信息分组,其中i=1…N
    X[i] 表示将M[i]进行分组后的第i个小分组,其中i=1…16

  • 标准幻数定义
    现定义四个标准幻数如下,
    A = 01 23 45 67
    B = 89 ab cd ef
    C = fe dc ba 98
    D = 76 54 32 10
    在计算机中存储时,采用小端存储方式,以A为例,A在Java中初始化的代码为为A=0x67452301

  • 常量表T
    T是一个常量表,T[i] = 4294967296 * abs(sin(i))的运算结果取整,其中i=1…64

  • 辅助方法
    我们定义四个辅助方法。
    F(x,y,z) = (x & y) | (~x & z)

G(x,y,z) = (x & z) | (y & (~z))

H(x,y,z) = x ^ y ^ z

I(x,y,z) = y ^ (x | (~z))
其中,x,y,z长度为32bit

下面就是最核心的信息处理过程,计算MD5的过程实际上就是轮流处理每个信息分组的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    A=0x67452301
B=0xefcdab89
C=0x98badcfe
D=0x10325476
for( j=1;j<=N;j++){ //依次处理每个分组,其中N代表分组个数
AA = A
BB = B
CC = C
DD = D
//开始处理分组,每个信息分组要经过4轮处理
/*第一轮
假设 [abcd k s i] 表示执行的运算是 a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s),其中<<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺序从左到右。*/
[ABCD 0 7 1] [DABC 1 12 2] [CDAB 2 17 3] [BCDA 3 22 4]
[ABCD 4 7 5] [DABC 5 12 6] [CDAB 6 17 7] [BCDA 7 22 8]
[ABCD 8 7 9] [DABC 9 12 10] [CDAB 10 17 11] [BCDA 11 22 12]
[ABCD 12 7 13] [DABC 13 12 14] [CDAB 14 17 15] [BCDA 15 22 16]

/*第二轮
假设 [abcd k s i] 表示执行的运算是 a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s),其中 <<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺 序从左到右。*/
[ABCD 1 5 17] [DABC 6 9 18] [CDAB 11 14 19] [BCDA 0 20 20]
[ABCD 5 5 21] [DABC 10 9 22] [CDAB 15 14 23] [BCDA 4 20 24]
[ABCD 9 5 25] [DABC 14 9 26] [CDAB 3 14 27] [BCDA 8 20 28]
[ABCD 13 5 29] [DABC 2 9 30] [CDAB 7 14 31] [BCDA 12 20 32]

/*第三轮
假设 [abcd k s i] 表示执行的运算是 a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s),其中<<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺 序从左到右。*/
[ABCD 5 4 33] [DABC 8 11 34] [CDAB 11 16 35] [BCDA 14 23 36]
[ABCD 1 4 37] [DABC 4 11 38] [CDAB 7 16 39] [BCDA 10 23 40]
[ABCD 13 4 41] [DABC 0 11 42] [CDAB 3 16 43] [BCDA 6 23 44]
[ABCD 9 4 45] [DABC 12 11 46] [CDAB 15 16 47] [BCDA 2 23 48]

/*第四轮
假设 [abcd k s i] 表示执行的运算是 a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s),其中<<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺序从左到右。*/
[ABCD 0 6 49] [DABC 7 10 50] [CDAB 14 15 51] [BCDA 5 21 52]
[ABCD 12 6 53] [DABC 3 10 54] [CDAB 10 15 55] [BCDA 1 21 56]
[ABCD 8 6 57] [DABC 15 10 58] [CDAB 6 15 59] [BCDA 13 21 60]
[ABCD 4 6 61] [DABC 11 10 62] [CDAB 2 15 63] [BCDA 9 21 64]

//将当前消息分组的运算结果和上一次的结果进行累加
A = A + AA
B = B + BB
C = C + CC
D = D + DD
}
//最终我们按照低字节在前的顺序依次将A,B,C,D拼接起来,就是计算得到的MD5值,因此,MD5值的长度是固定的,为128bit。
算法实现

MD5算法实现如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import java.security.MessageDigest;
import java.util.Arrays;
public class MD5Test{

//标准的幻数
private static final int A=0x67452301;
private static final int B=0xefcdab89;
private static final int C=0x98badcfe;
private static final int D=0x10325476;

private static final int S11 = 7;
private static final int S12 = 12;
private static final int S13 = 17;
private static final int S14 = 22;

private static final int S21 = 5;
private static final int S22 = 9;
private static final int S23 = 14;
private static final int S24 = 20;

private static final int S31 = 4;
private static final int S32 = 11;
private static final int S33 = 16;
private static final int S34 = 23;

private static final int S41 = 6;
private static final int S42 = 10;
private static final int S43 = 15;
private static final int S44 = 21;

private static final int GROUP_LEN = 64;

public byte[] digest(byte[] input){
byte [] paddingData = getPaddingData(input); //对原始数据进行补位
return process(paddingData);//处理分组,核心算法
}

private int[] getGroupData(byte[] data,int index){
int [] groupData=new int[16];
for(int i=0;i<16;i++){
groupData[i] = (data[4*i+index]&0xFF)| //这里注意,在byte转int时一定要进行&0xff操作
(data[4*i+1+index]&0xFF)<<8|
(data[4*i+2+index]&0xFF)<<16|
(data[4*i+3+index]&0xFF)<<24;
}
return groupData;
}

private int[] getConstTable() {
int[] T = new int[64];
for(int i=1;i<65;i++) {
T[i-1] = (int)((long)(Math.abs(Math.sin(i))*4294967296l));
}
return T;
}


private byte[] getPaddingData(byte[] input) {
int length = input.length;
long bitLength = length << 3;
int rest = length % 64;
int paddingLength = 0;
if(rest < 56) {
paddingLength = 64-rest;
}else {
paddingLength = 128 - rest;
}
byte[] paddingData = new byte[length+paddingLength];
for(int i=0;i<length;i++) {
paddingData[i] = input[i];
}
paddingData[length] = (byte)(1<<7);
for(int i=1;i<paddingLength-8;i++) {
paddingData[length+i] = 0;
}
for(int i=0;i<8;i++){
paddingData[length+paddingLength-8+i]=(byte)(bitLength&0xFF);
bitLength = bitLength >>> 8;
}
return paddingData;
}

private byte[] process(byte[] data) {
int [] result = {A,B,C,D};
int length = data.length;
int groupCount = length/64; //计算分组数量,每组512位(64字节)
int[] T = getConstTable();
for(int groupIndex=0;groupIndex<groupCount;groupIndex++){
int[] x=getGroupData(data,groupIndex*GROUP_LEN);//获取分组数据
int a = result[0];
int b = result[1];
int c = result[2];
int d = result[3];

/*第一轮*/
a = FF(a, b, c, d, x[0], S11, T[0]); //a = b + ((a + F(b,c,d) + X[0] + T[0]) <<< 7)
d = FF(d, a, b, c, x[1], S12, T[1]); //d = a + ((d + F(a,b,c) + X[1] + T[1]) <<< 12)
c = FF(c, d, a, b, x[2], S13, T[2]); //c = d + ((c + F(d,a,b) + X[2] + T[2]) <<< 17)
b = FF(b, c, d, a, x[3], S14, T[3]); //b = c + ((b + F(c,d,a) + X[3] + T[3]) <<< 22)
a = FF(a, b, c, d, x[4], S11, T[4]); //a = b + ((a + F(b,c,d) + X[4] + T[4]) <<< 7)
d = FF(d, a, b, c, x[5], S12, T[5]); //d = a + ((d + F(a,b,c) + X[5] + T[5]) <<< 12)
c = FF(c, d, a, b, x[6], S13, T[6]); //c = d + ((c + F(d,a,b) + X[6] + T[6]) <<< 17)
b = FF(b, c, d, a, x[7], S14, T[7]); //b = c + ((b + F(c,d,a) + X[7] + T[7]) <<< 22)
a = FF(a, b, c, d, x[8], S11, T[8]); //a = b + ((a + F(b,c,d) + X[8] + T[8]) <<< 7)
d = FF(d, a, b, c, x[9], S12, T[9]); //d = a + ((d + F(a,b,c) + X[9] + T[9]) <<< 12)
c = FF(c, d, a, b, x[10], S13, T[10]); //c = d + ((c + F(d,a,b) + X[10] + T[10]) <<< 17)
b = FF(b, c, d, a, x[11], S14, T[11]); //b = c + ((b + F(c,d,a) + X[11] + T[12]) <<< 22)
a = FF(a, b, c, d, x[12], S11, T[12]); //a = b + ((a + F(b,c,d) + X[12] + T[12]) <<< 7)
d = FF(d, a, b, c, x[13], S12, T[13]); //d = a + ((d + F(a,b,c) + X[13] + T[13]) <<< 12)
c = FF(c, d, a, b, x[14], S13, T[14]); //c = d + ((c + F(d,a,b) + X[14] + T[14]) <<< 17)
b = FF(b, c, d, a, x[15], S14, T[15]); //b = c + ((b + F(c,d,a) + X[15] + T[15]) <<< 22)

/*第二轮*/
a = GG(a, b, c, d, x[1], S21, T[16]); //a = b + ((a + G(b,c,d) + X[1] + T[16]) <<< 5)
d = GG(d, a, b, c, x[6], S22, T[17]); //d = a + ((d + G(a,b,c) + X[6] + T[17]) <<< 9)
c = GG(c, d, a, b, x[11], S23, T[18]); //c = d + ((c + G(d,a,b) + X[11] + T[18]) <<< 14)
b = GG(b, c, d, a, x[0], S24, T[19]); //b = c + ((b + G(c,d,a) + X[0] + T[19]) <<< 20)
a = GG(a, b, c, d, x[5], S21, T[20]); //a = b + ((a + G(b,c,d) + X[5] + T[20]) <<< 5)
d = GG(d, a, b, c, x[10], S22, T[21]); ///d = a + ((d + G(a,b,c) + X[10] + T[21]) <<< 9)
c = GG(c, d, a, b, x[15], S23, T[22]); ///c = d + ((c + G(d,a,b) + X[15] + T[22]) <<< 14)
b = GG(b, c, d, a, x[4], S24, T[23]); //b = c + ((b + G(c,d,a) + X[4] + T[23]) <<< 20)
a = GG(a, b, c, d, x[9], S21, T[24]); ///a = b + ((a + G(b,c,d) + X[9] + T[24]) <<< 5)
d = GG(d, a, b, c, x[14], S22, T[25]); //d = a + ((d + G(a,b,c) + X[14] + T[25]) <<< 9)
c = GG(c, d, a, b, x[3], S23, T[26]); //c = d + ((c + G(d,a,b) + X[3] + T[26]) <<< 14)
b = GG(b, c, d, a, x[8], S24, T[27]); //b = c + ((b + G(c,d,a) + X[8] + T[27]) <<< 20)
a = GG(a, b, c, d, x[13], S21, T[28]); //a = b + ((a + G(b,c,d) + X[13] + T[28]) <<< 5)
d = GG(d, a, b, c, x[2], S22, T[29]); //d = a + ((d + G(a,b,c) + X[2] + T[29]) <<< 9)
c = GG(c, d, a, b, x[7], S23, T[30]); //c = d + ((c + G(d,a,b) + X[7] + T[30]) <<< 14)
b = GG(b, c, d, a, x[12], S24, T[31]); //b = c + ((b + G(c,d,a) + X[12] + T[31]) <<< 20)

/*第三轮*/
a = HH(a, b, c, d, x[5], S31, T[32]); //a = b + ((a + H(b,c,d) + X[5] + T[32])<<< 4)
d = HH(d, a, b, c, x[8], S32, T[33]); //d = a + ((d + H(a,b,c) + X[8] + T[33])<<< 11)
c = HH(c, d, a, b, x[11], S33, T[34]); //c = d + ((c + H(d,a,b) + X[11] + T[34])<<< 16)
b = HH(b, c, d, a, x[14], S34, T[35]); //b = c + ((b + H(c,d,a) + X[14] + T[35])<<< 23)
a = HH(a, b, c, d, x[1], S31, T[36]); //a = b + ((a + H(b,c,d) + X[1] + T[36])<<< 4)
d = HH(d, a, b, c, x[4], S32, T[37]); //d = a + ((d + H(a,b,c) + X[4] + T[37])<<< 11)
c = HH(c, d, a, b, x[7], S33, T[38]); //c = d + ((c + H(d,a,b) + X[7] + T[38])<<< 16)
b = HH(b, c, d, a, x[10], S34, T[39]); //b = c + ((b + H(c,d,a) + X[10] + T[39])<<< 23)
a = HH(a, b, c, d, x[13], S31, T[40]); //a = b + ((a + H(b,c,d) + X[13] + T[40])<<< 4)
d = HH(d, a, b, c, x[0], S32, T[41]); //d = a + ((d + H(a,b,c) + X[0] + T[41])<<< 11)
c = HH(c, d, a, b, x[3], S33, T[42]); //c = d + ((c + H(d,a,b) + X[3] + T[42])<<< 16)
b = HH(b, c, d, a, x[6], S34, T[43]); //b = c + ((b + H(c,d,a) + X[6] + T[43])<<< 23)
a = HH(a, b, c, d, x[9], S31, T[44]); //a = b + ((a + H(b,c,d) + X[9] + T[44])<<< 4)
d = HH(d, a, b, c, x[12], S32, T[45]); //d = a + ((d + H(a,b,c) + X[12] + T[45])<<< 11)
c = HH(c, d, a, b, x[15], S33, T[46]); //c = d + ((c + H(d,a,b) + X[15] + T[46])<<< 16)
b = HH(b, c, d, a, x[2], S34, T[47]); //b = c + ((b + H(c,d,a) + X[2] + T[47])<<< 23)

/*第四轮*/
a = II(a, b, c, d, x[0], S41, T[48]); //a = b + ((a + I(b,c,d) + X[0] + T[48]) <<< 6)
d = II(d, a, b, c, x[7], S42, T[49]); //d = a + ((d + I(a,b,c) + X[7] + T[49]) <<< 10)
c = II(c, d, a, b, x[14], S43, T[50]); //c = d + ((c + I(d,a,b) + X[14] + T[50]) <<< 15)
b = II(b, c, d, a, x[5], S44, T[51]); //b = c + ((b + I(c,d,a) + X[5] + T[51]) <<< 21)
a = II(a, b, c, d, x[12], S41, T[52]); //a = b + ((a + I(b,c,d) + X[12] + T[52]) <<< 6)
d = II(d, a, b, c, x[3], S42, T[53]); //d = a + ((d + I(a,b,c) + X[3] + T[53]) <<< 10)
c = II(c, d, a, b, x[10], S43, T[54]); //c = d + ((c + I(d,a,b) + X[10] + T[54]) <<< 15)
b = II(b, c, d, a, x[1], S44, T[55]); //b = c + ((b + I(c,d,a) + X[1] + T[55]) <<< 21)
a = II(a, b, c, d, x[8], S41, T[56]); //a = b + ((a + I(b,c,d) + X[8] + T[56]) <<< 6)
d = II(d, a, b, c, x[15], S42, T[57]); //d = a + ((d + I(a,b,c) + X[15] + T[57]) <<< 10)
c = II(c, d, a, b, x[6], S43, T[58]); //c = d + ((c + I(d,a,b) + X[6] + T[58]) <<< 15)
b = II(b, c, d, a, x[13], S44, T[59]); //b = c + ((b + I(c,d,a) + X[13] + T[59]) <<< 21)
a = II(a, b, c, d, x[4], S41, T[60]); //a = b + ((a + I(b,c,d) + X[4] + T[60]) <<< 6)
d = II(d, a, b, c, x[11], S42, T[61]); //d = a + ((d + I(a,b,c) + X[11] + T[61]) <<< 10)
c = II(c, d, a, b, x[2], S43, T[62]); //c = d + ((c + I(d,a,b) + X[2] + T[62]) <<< 15)
b = II(b, c, d, a, x[9], S44, T[63]); //b = c + ((b + I(c,d,a) + X[9] + T[63]) <<< 21)

result[0] += a;
result[1] += b;
result[2] += c;
result[3] += d;
}
byte[] resultByte = new byte[16];
for(int i = 0;i<4;i++) {
for(int j = 0; j < 4; j++) {
resultByte[i*4+j] = (byte) (result[i] & 0xff);
result[i]=result[i]>>8;
}
}
return resultByte;
}


private static int F(int x, int y, int z) {
return (x & y) | ((~x) & z);
}

private static int G(int x, int y, int z) {
return (x & z) | (y & (~z));
}

private static int H(int x, int y, int z) {
return x ^ y ^ z;
}

private static int I(int x, int y, int z) {
return y ^ (x | (~z));
}

private static int FF(int a, int b, int c, int d, int x, int s, int t) {
a += (F(b, c, d)&0xFFFFFFFF) + x + t;
a = ((a&0xFFFFFFFF)<< s) | ((a&0xFFFFFFFF) >>> (32 - s)); //循环位移
a += b;
return (a&0xFFFFFFFF);
}

private static int GG(int a, int b, int c, int d, int x, int s, int t) {
a += (G(b, c, d)&0xFFFFFFFF) + x + t;
a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}

private static int HH(int a, int b, int c, int d, int x, int s, int t) {
a += (H(b, c, d)&0xFFFFFFFF) + x + t;
a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}

private static int II(int a, int b, int c, int d, int x, int s,int t) {
a += (I(b, c, d)&0xFFFFFFFF) + x + t;
a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}

public static void main(String []args){
MD5Test myMd5=new MD5Test();
String testData = "hello,world";
System.out.println("--------My MD5--------");
byte[] myResult = myMd5.digest(testData.getBytes());
System.out.println(Arrays.toString(myResult));

System.out.println("--------Java MD5--------");
try {
MessageDigest javaMd5 = MessageDigest.getInstance("MD5");
byte[] javaResult = javaMd5.digest(testData.getBytes());
System.out.println(Arrays.toString(javaResult));
} catch (Exception e) {
e.printStackTrace();
}
}
}

这里也和Java提供的标准MD5算法进行了对比,通过测试可以看到该MD5计算的结果和Java标准MD5算法的计算结果是一样的