很多软件工程师都认为MD5是一种加密算法,然而这种观点是不对的。作为一个 1992 年第一次被公开的算法,到今天为止已经被发现了一些致命的漏洞。本文讨论MD5在密码保存方面的一些问题。
假设下面一个场景:
-
软件产品让用户输入用户名与口令,随即使用MD5算法将口令(明文)转变成为摘要值。
-
用户登录时,用户输入的口令,也使用MD5进行计算,然后与存储的MD5进行比较,如果相同,则用户成功登录。
-
由于没有存储口令的原始值,所以即使相关人员(工程师、运维人员、黑客)获得了口令的MD5值,根据算法的特性,也无法知道原始的口令内容。
-
正是算法的不可逆性,因为口令只能够重新生成,而系统无法反馈原始的口令是什么。
以上场景是非常完美的。但是由于人类的弱点,大部分人会选择非常简单易记,或者有特殊意义的字符串做为口令。攻击者只需要将一些常见密码提前计算一下哈希就可以找到数据库中很多用于存储的密码,Wikipedia 上有一份关于最常见密码的列表,在2016年的统计中发现使用情况最多的前25个密码占了调查总数的10%,虽然这不能排除统计本身的不准确因素,但是也足以说明仅仅使用哈希的方式存储密码是不够安全的。提前计算的HASH表称为彩虹表,存储着一些常见密码的哈希,当攻击者通过入侵拿到某些网站的数据库之后就可以通过预计算表中存储的映射来查找原始密码如下图所示。
为了抵抗上述的暴力方法,可以使用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'' 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的设计中发现了缺陷并且在随后的发展中找到了低成本快速制造哈希碰撞的方法:
-
1996年《The Status of MD5 After a Recent Attack》——发现了MD5设计中的缺陷,但是并没有被认为是致命的缺点,密码学专家开始推荐使用其他的摘要算法;
-
2004年《How to Break MD5 and Other Hash Functions》——发现了MD5摘要算法不能抵抗哈希碰撞,我们不能在数字安全领域使用MD5算法;
-
2006年《A Study of the MD5 Attacks: Insights and Improvements》——创建一组具有相同MD5摘要的文件;
-
2008年《MD5 considered harmful today》——创建伪造的SSL证书;
-
2010年《MD5 vulnerable to collision attacks》——CMU软件工程机构认为MD5摘要算法已经在密码学上被破译并且不适合使用;
-
2012年《Flame》——恶意软件利用了MD5的漏洞并伪造了微软的数字签名
总结一下,之所以基于MD5的密码保存与对比策略不安全是因为:
-
实际应用的大量口令本身很简单;
-
MD5计算起来非常快,攻击者今天可以通过 GPU 每秒执行上亿次的计算来破解用户的密码;
-
算法自身的缺陷。