很多软件工程师都认为MD5是一种加密算法,然而这种观点是不对的。作为一个 1992 年第一次被公开的算法,到今天为止已经被发现了一些致命的漏洞。本文讨论MD5在密码保存方面的一些问题。

假设下面一个场景:

以上场景是非常完美的。但是由于人类的弱点,大部分人会选择非常简单易记,或者有特殊意义的字符串做为口令。攻击者只需要将一些常见密码提前计算一下哈希就可以找到数据库中很多用于存储的密码,Wikipedia 上有一份关于最常见密码的列表,在2016年的统计中发现使用情况最多的前25个密码占了调查总数的10%,虽然这不能排除统计本身的不准确因素,但是也足以说明仅仅使用哈希的方式存储密码是不够安全的。提前计算的HASH表称为彩虹表,存储着一些常见密码的哈希,当攻击者通过入侵拿到某些网站的数据库之后就可以通过预计算表中存储的映射来查找原始密码如下图所示。

Python工具箱系列(十七)

为了抵抗上述的暴力方法,可以使用md5加盐的策略,进一步强化md5暴力破解的难度。在上世纪70到80年代,早期版本的Unix系统就在/etc/passwrd中存储加盐的哈希密码,密码加盐后的哈希与盐会被一起存储在/etc/passwd文件中,今天哈希加盐的策略与几十年前的也没有太多的不同,差异可能在于盐的生成和选择。一个示范性质的代码如下所示。

from random import Random
from hashlib import md5
# 获取由4位随机大小写字母、数字组成的salt值
def create_salt(length=4):
    salt = ''
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789&#39'
    len_chars = len(chars) - 1
    random = Random()
    for i in range(length):
        # 每次从chars中随机取一位
        salt += chars[random.randint(0, len_chars)]
    return salt
# 获取原始密码+SALT,计算返回MD5值
def create_md5(pwd, salt):
    md5_obj = md5()
    inputstr = pwd+salt
    md5_obj.update(inputstr.encode(encoding='utf-8'))
    return md5_obj.hexdigest()
pwd = '123456'
salt = create_salt()
finalmd5 = create_md5(pwd,salt)
print(f'pwd:{pwd},salt:{salt},md5:{finalmd5}')

执行后的效果如下所示:

pwd:123456,salt:lhDy,md5:e7a2a020e5738dc9cc7822ca11b6fdf7

在实际使用时,需要保存salt的值与计算结果。加盐的方式主要还是为了增加攻击者的计算成本,当攻击者顺利拿到数据库中的数据时,由于每个密码都使用了随机的盐进行哈希,所以预先计算的彩虹表就没有办法立刻破译出哈希之前的原始数据,攻击者对每一个哈希都需要单独进行计算,这样能够增加了攻击者的成本,减少原始密码被大范围破译的可能性。但这个貌似完美的策略还是被发现存在问题。因为一个哈希函数或者摘要算法被找到哈希碰撞的概率决定了该算法的安全性,早在几十年前,人们就在MD5的设计中发现了缺陷并且在随后的发展中找到了低成本快速制造哈希碰撞的方法:

总结一下,之所以基于MD5的密码保存与对比策略不安全是因为:

发表回复