关于密码学:密码学哈希为什么要将盐加在明文后面

4次阅读

共计 2667 个字符,预计需要花费 7 分钟才能阅读完成。

家喻户晓,在做音讯认证或者签名时,仅应用 hash 函数安全性是不高的,容易蒙受字典和暴力破解 (https://www.cmd5.com/)。所以通常会应用带密钥或加盐的哈希算法作为音讯认证或者口令存储,正如题目所说,咱们在检索互联网上对于加盐的实现时,内容往往都是在明文 前面 加上随机值:

那做音讯认证的密钥或者盐可不可以加在明文后面呢?

这就引出本文的攻击方式。

MD5 算法计算逻辑

为了分明这个攻击方式原理,须要先理解下 md5 的计算逻辑。

md5 算法实质上是一种压缩算法,将长度小于2^64bit 的任意字符压缩成 128bit 固定长度字符。同 AES 之类的分组加密算法一样,md5 也须要进行分组计算。

如图所示,md5 的计算须要通过两个步骤:

  1. 分组 & 填充
  2. 具体计算

1. 分组 & 填充

首先会对明文依照 512bit 的长度进行分组,最初一个分组可能会产生长度有余 512bit,或者刚刚 512bit。

无论最初一个分组的长度是否刚好等于 512bit,依照填充规定都须要进行填充,具体细节:

  • 假如明文刚好能被 512 整除,须要新增一个分组,在开端 8*8=64bit 依照小端存储放入原始明文的长度,分组两头残余的 bit 依照10000000 的形式进行填充,造成一个总长 512bit 的新分组。
  • 假如整除 512bit 余数大于 0

    • 且余数大于512-8*8 =448 bit 则须要持续填充10000(0x80000…..)至下一个分组的 448bit,残余的 bit 依照小端存储填充原始明文长度
    • 余数小于 448bit,开端填充原始明文长度, 两头残余局部填充填充10000(0x80000…..)

以上两个步骤用代码示意即为:

        m_l = len(message)  # 原始音讯长度
        length = struct.pack('<Q', (m_l) * 8)  # 长度转化为小端 unsigned long long 8B
        blank_padding = b""message += b'\x80'  # 10000000
        # 此分组不足以填充长度时
        if 56 < len(message) < 64:
            blank_padding += b'\x00' * (56 + 64 - len(message))  # 填充至下一个分组
        # 分组能填充长度
        else:
            blank_padding = b'\x00' * (56 - len(message) % 64)  # 本分组填充
        if len(blank_padding) > 0:
            # 填充 10000
            message += blank_padding
        # 填充长度
        message += length

2. 具体计算

其实具体的计算过程,咱们不必关注,把这个过程当做一个黑盒(关注细节的能够关注文末 github 地址)就能够:

在上一步分组的根底上,第一组的分组的明文会和 128bit 的初始序列(幻数)做为输出进行压缩计算,初始序列是一组固定的值:

计算后会产生新的序列,做为下一组的“初始序列”和下一组的明文再次进行压缩计算,接下来的分组反复这种“上一组的输入作为下一组的输出”,最初一组的 128bit 输入即为最终的 md5 值。

哈希长度拓展攻打

理解了 md5 的计算逻辑,再回到这张图,上一次的的输入作为下一次的输出这种形式可能会导致一个问题。假如存在明文分组abc,明文分组产生 md5 的过程能够简化为:

  • 分组 a:h.a = md5(iv,a), md5 计算须要两个参数,iv 为初始序列,h.a 为压缩计算结果
  • 分组 b:h.ab = md5(h.a,b)
  • 分组 c 或者最终 md5: h.abc = md5(h.ab,c)

在这个过程当中,如果密钥被放在 a 分组当中,bc 为原音讯认证的明文,那攻击者能够在不晓得密钥的状况下,扩大明文长度,如减少明文 d,计算 abcd 的 hash,只须要晓得基于 abc 的 hash 值,即可生成新的 hash。

即:h.abcd = md5(h.abc,d)

这个过程即为 hash 长度扩大攻打。

接下来咱们依据理论的例子来实操下

实操

假如存在一个商城订单领取场景,订单的确认是通过前端参数给出,存在一个逻辑破绽能够通过前端参数来管制商品价格,从而实现“零元购”或者越权购买。

失常状况下进行购买,因为默认此用户只有 300 积分,所以会购买失败:

但如果进行参数价格 good_price 批改, 会因为签名校验不通过:

所以须要进行签名破解,先看下后端验证的逻辑:

<?php
    $total_score = 300;
    $flag = 'xxxxxxxxxxxx';
    $secret_key = "??????????????????????????????????????";  // 前端未知
    $post_data =urldecode(file_get_contents("php://input"));
    $user_sign = $_GET['signature'];
    $sign = md5($secret_key.$post_data);
    if ($user_sign === $sign) {$price = $_POST['good_price'];
        if ($price > $total_score){echo '对不起,您的积分余额有余,交易失败!';}else{echo "祝贺,购买胜利!$flag";}
    }else{echo '签名数据被篡改!';}

?>

后端存在一个签名逻辑,会验证用户的 post 参数加上密钥的 md5 值,如果用户批改了 post 参数,但因为不晓得密钥也就没产生成非法的 md5,所以验证会不通过。

如果没有理解过哈希长度扩大攻打,这个代码是没啥问题的,所以知识面决定攻击面。

而且这个中央密钥被放在了明文后面拼接,针对哈希长度扩大攻打,利用起来还挺简略的,能够应用现成的工具,比方hashpump, 依照提醒输出内容即可:

最初的明文中十六进制局部须要 url 编码,但因为 hashpump 须要编译,在 win 平台编译比拟麻烦,所以我本人实现了一个 md5 版本的利用工具(https://github.com/shellfeel/hash-ext-attack)

最初把失去的后果粘贴到 burp,胜利购买。

总结

文章剖析了下 md5 的计算逻辑,以及哈希长度扩大的攻打原理,对于此类攻打的修复,其实很简略只须要把密钥由加在明文后面改为明文前面,或者应用规范的 hmac 算法,hmac 算法外面会用密钥和明文做移位异或操作,从而加强 hash 的安全性,本文是以 md5 为例,其实对于有着相似 M - D 后果的 hash 算法都是能够这样利用的,比方 sha-0,sha-1,sha-2 等。

参考

  • 《白帽子讲 web 平安》
  • 先知社区
  • hashpump

公众号

欢送大家关注我的公众号,这里有干货满满的硬核平安常识,和我一起学起来吧!

正文完
 0