算法还原之MD5
哈希算法—MD5介绍
- 算法的输入:任意长度的明文消息
- 算法输出:128比特长,或者说32个十六进制数的消息摘要
MD5的应用场景
1、数据完整性校验:MD5算法常用于验证数据的完整性。在数据传输过程中,发送方可以计算数据的MD5哈希值将其发送给接收方。接收方收到数据后,再次计算哈希值并与发送方提供的哈希值进行比较。如果两者匹配,则说明数据在传输过程中没有被篡改。
2、密码存储:MD5算法也常用于密码存储。将用户的密码通过MD5哈希后存储到数据库中,即时数据库被泄露,攻击者也无法直接获取用户的明文你密码。然而,由于MD5算法存在已知的安全漏洞(如彩虹表攻击和碰撞攻击),现在已不推荐使用MD5来存储密码。更安全的做法是使用加盐哈希(如bcrypt或Argon2)。
MD5算法原理
MD5算法的实现,可以分为以下4个步骤:
MD5算法的整体流程如上图所示,主要分为4步:处理原文、设置初始值、循环加工、拼接结果。
(一)处理原文
处理原文就是一个关键词:补位,此处的补位有两个地方需要补。那具体怎么补位呢?
1、填充。如果输入信息的长度(bit)对512求余的结果不等于448,就需要填充使得对512求余的结果等于448。填充的方法是填充一个1和n个0。填充完后,信息的长度就为N*512+448(bit);
到这里,就会有两个问题:
- 为什么是对512取余,而且为什么补位要补到对512取余后为448?
- 如果原文的长度本来就是对512取余是448呢,是不是就不需要进行填充位的填补了?
首先我们来解决第2个问题:假设原文长度为len,且mod(len, 512) = 448,则需要再补充512位的填充位。假设填充位长度为appendLen,则有伪代码:
|
至于第1个问题,我们接着往下看:
2、补数据长度:这里需要补64位的原文长度,我们会发现,补充齐64位后,整体的长度就是512的整数倍了。这里就可以回答上面的第一个问题了:MD5是一种基于block的算法,要求每次处理的block都是512bits。64位的数据长度,即最长可以是2^64 。那如果原文的长度len > 2^64呢?则取len的低阶64位进行填充。
此时的数据是16字(32位)的整数倍,用M[0,…,N-1]表示此时的数据,其中N为16的倍数。如下图所示:
(二)设置初始值
装入标准幻数(四个整数)。标准的幻数(物理顺序)是(A=(01234567)16,B=(89ABCDEF)16,C=(FEDCBA98)16,D=(76543210)16)。
如果在程序中定义应该是:(A=0X67452301L
,B=0XEFCDAB89L
,C=0X98BADCFEL
,D=0X10325476L
)这样表示的原因是因为,数据在内存在为小端存储,举个例子,假设我们有一个16位的整数0x1234
(这里0x
表示十六进制数),在小端存储的系统中,这个数会被存储为0x34 0x12
,其中0x34
存储在低地址,0x12
存储在高地址。
A=0x01234567 |
(三)循环加工
这一步是整个算法最重要的一步。
图中,ABCD就是哈希值的四个分组,每一次循环都会让旧的ABCD产生新的ABCD。一共进行多少次循环呢?由处理后的原文长度决定。
假设经过第一步处理原文之后的长度为M,则主循环次数L=M÷512,每个主循环中包含512÷32*4=64次子循环,分为4组16次。上面这张图表达的是一次子循环的流程。
- 主循环次数 = M / 512
- 每个主循环中包含 512 / 32 * 4 = 64 次 子循环
其中F是一个非线性函数。MD5用到的非线性函数有下面四种,4组循环中依次使用FGHI。
F(X,Y,Z) =(X&Y) | ((~X) & Z) |
其中Mi为原文的一个分组,长度为32bits,每次循环所用到的分组编号不同,会交替使用到M0到M15中的数据。
而Ki则是一个32bits的常量,具体的计算公式如下:
Ki=floor(2^64*sin(i)) i的范围是1~64,单位是弧度
而<<<s则代表移位操作,注意:这里的移位是循环移位!而移多少位,则如下:
[7,12,17,22 ] |
第一组16次循环中依次使用【7,12,17,22】,使用4次,以此类推。所以经过一次循环后可以发现(其中=左边为新,等号右边全部使用旧的abcd):
A—>经过运算—>B
B—>直接—>C
C—>直接—>D
D—>直接—>A
a=d |
而一次主循环的4组16次子循环依次如下:
第一轮 |
具体的实现和原理这些,都可以参考如下内容:
https://mp.weixin.qq.com/s/Yem3oWb3sg3kh4j-MjWbcA
实现标准MD5算法
|
MD5函数算法描述
初始化向量
首先是初始化链接向量。需要4个初始值,每个长32bit。即8个十六进制数。它是运算的主要部分(这块是小端存储)
消息填充
附加填充,使得其比特长度除以512余数448。填充方式为:先填充一个1,后面跟上足够多的0,一直到符合要求。
在上一步新生成的字符串后面加上消息长度。消息长度为8字节=64bit
首先将初始长度*8放到填充字符串的后面;如果初始长度超出32位表示范围,就只取它超出的部分。总共是8个字节
运算
下面是一个循环 “ for(offset=0; offset<new_len; offset += (512/8)) “ 每64个字节为一组,进行分组处理。
如果我们本身的字符串长度小于448,那也就是不用填充。
将64个字节,又分为16个小组(4字节=32bit)
下面将4个初始化链接向量进行了处理
可以表示为下面图所示的样子。。。。(具体实现可以网上查一下)
A—>经过运算—>B
B—>直接—>C
C—>直接—>D
D—>直接—>A
可以看到上图中有F、Mi、Ki等标识。
- F:可以理解为一个函数,下面会细说
- Mi:表示一种运算,简单理解就是+-*/
- Ki:是一种参与运算的特定序列,如下所示。一般情况,该序列是固定的,也就是Ki如果没有修改过,是不会变化的。这个也是辨别当前算法是否为md5的一个思路。
其实图中的F确实表示的是函数,但不是一个函数,而是4个函数,接收三个参数
F(X,Y,Z) = (X&Y)|((~X)&Z) |
MD5汇编算法识别
【小技巧】
可以借助PEID中的插件【Krypto ANALyzer】实现对so中的加密算法进行简单分析。
IDA加载so文件后,定位到md5函数为止
有几个关键点,可以帮助判断当前算法是否为MD5
一、4个初始化链接向量
二、两个固定数组(加法常数矩阵+循环左移表)
上面这两个点,可以作为md5算法识别的参考。
同时,如果对MD5算法魔改,其实也是修改常量ABCD
和 加法常数矩阵
。