【数据加解密】MD5检验算法的原理及实现

MD5检验算法的原理及实现

MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 中被加以规范。
数据(如一段文字)运算变为另一固定长度值,是散列算法的基础原理。

1996年后被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途, 当前,由于MD5算法因其普遍、稳定、快速的特点,仍广泛应用于普通数据的错误检查、验证领域。

以对用户密码验证为例,当前大部分服务端程序保存用户密码的md5值,而不是原密码,服务端程序在验证用户的输入密码时,先将用户输入字节进行md5换算再与数据库中保存的md5值进行验证.

MD5散列函数的特点是:即使修改原数据的一个微小字节也会导致最终计算出的散列值发生巨大的变化。

算法

我们首先假设有一个 b 个bit的数据作为输入,然后我们希望根据某种规则计算出它的信息摘要。这里b是任意大小的数据,也不需要是8bit的倍数。 我们假设数据的字节如下:
                 m_0 m_1 …  m_{b-1}
后续的5个步骤是计算出该数据的散列值的步骤

步骤1 填充位

输入数据首先被"填充"(拓展),以保证 它的长度%512 = 448。填充总是被执行,即使输入数据的长度已经满足%523=448。
填充的具体方式如下:
在输入字节数据后追加一个位"1",然后追加位"0"使长度%512=448。总之,至少一个位(位1)、至多512位被追加到源数据后

步骤2 追加消息长度信息

追加一个64位的数据,该数据表示b的长度(步骤1填充之后的),如果b的长度大于2^64,那么只使用该长度值二进制表示的低64位(比如如果长度是0x82a89c7434,则只使用0xa89c7434)。

追加长度信息后,此时数据的长度是 512的倍数,并且也是16的倍数。

步骤3 初始化 MD Buffer

创建一个4-world 大小的 buffer (A、B、C、D)用于计算消息摘要。这里的A、B、C、D都是32位寄存器。这些寄存器初始化为以下十六进制低阶字节的值
          word A: 01 23 45 67
          word B: 89 ab cd ef
          word C: fe dc ba 98
          word D: 76 54 32 10
在伪代程序中表示为

var int h0 := 0x67452301
var int h1 := 0xEFCDAB89
var int h2 := 0x98BADCFE
var int h3 := 0x10325476

步骤4 以 16-World Blocks 处理消息

我们首先定义四个辅助函数,每个函数输入三个32位的单词,输出一个32位的单词。
**F(X,Y,Z) **= ( **X **& **Y **) | ( ( ~**X **) & **Z **)
G(X,Y,Z) = ( X & Z ) | ( Y & ( ~Z ) )
**H(X,Y,Z) **= XYZ
**I(X,Y,Z) **= Y^( X | ( ~Z ) )

定义 一个包含64个元素的表 r[1 … 64] ,该表每个位置 i 表示 第i轮使用 leftRotate的位数 (left rotate 即从高位移入低位)

定义一个包含64个元素的表 K[1 … 64],该表由正弦函数的值组合合成,每一个位置 i 的值为 floor(abs(sin(i + 1)) × 2^32)。

处理流程的伪代码如下:
**

//Note: All variables are unsigned 32 bits and wrap modulo 2^32 when calculating
var int[64] r, k

//r specifies the per-round shift amounts
r[ 0..15]:= {7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22} 
r[16..31]:= {5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20}
r[32..47]:= {4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23}
r[48..63]:= {6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21}

//Use binary integer part of the sines of integers as constants:
for i from 0 to 63
    k[i] := floor(abs(sin(i + 1)) × 2^32)

//Initialize variables:
var int h0 := 0x67452301
var int h1 := 0xEFCDAB89
var int h2 := 0x98BADCFE
var int h3 := 0x10325476

//Pre-processing:
append "1" bit to message
append "0" bits until message length in bits ≡ 448 (mod 512)
append bit length of message as 64-bit little-endian integer to message

