#include <iostream> #include <string> using namespace std; void H_getMD5(const unsigned long Message[16]); static unsigned long State[4]; /* MD5值是由4个32bit的数组成的,也就是4个4Byte长的无符号整型 */ static unsigned long Count[2]; /* MD5算法除了要对原字符串按bit填充1和0之外,还需要在末尾附上64bit 的值,值是用字符串的bit长度 mod 2^64,这两整型是8byte,共64bit */ static unsigned char buffer[64]; /* MD5按每 512bit 一组来处理,这用来存放每组的 512bit */ /* Left_Shift表示,对x进行循环左移 n 位 */ #define Left_Shift(x,n) (((x)<<(n))|((x)>>(32-(n)))) /* F、G、H、I 4个函数是MD5的4个基本的非线性函数 */ #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))) /* MD5主轮函数一共64次,每一次分别使用对应的位数来进行循环左移 */ const int shift_bit[64] = { 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 }; /* ** K_Sin数组里存的是sin(i)*2^32的整数部分的绝对值, ** 提前打表,提高运行效率。 */ const unsigned long K_Sin[64] = { 0xd76aa478l, 0xe8c7b756l, 0x242070dbl, 0xc1bdceeel, 0xf57c0fafl, 0x4787c62al, 0xa8304613l, 0xfd469501l, 0x698098d8l, 0x8b44f7afl, 0xffff5bb1l, 0x895cd7bel, 0x6b901122l, 0xfd987193l, 0xa679438el, 0x49b40821l, 0xf61e2562l, 0xc040b340l, 0x265e5a51l, 0xe9b6c7aal, 0xd62f105dl, 0x02441453l, 0xd8a1e681l, 0xe7d3fbc8l, 0x21e1cde6l, 0xc33707d6l, 0xf4d50d87l, 0x455a14edl, 0xa9e3e905l, 0xfcefa3f8l, 0x676f02d9l, 0x8d2a4c8al, 0xfffa3942l, 0x8771f681l, 0x6d9d6122l, 0xfde5380cl, 0xa4beea44l, 0x4bdecfa9l, 0xf6bb4b60l, 0xbebfbc70l, 0x289b7ec6l, 0xeaa127fal, 0xd4ef3085l, 0x04881d05l, 0xd9d4d039l, 0xe6db99e5l, 0x1fa27cf8l, 0xc4ac5665l, 0xf4292244l, 0x432aff97l, 0xab9423a7l, 0xfc93a039l, 0x655b59c3l, 0x8f0ccc92l, 0xffeff47dl, 0x85845dd1l, 0x6fa87e4fl, 0xfe2ce6e0l, 0xa3014314l, 0x4e0811a1l, 0xf7537e82l, 0xbd3af235l, 0x2ad7d2bbl, 0xeb86d391l, }; /* ** 要对字符串按bit位填充一个1,然后紧接着若干个0, ** 使得它的bit长度 % 512 == 448,最多需要拼接512bit, ** 也就是 64 字节 */ const unsigned char PADDING[64] = { 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 Encode(unsigned char* str_out, const unsigned long* ulong_in, int char_len) { /************************************************** @brief : 因为X86体系结构是小端模式,所以需要对输入的unsigned long数组进行字节序调整, 以满足可以按unsigned long为单位(按4字节为单位)读取。 @param[out] : 无 @param[in] : const unsigned long* ulong_in 需要调整字节序的unsigned long数组 @param[in] : unsigned char* str_out 已调整完字节序的字符数组 @param[in] : int char_len 以字符位单位的数组的长度 **************************************************/ //因为输入的字符串是4的倍数,所以肯定能被4整除 // circle_time是循环的次数,也就是 int circle_time = char_len / 4; for (int i = 0; i < circle_time; i++) { //取unsigned long的最低位的1字节 str_out[i*4] = (unsigned char)(ulong_in[i] & 0xff); //取unsigned long的次低位的1字节 str_out[i * 4 + 1] = (unsigned char)((ulong_in[i] >> 8) & 0xff); //取unsigned long的次高位的1字节 str_out[i * 4 + 2] = (unsigned char)((ulong_in[i] >> 16) & 0xff); //取unsigned long的最高位的1字节 str_out[i * 4 + 3] = (unsigned char)((ulong_in[i] >> 24) & 0xff); } } void Decode(unsigned long* ulong_out, const unsigned char* str_in, int char_len) { /************************************************** @brief : 因为X86体系结构是小端模式,所以需要对输入的unsigned char数组进行字节序调整, 以满足可以按unsigned long为单位(按4字节为单位)读取。 @param[out] : 无 @param[in] : const unsigned char* str_in 需要调整字节序的unsigned char数组 @param[in] : unsigned long* ulong_out 已调整完字节序的unsigned long数组 @param[in] : int char_len 以字符位单位的数组的长度 **************************************************/ //因为输入的字符串也肯定是4的倍数,所以肯定能被4整除 // circle_time是循环的次数,也就是 int circle_time = char_len / 4; for (int i = 0; i < circle_time; i++) { ulong_out[i] = ((unsigned long)str_in[i * 4]) | (((unsigned long)str_in[i * 4 + 1]) << 8) | (((unsigned long)str_in[i * 4 + 2]) << 16) | (((unsigned long)str_in[i * 4 + 3]) << 24); } } void Transform_64(const unsigned char block[64]) { /************************************************** @brief : 将512bit转换成可以(在小端模式下)按照64bit为单位正确读取的字节序 @param[out] : 无 @param[in] : const unsigned char block[64] 输入是以字节为单位排序的512bit **************************************************/ //新建一个512bit的数组用以保存调整好的字节序 unsigned long Message[16]; //进行调整 Decode(Message, block, 64); //对调整完的字节序进行主轮函数(每一主轮函数共有64次运算) H_getMD5(Message); } void H_getMD5(const unsigned long Message[16]) { /************************************************** @brief : 输入一组512bit的数据,进行64次运算,更新State[0]~State[3] @param[out] : 无 @param[in] : unsigned long Message[16],4字节*16=64字节,等于512bit **************************************************/ unsigned long A = State[0], B = State[1], C = State[2], D = State[3]; unsigned long Func; int g; for (int i = 0; i < 64; i++) { //64次运算,每轮16次 if (i < 16) { //第一轮 Func = F(B, C, D); g = i; } else if (i < 32) { //第二轮 Func = G(B, C, D); g = (5 * i + 1) % 16; } else if (i < 48) { //第三轮 Func = H(B, C, D); g = (3 * i + 5) % 16; } else { //第四轮 Func = I(B, C, D); g = (7 * i) % 16; } unsigned long temp = D; D = C; C = B; B = B + Left_Shift(A + Func + K_Sin[i] + Message[g], shift_bit[i]); A = temp; } State[0] += A; State[1] += B; State[2] += C; State[3] += D; } void Init_State() /************************************************** @brief : 初始化MD5的State[0]、State[1]、State[2]、State[3],和Count[0]、Count[1] @param[out] : 无 @param[in] : 无 **************************************************/ { Count[0] = 0; Count[1] = 0; /* 初始化State[]为各自对应的4个幻数 */ State[0] = 0x67452301lL; State[1] = 0xefcdab89lL; State[2] = 0x98badcfelL; State[3] = 0x10325476lL; } void Update(const unsigned char* input, int inputLen) /************************************************** @brief : 最后一次的512bit运算,要对字符串追加100...00比特流和64bit长度, 再继续运算即可求得MD5值 @param[out] : 无 @param[in] : const unsigned char* input 表示要填充进buffer[64]数组里的字符串 @param[in] : int inputLen 表示要填充进buffer[64]数组里的字符串的长度 **************************************************/ { int i,index, partLen; /* 用 index 保存未更新前的Count[0]里记录的字节数 */ index = (unsigned int)((Count[0] >> 3) & 0xFF); /* * 更新Count[0],因为要拼接上inputLen长度的字节 */ Count[0] += ((unsigned long)inputLen << 3); if (Count[0] < ((unsigned long)inputLen << 3)) { //如果小于,说明Count[0]溢出了,所以Count[1]需要加1 Count[1]++; } /* * Count[1]和Count[0]都是4字节32bit长的,把它们放在一起看,就是一个64bit的长度, * Count[1] 增1,表示字符串的bit长度要增加 1*2^32次方,inputLen是要在字符串后面 * 增加的字符串字节长度,inputLen先<<3,再>>32,就是Count[1]要增加的数目了 */ Count[1] += ((unsigned long)inputLen >> 29); // partLen表示还缺多少个字节,才够512bit(一组) partLen = 64 - index; /* Transform as many times as possible. */ if (inputLen >= partLen) //如果inputLen(要更新的字节数)大于等于缺的字节数,那也意味目前够512bit(一组)了 { //分阶段把数据填入buffer里。因为buffer只有512bit,所以要分批 memcpy(&buffer[index], input, partLen); Transform_64(buffer); for (i = partLen; i + 63 <= inputLen; i += 64) { Transform_64(&(input[i])); } index = 0; } else { i = 0; } /* * 上面的都是按照一整组一整组来处理的, * 有可能剩下的,不够一整组,需要对剩下的单独处理, */ if ((inputLen - i) != 0) { memcpy(&buffer[index], &input[i], inputLen - i); } } void Final(unsigned char digest[16]) /************************************************** @brief : 最后一次的512bit运算,要对字符串追加100...00比特流和64bit长度, 再继续运算即可求得MD5值 @param[out] : 无 @param[in] : unsigned char digest[16] 表示用来存放最终MD5值的字符数组 **************************************************/ { unsigned char ulong2uchar[8]; int char_len, padLen; /** 把unsigned long类型的Count数组里的值,转换成在小端模式下, ** 使其以usnigned char为单位,也能正常读取。 **/ Encode(ulong2uchar, Count, 8); /** 填充100000..000的比特流,使得字符串的bit长度 % 512 == 448 ** 也就是字节的长度 % 64 == 56 **/ //Count里存的是字符串的bit长度,>>3的结果是字节长度 char_len = (int)((Count[0] >> 3) & 0x3f); //padLen表示要填充的字节的长度,如果char_len<56,只需要补齐到56字节就行 //如果char_len ≥ 56,那么总共的组数就要再多一组(512bit)了, //要填充的字节数是(64-(char_len - 56)),也就是(120 - char_len) padLen = (char_len < 56) ? (56 - char_len) : (120 - char_len); /** ** 给字符串加上要填充的100..000的bit,只是按照字节的形式填充而已, ** padLen是 要填充的bit长度 除以 8,也就是要填充的字节长度 **/ Update(PADDING, padLen); /** ** 给字符串加上64bit(也就是8字节)的长度, ** 如果长度大于2^64,只取长度的低64位 **/ Update(ulong2uchar, 8); /* ** 把运算出来的最终的State数组,存放在digest里 ** 同时调成在小端模式下,以字节为单位,也能正确读取出来 */ Encode(digest, State, 16); } void Data(const unsigned char* data, int len, unsigned char digest[16]) /************************************************** @brief : 调度MD5核心算法 @param[out] : 无 @param[in] : const char* data 表示要进行MD5运算的字符串 @param[in] : int len 表示要进行MD5运算的字符串的长度 @param[in] : unsigned char digest[16] 表示用来存放最终MD5值的字符数组 **************************************************/ { Init_State(); //初始化State数组和Count数组 Update(data, len); //把要进行MD5运算的字符串,填写到buffer[64]数组里 Final(digest); //最后一次主轮函数,要对字符串追加100...00比特流和64bit长度, //再继续运算即可求得MD5值 } char* getMD5(const char* data, int len, char* buf) { /************************************************** @brief : 得到字符串的MD5值 @param[out] : char* 返回一个指向32个小写字符的字符指针,每个字符的范围是(0~f) @param[in] : const char* data 表示要进行MD5运算的字符串 @param[in] : int len 表示要进行MD5运算的字符串的长度 @param[in] : char* buf 表示用来存放最终MD5值的字符指针 **************************************************/ unsigned char digest[16]; Data((const unsigned char*)data, len, digest); char* p = buf; for (int i = 0; i < 16; i++) { *p = "0123456789abcdef"[digest[i] >> 4]; p++; *p = "0123456789abcdef"[digest[i] & 0x0f]; p++; } *p = 0; return buf; } int main() { char* out = new char[1000]; const char* str = "0123456789"; cout << getMD5(str, strlen(str), out) << endl; delete[] out; return 0; } // 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单 // 调试程序: F5 或调试 >“开始调试”菜单 // 入门使用技巧: // 1. 使用解决方案资源管理器窗口添加/管理文件 // 2. 使用团队资源管理器窗口连接到源代码管理 // 3. 使用输出窗口查看生成输出和其他消息 // 4. 使用错误列表窗口查看错误 // 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目 // 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
代码里包含一次测试用例,测试的字符串为“0123456789”,运算结果为“781e5e245d69b566979b86e28d23f2c7”。
和在网上随便找的网站对同一字符串进行MD5消息摘要运算,结果一致。
所以,代码本身逻辑本身大概率是没有问题。
有问题欢迎指出。