MD5即Message-Digest Algorithm 5, 信息学中使用广泛的哈希算法

MD5简介

MD5即Message-Digest Algorithm 5, 信息学中使用广泛的哈希算法

这个算法具有很多性质:

  1. 压缩性: 对于任意长度的输入, 输出长度总是相同的
  2. 容易计算: 线性时间复杂度
  3. 抗修改性: 对原数据的一点点修改都会导致最终结果的巨大变化
  4. 抗碰撞性: 已知原数据和MD5值很难生成与原数据不同但MD5值相同的数据

MD5可以生成任意一个文件的“数字指纹”,对文件的微小改动都会直接导致数字指纹的巨大变化。

注:MD5加密中文需要使用UTF-8编码,但Windows下默认是GBK编码,两种编码得到的结果是不一样的

加密步骤

填充

MD5中,首先要对信息进行填充,先填充一个1,后面都填充0,使得信息的长度 $len \equiv 448 \pmod{512}$ ,即 $len\bmod 512 = 448$。为什么要求模出来是448呢?因为448=512-64,而填充完后后面还要再填上64位的原数据长度,如果超出64位则填充原数据长度的后64位,这样可以使得最终的数据长度为512的整数倍,才可以满足后面继续加密的需要。

初始化变量

初始化4个128位链接变量如下:

1
2
3
4
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476

处理数据

分组

将原始数据每512bits为一个分组 (这就是前面要求填充到512整数倍的原因),对每组分别进行处理

处理每个分组的数据

每个分组有四个变量$a,b,c,d$,第一分组的$a,b,c,d$分别为上面所写了的4个16进制数,此后每一个分组的这四个变量都是上一个分组计算的结果。

现在定义四个非线性函数 (逻辑运算) :

其中$\land$表示按位与,$\lor$表示按位或,$\neg$表示按位非,$\oplus$表示按位异或

接下来,我们设$M_j$为这一组信息的第$j$个子分组 ($0\le j\le 15$) ,$t_j$为$2^{32}\cdot |\sin{i}|\ \ (1\le i\le 64)$的整数部分,$i$是整数,单位是弧度($2^{32}=4294967296$)

现在定义四个操作:

注意:<<在这里是左环移而不是一般的左移!!!

左环移和左移的区别:

现有一个二进制数11010010,左环移三位后是10010110,而左移三位后是10010000

接下来就可以操作了,共4轮循环,每轮16次,伪代码如下:

其中:

$s{i,j\bmod 4}$为常数数组,具体数值见下面代码里的s数组,$M_j$和$t{16i-j}$上文已经讲过了

最后把$a,b,c,d$分别加上$A,B,C,D$

然后进行下一组运算

输出

输出数据为$a,b,c,d$的级联

代码(C++)

编译指令:g++ MD5.cpp -o md5.exe -Wall -Wextra

MD5.hpp:

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
#ifndef CSTRING
#include <cstring>
#endif // !CSTRING
#include <string>

#ifndef MD5_HPP
#define MD5_HPP

//----------------------------------------宏定义----------------------------------------
#define F_MD5(x, y, z) ((x & y) | (~x & z))
#define G_MD5(x, y, z) ((x & z) | (y & ~z))
#define H_MD5(x, y, z) (x ^ y ^ z)
#define I_MD5(x, y, z) (y ^ (x | ~z))
#define ROTATE_LEFT_MD5(x, n) ((x << n) | (x >> (32 - n)))

#define FF_MD5(a, b, c, d, m, s, t) (a = b + (ROTATE_LEFT_MD5((a + F_MD5(b, c, d) + m + t), s)))
#define GG_MD5(a, b, c, d, m, s, t) (a = b + (ROTATE_LEFT_MD5((a + G_MD5(b, c, d) + m + t), s)))
#define HH_MD5(a, b, c, d, m, s, t) (a = b + (ROTATE_LEFT_MD5((a + H_MD5(b, c, d) + m + t), s)))
#define II_MD5(a, b, c, d, m, s, t) (a = b + (ROTATE_LEFT_MD5((a + I_MD5(b, c, d) + m + t), s)))


//----------------------------------------MD5结构体定义----------------------------------------
struct MD5_CTX
{
unsigned int count[2];
unsigned int state[4]; //加密结果
unsigned char buffer[64];
};

//----------------------------------------常量定义----------------------------------------
const unsigned int t[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};

const unsigned int s[4][4]{
{7, 12, 17, 22},
{5, 9, 14, 20},
{4, 11, 16, 23},
{6, 10, 15, 21}};

unsigned char PADDING[] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

//----------------------------------------函数预定义----------------------------------------
void MD5Init(MD5_CTX *context);
void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen);
void MD5Final(MD5_CTX *context, unsigned char digest[16]);
void MD5Transform(unsigned int state[4], unsigned char block[64]);
void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len);
void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len);

//----------------------------------------主函数----------------------------------------
void MD5Init(MD5_CTX *context)
{
context->count[0] = 0;
context->count[1] = 0;
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
}

