MD5消息摘要算法,属Hash算法一类。MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要。
具体实现可参考博客
https://blog.csdn.net/sinat_27933301/article/details/79538169
和官方标准RFC1321
https://tools.ietf.org/html/rfc1321
算法实现
一些常量的定义
//定义循环右移函数 # define RPTATE_SHIFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) //定义F,G,H,I函数 # define F(x, y, z) (((x) & (y)) | ((~x) & (z))) # define G(x, y, z) (((x) & (z)) | ((y) & (~z))) # define H(x, y, z) ((x) ^ (y) ^ (z)) # define I(x, y, z) ((y) ^ ((x) | (~z))) //定义寄存器word A,B,C,D # define A 0x67452301 # define B 0xefcdab89 # define C 0x98badcfe # define D 0x10325476 //strBaye的长度 unsigned int strlength = 0 ; //A,B,C,D的临时变量 int tempA = 0 , tempB = 0 , tempC = 0 , tempD = 0 ; //定义k数组,用于压缩函数 const unsigned int k [ ] = { 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 } ; //用数组存储向左位移数,方便操作 //每一行 表示一轮的左位移数 , 根据 RFC 1321的标准参数来做 const unsigned int s [ ] = { 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 } ; //用于转换16进制 const char str16 [ ] = "0123456789abcdef" ;
这些定义包括标准里的 A,B,C,D word, F,G,H,I函数, 用于压缩参数的常量值, 这些先定义下来,方便后面写程序,防止出错
主要函数getMD5Code
此暗示通过传入一个字符串,返回对此字符串进行MD4处理得到的摘要字符串ik, 这是一个主要的流程。
首先初始化最终的变量A,B,C,D, 然后进行填充操作, 进行压缩操作, 最后转换为16进制哈希值。
string getMD5Code ( string source ) { //初始化 tempA = A ; tempB = B ; tempC = C ; tempD = D ; //把string变成二进制, 同时附加填充位 unsigned int * strByte = padding ( source ) ; // 对于i = 0到N / 16-1 将块i复制到X. // 对于j = 0到15做 // 将X [j]设置为M [i * 16 + j]。 // 进行压缩函数操作 for ( int i = 0 ; i < strlength / 16 ; i ++ ) { unsigned int num [ 16 ] ; for ( int j = 0 ; j < 16 ; j ++ ) { num [ j ] = strByte [ i * 16 + j ] ; } MD5compress ( num ) ; } //把得到的摘要2进制变成16进制字符串输出 return changeToHex ( tempA ) . append ( changeToHex ( tempB ) ) . append ( changeToHex ( tempC ) ) . append ( changeToHex ( tempD ) ) ; }
填充函数padding
填充函数
处理后应满足bits≡448(mod512), 填充方式为先加一个1,其它位补零,最后加上64位的原来长度
unsigned int * padding ( string str ) { //以512位,64个字节为一组, num表示组数, 利用整数相除直接得到组数 unsigned int num = ( ( str . length ( ) + 8 ) / 64 ) + 1 ; //对于一组需要16个整数来存储 16*4=64, strByte表示此字符串的2进制表示(这里用int数组表示) unsigned int * strByte = new unsigned int [ num * 16 ] ; //初始化字符串的长度(长度是16*组数) strlength = num * 16 ; //初始化 strByte数组 for ( unsigned int i = 0 ; i < num * 16 ; i ++ ) { strByte [ i ] = 0 ; } //一个整数存储四个字节,一个unsigned int对应4个字节,保存4个字符信息 for ( unsigned int i = 0 ; i < str . length ( ) ; i ++ ) { strByte [ i / 4 ] | = ( str [ i ] ) << ( ( i % 4 ) * 8 ) ; } //尾部添加1 一个unsigned int保存4个字符信息,所以用128左移 strByte [ str . length ( ) / 4 ] | = 0x80 << ( ( ( str . length ( ) % 4 ) ) * 8 ) ; /* *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 */ strByte [ num * 16 - 2 ] = str . length ( ) * 8 ; return strByte ; }
压缩函数
按照RFC 1321的标准 进行一共64次压缩运算, 因为每一次的参数是固定的, 所以可以提前输入参数, 这里我是k数组来存储这些参数
//MD5 压缩函数 Hmd5 void MD5compress ( unsigned int M [ ] ) { int f = 0 , g = 0 ; int a = tempA , b = tempB , c = tempC , d = tempD ; /*第1轮迭代: *X[j] , j = 1..16. *第2轮迭代: *X[p2(j)], p2(j) = (1 + 5j) mod 16, j = 1..16. *第3轮迭代: *X[p3(j)], p3(j) = (5 + 3j) mod 16, j = 1..16. *第2轮迭代: *X[p4(j)], p4(j) = 7j mod 16, j = 1..16*/ for ( int i = 0 ; i < 64 ; i ++ ) { if ( i < 16 ) { f = F ( b , c , d ) ; g = i ; } else if ( i < 32 ) { f = G ( b , c , d ) ; g = ( 5 * i + 1 ) % 16 ; } else if ( i < 48 ) { f = H ( b , c , d ) ; g = ( 3 * i + 5 ) % 16 ; } else { f = I ( b , c , d ) ; g = ( 7 * i ) % 16 ; } //每次循环使用相同的迭代逻辑和 4*16 次运算的预设参数表, 也就是前面的k表 unsigned int tmp = d ; d = c ; c = b ; b = b + RPTATE_SHIFT ( ( a + f + k [ i ] + M [ g ] ) , s [ i ] ) ; a = tmp ; } tempA = a + tempA ; tempB = b + tempB ; tempC = c + tempC ; tempD = d + tempD ; }
进制转换函数
把数字变为2进制 以字符串输出
string changeToHex ( int num ) { int b ; string tmp ; string str = "" ; for ( int i = 0 ; i < 4 ; i ++ ) { tmp = "" ; b = ( ( num >> i * 8 ) % ( 1 << 8 ) ) & 0xff ; //逆序处理每个字节 for ( int j = 0 ; j < 2 ; j ++ ) { tmp . insert ( 0 , 1 , str16 [ b % 16 ] ) ; b = b / 16 ; } str + = tmp ; } return str ; }
测试函数
验证函数,通过RFC 1321给的标准输入输出来判断
int main ( ) { //这是 RFC 1321的标准测试输入和输出, 用来验证此MD5算法的正确性 string input [ 7 ] = { "" , "a" , "abc" , "message digest" , "abcdefghijklmnopqrstuvwxyz" , "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" , "12345678901234567890123456789012345678901234567890123456789012345678901234567890" } ; string expect [ 7 ] = { "d41d8cd98f00b204e9800998ecf8427e" , "0cc175b9c0f1b6a831c399e269772661" , "900150983cd24fb0d6963f7d28e17f72" , "f96b697d7cb7938d525a2f31aaf161d0" , "c3fcd3d76192e4007dfb496cca67e13b" , "d174ab98d277d9f5a5611c2c9f419d9f" , "57edf4a22be3c955ac49da2e2107b67a" } ; for ( int i = 0 ; i < 7 ; i ++ ) { cout << "--------------------------------" << endl ; cout << "测试 " << i + 1 << ":" << endl ; cout << "原消息: " << input [ i ] << endl ; cout << "MD5标准输出: " << expect [ i ] << endl ; string digest = getMD5Code ( input [ i ] ) ; cout << "MD5输出: " << digest << endl ; } }
测试结果
可看到和标准输出是相同的, 证明此MD5算法正确。
源码传送门
https://github.com/wangjiwu/implement-MD5-in-C-
参考文档
MD5加密算法原理及实现
https://www.cnblogs.com/hjgods/p/3998570.html
The MD5 Message-Digest Algorithm
https://tools.ietf.org/html/rfc1321