MD5是由Ronald Linn Rivest设计,于1992年公开,用以取代MD4算法的一种散列算法,首先要明确的是,他本身并非是一种加密算法,但因为其不可逆性,导致在密码界也常有应用,因为不可逆性,曾一度被认为是一种比较安全的算法,但据说被我天朝山东大学的一位女教授给破解了,只用几个小时就可以将其碰撞(就是用不同的明文算出相同的密文,姑且这样形容…),所以就目前而言,已经不再是一种百分之百安全的算法了(跟md5出自同宗的SHA-1算法据说也被谷歌给破了…)
MD5算法的过程,总体简述就是,输入一个不定长度的数据,经过填充按照64字节一组分别进行计算,最后输出一个定长128its的数据。
详细过程:
FF(a,b,c,d,Mj,s,ti)表示a=b+((a+F(b,c,d)+Mj+ti)<<s)
GG(a,b,c,d,Mj,s,ti)表示a=b+((a+G(b,c,d)+Mj+ti)<<s)
HH(a,b,c,d,Mj,s,ti)表示a=b+((a+H(b,c,d)+Mj+ti)<<s)
II(a,b,c,d,Mj,s,ti)表示a=b+((a+I(b,c,d)+Mj+ti)<<s)
然后开始
第一轮
a=FF(a,b,c,d,M0,7,0xd76aa478)
b=FF(d,a,b,c,M1,12,0xe8c7b756)
c=FF(c,d,a,b,M2,17,0x242070db)
d=FF(b,c,d,a,M3,22,0xc1bdceee)
a=FF(a,b,c,d,M4,7,0xf57c0faf)
b=FF(d,a,b,c,M5,12,0x4787c62a)
c=FF(c,d,a,b,M6,17,0xa8304613)
d=FF(b,c,d,a,M7,22,0xfd469501)
a=FF(a,b,c,d,M8,7,0x698098d8)
b=FF(d,a,b,c,M9,12,0x8b44f7af)
c=FF(c,d,a,b,M10,17,0xffff5bb1)
d=FF(b,c,d,a,M11,22,0x895cd7be)
a=FF(a,b,c,d,M12,7,0x6b901122)
b=FF(d,a,b,c,M13,12,0xfd987193)
c=FF(c,d,a,b,M14,17,0xa679438e)
d=FF(b,c,d,a,M15,22,0x49b40821)
第二轮
a=GG(a,b,c,d,M1,5,0xf61e2562)
b=GG(d,a,b,c,M6,9,0xc040b340)
c=GG(c,d,a,b,M11,14,0x265e5a51)
d=GG(b,c,d,a,M0,20,0xe9b6c7aa)
a=GG(a,b,c,d,M5,5,0xd62f105d)
b=GG(d,a,b,c,M10,9,0x02441453)
c=GG(c,d,a,b,M15,14,0xd8a1e681)
d=GG(b,c,d,a,M4,20,0xe7d3fbc8)
a=GG(a,b,c,d,M9,5,0x21e1cde6)
b=GG(d,a,b,c,M14,9,0xc33707d6)
c=GG(c,d,a,b,M3,14,0xf4d50d87)
d=GG(b,c,d,a,M8,20,0x455a14ed)
a=GG(a,b,c,d,M13,5,0xa9e3e905)
b=GG(d,a,b,c,M2,9,0xfcefa3f8)
c=GG(c,d,a,b,M7,14,0x676f02d9)
d=GG(b,c,d,a,M12,20,0x8d2a4c8a)
第三轮
a=HH(a,b,c,d,M5,4,0xfffa3942)
b=HH(d,a,b,c,M8,11,0x8771f681)
c=HH(c,d,a,b,M11,16,0x6d9d6122)
d=HH(b,c,d,a,M14,23,0xfde5380c)
a=HH(a,b,c,d,M1,4,0xa4beea44)
b=HH(d,a,b,c,M4,11,0x4bdecfa9)
c=HH(c,d,a,b,M7,16,0xf6bb4b60)
d=HH(b,c,d,a,M10,23,0xbebfbc70)
a=HH(a,b,c,d,M13,4,0x289b7ec6)
b=HH(d,a,b,c,M0,11,0xeaa127fa)
c=HH(c,d,a,b,M3,16,0xd4ef3085)
d=HH(b,c,d,a,M6,23,0x04881d05)
a=HH(a,b,c,d,M9,4,0xd9d4d039)
b=HH(d,a,b,c,M12,11,0xe6db99e5)
c=HH(c,d,a,b,M15,16,0x1fa27cf8)
d=HH(b,c,d,a,M2,23,0xc4ac5665)
第四轮
a=II(a,b,c,d,M0,6,0xf4292244)
b=II(d,a,b,c,M7,10,0x432aff97)
c=II(c,d,a,b,M14,15,0xab9423a7)
d=II(b,c,d,a,M5,21,0xfc93a039)
a=II(a,b,c,d,M12,6,0x655b59c3)
b=II(d,a,b,c,M3,10,0x8f0ccc92)
c=II(c,d,a,b,M10,15,0xffeff47d)
d=II(b,c,d,a,M1,21,0x85845dd1)
a=II(a,b,c,d,M8,6,0x6fa87e4f)
b=II(d,a,b,c,M15,10,0xfe2ce6e0)
c=II(c,d,a,b,M6,15,0xa3014314)
d=II(b,c,d,a,M13,21,0x4e0811a1)
a=II(a,b,c,d,M4,6,0xf7537e82)
b=II(d,a,b,c,M11,10,0xbd3af235)
c=II(c,d,a,b,M2,15,0x2ad7d2bb)
d=II(b,c,d,a,M9,21,0xeb86d391)
最后要说明的一点是,如果原数据长度超过或达到了56个字节,就要将其长度补齐为64,不然无法计算56以及超过56字节的数据
然后上代码
首先初始化一些需要的必要数据以及变量缓存区:
//用于存放最终结果,以便转化为字符串
short output[16];
char dest[16];
memset(dest, 0, SHORT_MD5_LEN);
memset(dst, 0, CHAR_MD5_LEN);
//四组幻数
unsigned int h[] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
static const unsigned int k[64] =
{
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
//四轮循环(GG,FF,HH,II)每轮循环每一步所要位移的位数
static const unsigned int qz[] =
{
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
//每一轮所要读取的元素下标
static const unsigned int s[] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9
};
//循环所需要的变量
unsigned int i = 0, j = 0;
这样我们所需要的一些变量以及数据就初始化完成了,现在开始填充组装数据
//N*512+448
//N=(数据长度+64(bit))/512(bit),长度+8是为了防止数据长度>=56
//任意数据长度+64bit刚好是512倍数,最后+8空出存放数据原始长度的位置
size_t n_len = ((len + 8) / 64) * 64 + 56 + 8;
unsigned char *n_text = (unsigned char *)malloc(n_len);
memset(n_text, 0x00, n_len);
memcpy(n_text, text, len);
//末尾添加二进制1000 0000
n_text[len] = 0x80;
//追加长度
//注意此处末尾添加的是一个64位的数据!!!
unsigned char len_s[8];
memset(len_s, 0x00, 8);
unsigned long temp_len = 0x00000000;
temp_len = (unsigned long)(len * 8);
//此处注意,只拷贝4个字节数据,因为
//unsigned long只有四个字节
memcpy(len_s, &temp_len, 4);
memcpy(n_text + (n_len-8), len_s, 8);
注意填充二进制10时,至少填充一个1000 0000即0x80,还有填充原数据长度是必须得是八个字节
然后开始将填充组装后的数据分为512bit,然后每512bit再分为4组每组32bit共16字节128bit
//每64字节(512位)
//处理一次,因为填充过后的数刚好是64的倍数
for (j = 0; j < n_len; j += 64)
{
unsigned int H[4] = { 0,0,0,0 };
memcpy(H, h, 4 * sizeof(unsigned int));
//分段拷贝内容,以供处理多组数据
unsigned char temp_text[64];
memset(temp_text, 0x00, 64);
memcpy(temp_text, n_text + j, 64);
//一共循环64次,分为四组
for (i = 0; i < 64; i++)
{
//四组非线性函数运算,用开关语句来判断是第几组
// 0~16第一组,16~32第二组
//32~48第三组,48~64第四组
unsigned int R = 0, f = 0, tmp = 0;
switch ((int)i / 16)
{
//H[1]=X,H[2]=Y,H[3]=Z
//F(X,Y,Z)
case 0: f = (H[1] & H[2]) | ((~H[1]) & H[3]); break;
//G(X,Y,Z)
case 1: f = (H[3] & H[1]) | (H[2] & (~H[3])); break;
//H(X,Y,Z)
case 2: f = H[1] ^ H[2] ^ H[3]; break;
//I
case 3: f = H[2] ^ (H[1] | (~H[3])); break;
}
//abcd分别交换位置
tmp = H[3];
H[3] = H[2];
H[2] = H[1];
//R=(a+?(bcd)+M?+ti),四个字节一运算不是一个字节
R = H[0] + f + k[i] + (((unsigned int *)temp_text)[s[i]]);
//b+((a+?(bcd)+M?+ti)<
H[1] = H[1] + ((R << qz[i]) | (R >> (32 - qz[i])));
H[0] = tmp;
}
//每轮循环结束后,ABCD分别与abcd相加
for (i = 0; i < 4; i++) h[i] += H[i];
}
因为最后要以字符串的形式输出,所以还需要将十六进制数转为字符串,并且还要将上面动态申请的内存释放,防止内存泄漏
free(n_text);
memcpy(dest, h, 16);
//与0xff位与将高位的ff变为00
for (int i = 0; i < 16; i++)
output[i] = dest[i] & 0xff;
//将十六进制数据打印成字符串
for (int i = 0; i < SHORT_MD5_LEN; i++)
sprintf(dst + i * 2, "%02x", output[i]);
}
完整项目代码地址:
linux gcc编译
windows MSVC编译