void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen)
{
//context: 已初始化的结构体
//input: 需加密的信息
//inputlen: 需加密的信息的长度
unsigned int i = 0, index = 0, partlen = 0;
index = (context->count[0] >> 3) & 0x3F;
partlen = 64 - index;
context->count[0] += inputlen << 3;
if (context->count[0] < (inputlen << 3))
context->count[1]++;
context->count[1] += inputlen >> 29;

if (inputlen >= partlen)
{
memcpy(&context->buffer[index], input, partlen);
MD5Transform(context->state, context->buffer);
for (i = partlen; i + 64 <= inputlen; i += 64)
MD5Transform(context->state, &input[i]);
index = 0;
}
else
{
i = 0;
}
memcpy(&context->buffer[index], &input[i], inputlen - i);
}

void MD5Final(MD5_CTX *context, unsigned char digest[16])
{
//context: 加密好的 MD5结构体
//digest: 最终保存位置
unsigned int index = 0, padlen = 0;
unsigned char bits[8];
index = (context->count[0] >> 3) & 0x3F;
padlen = (index < 56) ? (56 - index) : (120 - index);
MD5Encode(bits, context->count, 8);
MD5Update(context, PADDING, padlen);
MD5Update(context, bits, 8);
MD5Encode(digest, context->state, 16);
}

void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len)
{
unsigned int i = 0, j = 0;
while (j < len)
{
output[j] = input[i] & 0xFF;
output[j + 1] = (input[i] >> 8) & 0xFF;
output[j + 2] = (input[i] >> 16) & 0xFF;
output[j + 3] = (input[i] >> 24) & 0xFF;
i++;
j += 4;
}
}

void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len)
{
unsigned int i = 0, j = 0;
while (j < len)
{
output[i] = (input[j]) |
(input[j + 1] << 8) |
(input[j + 2] << 16) |
(input[j + 3] << 24);
i++;
j += 4;
}
}

void MD5Transform(unsigned int state[4], unsigned char block[64])
{
//state: MD5结构体里的 state数组
//block: 要加密的 512bits数据块
unsigned int a[4];
//注意一下,这里 a[0],a[1],a[2],a[3]分别代表 d,c,b,a, 顺序是反的, 方便后面操作
a[0] = state[3];
a[1] = state[2];
a[2] = state[1];
a[3] = state[0];
unsigned int x[64];
int i, j, k;
int kinit[4] = {0, 1, 5, 0}; //k的初始值

MD5Decode(x, block, 64);
for (i = 0; i < 4; i++)
{
k = kinit[i];
for (j = 0; j < 16; j++)
{
switch (i)
{
case 0:
FF_MD5(a[(j + 3) % 4], a[(j + 2) % 4], a[(j + 1) % 4], a[j % 4],
x[k], s[i][j % 4], t[i * 16 + j]);
k = (k + 1) % 16;
break;
case 1:
GG_MD5(a[(j + 3) % 4], a[(j + 2) % 4], a[(j + 1) % 4], a[j % 4],
x[k], s[i][j % 4], t[i * 16 + j]);
k = (k + 5) % 16;
break;
case 2:
HH_MD5(a[(j + 3) % 4], a[(j + 2) % 4], a[(j + 1) % 4], a[j % 4],
x[k], s[i][j % 4], t[i * 16 + j]);
k = (k + 3) % 16;
break;
case 3:
II_MD5(a[(j + 3) % 4], a[(j + 2) % 4], a[(j + 1) % 4], a[j % 4],
x[k], s[i][j % 4], t[i * 16 + j]);
k = (k + 7) % 16;
break;
}
}
}
state[0] += a[3];
state[1] += a[2];
state[2] += a[1];
state[3] += a[0];
}
void MD5(unsigned char *input, unsigned char digest[16]) //最终的加密函数
{
MD5_CTX MD5;
MD5Init(&MD5);
MD5Update(&MD5, input, strlen((char *)input));
MD5Final(&MD5, digest);
return;
}
std::string MD5string(std::string input) //对输入的 std::string进行 MD5加密并返回 std::string
{
unsigned char digest[16];
char tmp[2];
std::string res;
int i;
MD5((unsigned char *)(input.c_str()), digest);
for (i = 0; i < 16; i++)
{
sprintf(tmp, "%02x", digest[i]);
res.push_back(tmp[0]);
res.push_back(tmp[1]);
}
return res;
}
#endif // !MD5_HPP

MD5.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <bits/stdc++.h>
#include "MD5.hpp"
using namespace std;
int main()
{
string s, res;
unsigned char decryption[16];
cin >> s;
MD5((unsigned char *)(s.c_str()), decryption);
res = MD5string(s);
for (int i = 0; i < 16; i++)
{
printf("%02x", decryption[i]);
}
printf("\n");
cout << res << endl;
system("pause");
return 0;
}

生成t数组的Python代码(C++的cmath库是真的弱)

1
2
3
4
5
6
7
8
9
10
11
12
13
import math

def main():
for i in range(1, 65):
print("%#x" % int(math.floor((2**32)*math.fabs(math.sin(i)))), end='')
print(',', end='')
if i % 8 == 0:
print('\n', end='')


if __name__ == "__main__":
main()

参考资料:

https://zhuanlan.zhihu.com/p/37257569

https://www.cnblogs.com/foxclever/p/7668369.html