md5算法简要叙述:
md5
以64Bytes分组来处理输入的信息,而每一分组又被划分为16个4Bytes子分组,经过一系列处理后,算法的输出为四个4Bytes分组的级联。
拿到待加密信息后,首先需要在末尾填充一个0x80,以及无数个0x00,使得填充后信息的总长度(Bytes)对64求余的结果等于56。之后再填充一个以8Bytes表示的填充前信息长度(bits)。
接下来进入主循环的四轮运算。每一轮都会调用16次相同的函数,但参数不同。每一轮所掉用的函数均不相同。下面直接来看看主循环所掉用的所有函数:
第一轮:
FF(a, b, c, d, *M[0], 7, 0xd76aa478);
FF(d, a, b, c, *M[1], 12, 0xe8c7b756);
FF(c, d, a, b, *M[2], 17, 0x242070db);
FF(b, c, d, a, *M[3], 22, 0xc1bdceee);
FF(a, b, c, d, *M[4], 7, 0xf57c0faf);
FF(d, a, b, c, *M[5], 12, 0x4787c62a);
FF(c, d, a, b, *M[6], 17, 0xa8304613);
FF(b, c, d, a, *M[7], 22, 0xfd469501);
FF(a, b, c, d, *M[8], 7, 0x698098d8);
FF(d, a, b, c, *M[9], 12, 0x8b44f7af);
FF(c, d, a, b, *M[10], 17, 0xffff5bb1);
FF(b, c, d, a, *M[11], 22, 0x895cd7be);
FF(a, b, c, d, *M[12], 7, 0x6b901122);
FF(d, a, b, c, *M[13], 12, 0xfd987193);
FF(c, d, a, b, *M[14], 17, 0xa679438e);
FF(b, c, d, a, *M[15], 22, 0x49b40821);
第二轮:
GG(a, b, c, d, *M[1], 5, 0xf61e2562);
GG(d, a, b, c, *M[6], 9, 0xc040b340);
GG(c, d, a, b, *M[11], 14, 0x265e5a51);
GG(b, c, d, a, *M[0], 20, 0xe9b6c7aa);
GG(a, b, c, d, *M[5], 5, 0xd62f105d);
GG(d, a, b, c, *M[10], 9, 0x02441453);
GG(c, d, a, b, *M[15], 14, 0xd8a1e681);
GG(b, c, d, a, *M[4], 20, 0xe7d3fbc8);
GG(a, b, c, d, *M[9], 5, 0x21e1cde6);
GG(d, a, b, c, *M[14], 9, 0xc33707d6);
GG(c, d, a, b, *M[3], 14, 0xf4d50d87);
GG(b, c, d, a, *M[8], 20, 0x455a14ed);
GG(a, b, c, d, *M[13], 5, 0xa9e3e905);
GG(d, a, b, c, *M[2], 9, 0xfcefa3f8);
GG(c, d, a, b, *M[7], 14, 0x676f02d9);
GG(b, c, d, a, *M[12], 20, 0x8d2a4c8a);
第三轮:
HH(a, b, c, d, *M[5], 4, 0xfffa3942);
HH(d, a, b, c, *M[8], 11, 0x8771f681);
HH(c, d, a, b, *M[11], 16, 0x6d9d6122);
HH(b, c, d, a, *M[14], 23, 0xfde5380c);
HH(a, b, c, d, *M[1], 4, 0xa4beea44);
HH(d, a, b, c, *M[4], 11, 0x4bdecfa9);
HH(c, d, a, b, *M[7], 16, 0xf6bb4b60);
HH(b, c, d, a, *M[10], 23, 0xbebfbc70);
HH(a, b, c, d, *M[13], 4, 0x289b7ec6);
HH(d, a, b, c, *M[0], 11, 0xeaa127fa);
HH(c, d, a, b, *M[3], 16, 0xd4ef3085);
HH(b, c, d, a, *M[6], 23, 0x04881d05);
HH(a, b, c, d, *M[9], 4, 0xd9d4d039);
HH(d, a, b, c, *M[12], 11, 0xe6db99e5);
HH(c, d, a, b, *M[15], 16, 0x1fa27cf8);
HH(b, c, d, a, *M[2], 23, 0xc4ac5665);
第四轮:
II(a, b, c, d, *M[0], 6, 0xf4292244);
II(d, a, b, c, *M[7], 10, 0x432aff97);
II(c, d, a, b, *M[14], 15, 0xab9423a7);
II(b, c, d, a, *M[5], 21, 0xfc93a039);
II(a, b, c, d, *M[12], 6, 0x655b59c3);
II(d, a, b, c, *M[3], 10, 0x8f0ccc92);
II(c, d, a, b, *M[10], 15, 0xffeff47d);
II(b, c, d, a, *M[1], 21, 0x85845dd1);
II(a, b, c, d, *M[8], 6, 0x6fa87e4f);
II(d, a, b, c, *M[15], 10, 0xfe2ce6e0);
II(c, d, a, b, *M[6], 15, 0xa3014314);
II(b, c, d, a, *M[13], 21, 0x4e0811a1);
II(a, b, c, d, *M[4], 6, 0xf7537e82);
II(d, a, b, c, *M[11], 10, 0xbd3af235);
II(c, d, a, b, *M[2], 15, 0x2ad7d2bb);
II(b, c, d, a, *M[9], 21, 0xeb86d391);
可以看到,虽然有4种函数,但参数的种类均一致。下面就来说说每个参数的来历。
·
前四个参数a,b,c,d。它们第一次进入主循环的初始值分别为(注意:这里为大端字节序表示,通常我们用的计算机为小端系统):
a = 0x1234567
b = 0x89ABCDEF
c = 0xFEDCBA98
d = 0x7654321
之后再进入主循环时,它们的值为上一个64Bytes分组的计算结果。
·
第五个参数*M[j]。它分别代表了每个子分组(这里我使用了数组指针 unsigned int *M[16])。
·
第六个参数是个常数,函数中控制数据循环左移多少位。
·
第七个参数也是常数,不过他有来历。它是2^32*abs(sin(i))的整数部分,i从1~64。
接下来再来看看每个函数是如何实现的。
#define FF(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
arg1 = arg2 + ((arg1 + F(arg2, arg3, arg4) + arg5 + arg7) << arg6)
#define GG(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
arg1 = arg2 + ((arg1 + G(arg2, arg3, arg4) + arg5 + arg7) << arg6)
#define HH(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
arg1 = arg2 + ((arg1 + H(arg2, arg3, arg4) + arg5 + arg7) << arg6)
#define II(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
arg1 = arg2 + ((arg1 + I(arg2, arg3, arg4) + arg5 + arg7) << arg6)
大部分都是一样的,区别只在于内部调用的F()、G()、H()、I()。
最后再来看看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)))
经过主循环的这四轮运算,将a、b、c、d加回到上一个64Bytes分组的运算结果上(第一次进主循环的话,上一次的运算结果就是初始值),就得到了当前64Bytes分组的运算结果。
所有分组计算完之后,最后的运算结果如果为大端系统,直接将a、b、c、d连在一起输出就好了;如果为小端系统,需要进行转换。
算法实现步骤:
1
、初始化a、b、c、d。
2
、从标准输入读取待加密字符串,每次读取64Bytes。
3
、因为填充的信息在待加密信息的末尾,所以只要还没读到待加密信息末尾,前面的64Bytes分组(零个或多个)正常进行主循环运算。
4
、当读到的待加密信息不足64Bytes时,需要对待加密信息进行填充。填充后继续进行主循环运算。
5
、小端系统将运算结果转换后输出;大端系统直接级联输出。
算法实现注意事项:
1
、对于小端系统,a、b、c、d的初始值为:
a = 0x67452301
b = 0xEFCDAB89
c = 0x98BADCFE
d = 0x10325476
2
、填充到待加密信息末尾的填充前长度是以bit为单位的。比如填充前信息长度(也就是待加密信息的长度)为1Bytes,那么需要填充的值是8,而不是1。我在编码过程中就在这里摔过大马爬,-,.-。
3
、在进行主循环的运算时,如果上一次的计算结果为A、B、C、D。那么运算的前后应该如下这样:
unsigned int a = 0, b = 0, c = 0, d = 0;
a = A; b = B; c = C; d = D;
a
、b、c、d进入主循环进行运算,在运算过程中它们的值会改变。
A += a; B += b; C += c; D += d;
新的A、B、C、D才是这次的运算结果,而不是a、b、c、d,-,.-。(好吧,我承认,当时我还把a、b、c、d保存起来作为下一次的运算结果,-,.-)
4
、在主循环中,FF()、GG()、HH()、II()函数中的移位操作是循环左移!而不是左移,-,.-。
5
、如果填充前的待加密信息总长度(Bytes)对64求余的结果正好是56,那还需不需要填充那个0x80和无数个0x00?回答是肯定的。实际情况是,填充一个0x80,然后填充63个0x00,最后再填充8Bytes的填充前长度(bits)。看起来像没事儿找事儿,不过这确实是必要的,-,.-。