//Process the message in successive 512-bit chunks:
for each 512-bit chunk of message
    break chunk into sixteen 32-bit little-endian words w[i], 0 ≤ i ≤ 15

    //Initialize hash value for this chunk:
    var int a := h0
    var int b := h1
    var int c := h2
    var int d := h3

    //Main loop:
    for i from 0 to 63
        if 0 ≤ i ≤ 15 then
            f := (b and c) or ((not b) and d)
            g := i
        else if 16 ≤ i ≤ 31
            f := (d and b) or ((not d) and c)
            g := (5×i + 1) mod 16
        else if 32 ≤ i ≤ 47
            f := b xor c xor d
            g := (3×i + 5) mod 16
        else if 48 ≤ i ≤ 63
            f := c xor (b or (not d))
            g := (7×i) mod 16
 
        temp := d
        d := c
        c := b
        b := leftrotate((a + f + k[i] + w[g]),r[i]) + b
        a := temp
    Next i
    //Add this chunk's hash to result so far:
    h0 := h0 + a
    h1 := h1 + b 
    h2 := h2 + c
    h3 := h3 + d
End ForEach
var int digest := h0 append h1 append h2 append h3 //(expressed as little-endian)

上述算法流程图如下:

image.png

Figure 1. 一个MD5运算— 由类似的64次循环构成,分成4组16次。F 一个非线性函数;一个函数运算一次。M 表示一个 32-bits 的输入数据,K 表示一个 32-bits 常数,用来完成每次不同的计算。

更直观计算过程是 以64byte(512bit)为chunk执行以下函数过程
image.png
image.png
image.png
image.png
其中

最后将缓存变量的拷贝和计算结果相加:
image.png

接着继续对下一个 64bit进行计算,直到计算完所有的信息。

步骤5 输出结果

最终散列函数的结果由最终的A ,B ,C ,D 组成,拼接这4个变量,其中其中A为最低位,D为最高位。

md5 hash破解

我们知道使用md5算法不论源数据多大最终计算的结果都是256位的,理论上得到一个md5值是无法还原得到它的源数据的。但是事实上我们还是有方法在数据有限域内破解md5的。举个简单的攻击的例子,由于md5算法都是公开的,我们可以根据网上的一些弱口令、或者提前随机生成很多字符串,算出这些字符串的md5值,那么我们就形成了一个 {md5值:源字符串}的表,如果得到的md5值的源数据刚好落在这个表中,那么我们就得到这个hash值可能的源数据。

除了预先计算md5,还有一种使用 彩虹表的方式进行碰撞攻击,具体参见 wiki这里有一些免费的彩虹表数据

hash 碰撞

我们知道hash函数是将任意数量的数据映射成一个固定长度的字符串,所以一定存在不同的输入经过hash之后变成相同的字符串的情况。加密hash函数(Cryptographic hash function)在设计的时候希望使这种碰撞攻击实现起来成本难以置信的高。但时不时的就有密码学家发现快速实现hash碰撞的方法。最近的一个例子就是MD5,它的碰撞攻击已经实现了。

碰撞攻击是找到另外一个跟原密码不一样,但是具有相同hash的字符串。但是,即使在相对弱的hash算法,比如MD5,要实现碰撞攻击也需要大量的算力(computing power),所以在实际使用中偶然出现hash碰撞的情况几乎不太可能。一个使用加盐MD5的密码hash在实际使用中跟使用其他算法比如SHA256一样安全。不过如果可以的话,使用更安全的hash函数,比如SHA256, SHA512, RipeMD, WHIRLPOOL等是更好的选择。

哈希碰撞的概率取决于2个元素:

  • 取值空间的大小(即哈希值的长度)
  • 整个生命周期中,哈希值的计算次数

加盐

因此,在一些敏感的数据保存,比如用户密码,最简单的提升加密强度的方法就是“加盐”,即在对用户密码进行md5运算前 额外添加上一个随机字符串,再进行hash运算。

盐的长度不能太短,如果太短攻击者也可以提前针对可能的盐制作查询表。

参考

https://en.wikipedia.org/wiki/MD5#Simple_Implementation
加盐hash保存密码的正确方式
哈希碰撞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卓修武

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值