MD5算法解释及C/C++实现

关于MD5

MD5是由Ronald Linn Rivest设计,于1992年公开,用以取代MD4算法的一种散列算法,首先要明确的是,他本身并非是一种加密算法,但因为其不可逆性,导致在密码界也常有应用,因为不可逆性,曾一度被认为是一种比较安全的算法,但据说被我天朝山东大学的一位女教授给破解了,只用几个小时就可以将其碰撞(就是用不同的明文算出相同的密文,姑且这样形容…),所以就目前而言,已经不再是一种百分之百安全的算法了(跟md5出自同宗的SHA-1算法据说也被谷歌给破了…)

算法过程

MD5算法的过程,总体简述就是,输入一个不定长度的数据,经过填充按照64字节一组分别进行计算,最后输出一个定长128its的数据。
详细过程:

  1. 首先输入一串不定长度的数据,用其长度除以512余数要为448,如果不够则在其后填充一个二进制的1和若干二进制0
  2. 将输入的原数据长度填充到最后,注意一定要站8个字节,也就是64bit
  3. 将填充完毕的数据,每64字节即512bit为一组进行计算(如果不出错的话,填充后的数据长度刚好是64字节的倍数),然后每组计算过程中,又需要一个128bit的缓存器来存储计算的结果,可以将这个缓存器分为4组32bit的幻数A、B、C、D,将其分别初始化为:
    A : 01 23 45 67
    B: 89 ab cd ef
    C: fe dc ba 98
    D: 76 54 32 10
    然后要注意大小端序的问题所以应该写成:67452301、efcdab89、98badcfe、10325476
  4. 然后将之前分好的512bit一组再分为4组,每组4个字节共16字节128bit,如何在进行4轮计算,刚好把这512bit即64字节处理完
  5. 还需要四个非线性函数F、G、H、I,对输入进行处理计算
    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))
  6. 然后开始4论计算,首先说明每组计算过程:
	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)
  1. 最后记得每组计算完毕后ABCD分别要与abcd相加,然后进入下一组512bit的计算

最后要说明的一点是,如果原数据长度超过或达到了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)<<s))
			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编译

猜你喜欢

转载自blog.csdn.net/weixin_43815930/article/details/106065452