5.3 MD5信息摘要算法详解-lmn
01 MD5概要
MD5信息摘要算法,一种被广泛使用的密码散列函数,提供消息完整性,MD5的长度为128位(按照16进制编码,16字节,得到32个字符)是一个散列值(hash value)。
MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。
1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。
2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途[1]。
02 MD5特性
- 长度固定,任意长度的数据都会输出相同长度的md5值
- 不可逆
- 对原数据进行任何改动,改变一个字节输出数据也会有很大差异
在浏览各种博客的时候,发现很多很多评论都在指出各种文章中,作者定义md5为加密算法,在这里,我非常认同其中一篇[2]的观点:
认为MD5属于加密算法的人觉得MD5与其他加密算法相似,都为将明文进行运算等各种操作得到与明文完全不相同的数据,而认为MD5不属于加密算法的人认为没有解密算法,无法称为加密算法,当然,第二种相比来说更加严谨,默认情况下不称为加密算法。
03 MD5应用
- 密码保护
“如果直接将密码信息以明码方式保存在数据库中,不使用任何保密措施,系统管理员就很容易能得到原来的密码信息,这些信息一旦泄露, 密码也很容易被破译。”,利用MD5,在后台可以不记录密码本身,并且在后台进行校验密码
- 电子签名
通过检查文件前后 MD5 值是否发生了改变,就可以知道源文件是否被改动。 - 在百科上还看到一个很有用的应用实例,就是筛选垃圾邮件,
1)建立邮件MD5库,分别储存邮件的 MD5 值、允许出现的次数(假定为 3)和出现次数(初值为零)。
2)对每一封收到的邮件进行MD5计算,并与MD5库中的值进行比较
3)如未发现相同的 MD5 值,说明此邮件是第一次收到,将此 MD5 值存入资料库,并将出现次数置为1
4)如发现相同的 MD5 值,说明收到过同样内容的邮件,将出现次数+1[3]
04 MD5实现算法
首先需要对信息进行填充,对输入数据进行按位(bit)数据填充,长度进行补齐使消息的长度 X 变成
X = n*512+448
也就是
X mod 512=448
在进行填充时,在消息后面进行填充,填充第一位为1,其余为0
如果数据长度正好为:n*512+448
我们也要对数据进行填充,变成 (n+1)*512+448
之后的448为(448 = 512-64),64位用于填充原消息的长度。
最终被用来处理的数据就为
n*512+448+64 = N*512
四个幻数在内存地址上从低到高为:
A = 0×01234567
B = 0×89ABCDEF
C = 0xFEDCBA98
D = 0×76543210
大小为 4*4 = 16字节
每个标准幻数的大小 与 md5输出大小一致
在程序中,使用小端字节序,md5为四个标准幻数进行多轮哈希运算
(小端模式:高字节在前,低字节在后)
则在程序中应该为:
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476
此处插播一下大端小端问题
关于大小端字节序错误的观念比如
- 大小端字节序指的是数据在电脑上的二进制顺序
这里一定要记住:大小端字节序指的是数据在电脑上存储的字节顺序!!!
例如:数据 0x 11 22 33 44
小端存储为:44 33 22 11(低位放在低地址,高位放在高地址)
大端存储为:11 22 33 44(低位放在高地址,高位放在低地址)
为什么会有大小端模式之分呢?
在之前听过鹏哥的C语言课程中有详细的解释过:
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit ,但是在C语言中除了 8bit 的 char 之外,还有 16bit 的 short 型, 32bit 的 long 型(要看具体的编泽器),另外 ,对于位数大于8位的处理器,例如 16 位或者 32 位的处理器,由于寄存器宽度大于个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
当然也可以自己检测大小端:
//因为使用共用体,所以返回值就可以判断大小端
int check_sys()
{
union
{
char c;//1
int i;//4
}u;
u.i = 1;
//返回1,表示小端
//返回0,表示大端
return u.c;
}
int main()
{
int ret = check_sys();
if(1 == ret)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
回归正题:
在数据填充部分,我们知道,我们已经将原始数据填充为:N个512位(n个64字节)
然后将每个64字节分成16份
每份4字节
分好后,定义4个函数
FF(a,b,c,d,Mi,s,tj)
GG(a,b,c,d,Mi,s,tj)
HH(a,b,c,d,Mi,s,tj)
II(a,b,c,d,Mi,s,tj)
其中,a,b,c,d表示4个标准幻数
第五个为每一份的四字节数据,也就是第i个子分组(一共有16个分组)
第六个和第七个表示为固定长度,用于执行逻辑与、或、非、异或运算、加法运算、移位运算
也就是每个512位(64字节)的分组,划分成16份,每份都运行一次FF,GG,HH,II中的一个函数
每个函数为一轮的话,要运行4*16=64次,每一次改变第一个参数的值
比如在第一轮的第一次运算[3]:
FF(a,b,c,d,M0,s,t1)
a = a+(F(b,c,d)+M0+t1);
a = (s<<a) | (s>>(32-a));
a = a + b;
在第一轮的第一次运算:
FF(b,c,d,a,M1,s,t2)
b = b+(F(c,d,a)+M1+t2);
b = (s<<b) | (s>>(32-b));
b = b + c;
而我们运行的64次则为:
第一轮
FF(a,b,c,d,M0,7,0xd76aa478)
FF(d,a,b,c,M1,12,0xe8c7b756)
FF(c,d,a,b,M2,17,0×242070db)
FF(b,c,d,a,M3,22,0xc1bdceee)
FF(a,b,c,d,M4,7,0xf57c0faf)
FF(d,a,b,c,M5,12,0×4787c62a)
FF(c,d,a,b,M6,17,0xa8304613)
FF(b,c,d,a,M7,22,0xfd469501)
FF(a,b,c,d,M8,7,0×698098d8)
FF(d,a,b,c,M9,12,0×8b44f7af)
FF(c,d,a,b,M10,17,0xffff5bb1)
FF(b,c,d,a,M11,22,0×895cd7be)
FF(a,b,c,d,M12,7,0×6b901122)
FF(d,a,b,c,M13,12,0xfd987193)
FF(c,d,a,b,M14,17,0xa679438e)
FF(b,c,d,a,M15,22,0×49b40821)
第二轮
GG(a,b,c,d,M1,5,0xf61e2562)
GG(d,a,b,c,M6,9,0xc040b340)
GG(c,d,a,b,M11,14,0×265e5a51)
GG(b,c,d,a,M0,20,0xe9b6c7aa)
GG(a,b,c,d,M5,5,0xd62f105d)
GG(d,a,b,c,M10,9,0×02441453)
GG(c,d,a,b,M15,14,0xd8a1e681)
GG(b,c,d,a,M4,20,0xe7d3fbc8)
GG(a,b,c,d,M9,5,0×21e1cde6)
GG(d,a,b,c,M14,9,0xc33707d6)
GG(c,d,a,b,M3,14,0xf4d50d87)
GG(b,c,d,a,M8,20,0×455a14ed)
GG(a,b,c,d,M13,5,0xa9e3e905)
GG(d,a,b,c,M2,9,0xfcefa3f8)
GG(c,d,a,b,M7,14,0×676f02d9)
GG(b,c,d,a,M12,20,0×8d2a4c8a)
第三轮
HH(a,b,c,d,M5,4,0xfffa3942)
HH(d,a,b,c,M8,11,0×8771f681)
HH(c,d,a,b,M11,16,0×6d9d6122)
HH(b,c,d,a,M14,23,0xfde5380c)
HH(a,b,c,d,M1,4,0xa4beea44)
HH(d,a,b,c,M4,11,0×4bdecfa9)
HH(c,d,a,b,M7,16,0xf6bb4b60)
HH(b,c,d,a,M10,23,0xbebfbc70)
HH(a,b,c,d,M13,4,0×289b7ec6)
HH(d,a,b,c,M0,11,0xeaa127fa)
HH(c,d,a,b,M3,16,0xd4ef3085)
HH(b,c,d,a,M6,23,0×04881d05)
HH(a,b,c,d,M9,4,0xd9d4d039)
HH(d,a,b,c,M12,11,0xe6db99e5)
HH(c,d,a,b,M15,16,0×1fa27cf8)
HH(b,c,d,a,M2,23,0xc4ac5665)
第四轮
II(a,b,c,d,M0,6,0xf4292244)
II(d,a,b,c,M7,10,0×432aff97)
II(c,d,a,b,M14,15,0xab9423a7)
II(b,c,d,a,M5,21,0xfc93a039)
II(a,b,c,d,M12,6,0×655b59c3)
II(d,a,b,c,M3,10,0×8f0ccc92)
II(c,d,a,b,M10,15,0xffeff47d)
II(b,c,d,a,M1,21,0×85845dd1)
II(a,b,c,d,M8,6,0×6fa87e4f)
II(d,a,b,c,M15,10,0xfe2ce6e0)
II(c,d,a,b,M6,15,0xa3014314)
II(b,c,d,a,M13,21,0×4e0811a1)
II(a,b,c,d,M4,6,0xf7537e82)
II(d,a,b,c,M11,10,0xbd3af235)
II(c,d,a,b,M2,15,0×2ad7d2bb)
II(b,c,d,a,M9,21,0xeb86d39
4个函数都执行了16次
然后再将计算得到的4个标准幻数的值与上一轮或开始值相加,然后作为第二轮输入,重复每一次
最终得到的更新后的标准幻数,按顺序得出我们的md5值,这里再强调一下,前面也说了是小端排序,所以应该是这样的
当然了md5的c语言实现代码在之后进行详细补充,加密流程可以参考[4]是个非常详细的过程
05 MD5在线网站
参考链接
[1] https://baike.baidu.com/item/MD5/212708?fr=aladdin
[2] https://blog.csdn.net/u012611878/article/details/54000607
[3] https://blog.csdn.net/Oliver_xpl/article/details/90214896?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164493954316780269867160%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164493954316780269867160&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_ulrmf~default-2-90214896.pc_search_insert_ulrmf&utm_term=MD5&spm=1018.2226.3001.4187
[4] https://www.bilibili.com/video/BV1u44y1z7t1?from=search&seid=18258260943716626789&spm_id_from=333.337.0.0
请登录后查看回复内容