如何使数据库中的密码更安全:哈希、加密和加盐

0X00 前言

设计一个系统,不论是 Web 还是其他的什么形式,通过用户名和密码认证也是一个再正常不过的事情了。但是如何保存密码却是一个值得讨论的问题,相信各位最开始的一个有用户名和密码的程序多半也是用明文存储的密码吧 🤓

这里总结了六种比较常用于密码存储的方式,接下来可以逐一进行简单的分析以帮助我们更好的保护用户的密码

一清二白:明文存储密码,直接存 password

掩耳盗铃:使用 BASE64 之类的编码,存储为 cGFzc3dvcmQ=

盘古之法:使用早已不再安全的 md5 之类的摘要算法,存储为

5f4dcc3b5aa765d61d8327deb882cf99

祖宗之法:使用也已经不再安全的 sha-1 之类的摘要算法,存储为

5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8

凑合能用:使用现代的安全的例如 sha-256 之类额算法,存储为

5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

现代手段:采用系统化的多次迭代的加盐哈希方式加密,例如常见的 PBKDF2

有兴趣的话可以把我上面的这几个 md5/sha-1/sha-256 拿来到 CrackStation 尝试解一下,就知道弱密码究竟有多危险了。

不过在开始之前要先进行一些简单的科普,以便于能够比较好的理解后面的东西。

名词解释

拖库:简单来说就是有不怀好意的小老弟获取了你数据库的访问权限,甚至将其 dump 然后下载下来了
撞库:当你有某个库的用户名和密码明文了,其实可以拿着用户名密码去其他系统也尝试登录,因为很多人的用户名和密码在各个系统都是一样的,比如同一个邮箱注册的密码也用的完全相同
碰撞:一般是说 hash 碰撞,在有一对原文和对应的 hash 值后再找到一个 hash 值相同但原文不同的的方法
:加盐是一种在进行散列前向原文指定位置插入内容,将散列前的原文变得很长从而使彩虹表等方式失效的行为

密码破解

常见的几种破解密码的方式有:暴力破解、用字典的暴力破解、彩虹表、社会工程学。

其中暴力破解最为暴力(废话噢),就是把所有可能的密码组合一个个的尝试一直尝试到天荒地老。如果是一个 8 位密码且只由大小写数字构成的话也有 62^8=218340105584896 这么多种组合,就算每秒钟能算出来十万个也需要七十年才能完全遍历一遍;
其次是用字典,字典就是将常用的弱密码、生日这种存起来,暴力破解的时候不再尝试所有组合,而是从字典中逐个取出来尝试,速度较快但是成功率降低了,毕竟只尝试了可能性最大的一小部分密码;
彩虹表也是类似于字典,但是字典每条只是一个密码,彩虹表是密码明文与 hash 之后的 key-value 对,当数据库被拖/泄露了之后,可以尝试拿泄露出来的 hash 值来反查密码原文;
社会工程学听起来好像很野,但是谁还没有过用生日和手机号作为密码解密的尝试呢 🤣

0X01 一清二白:明文存储

明文存储实现起来当然是最容易的,但是但凡能够访问数据库的就都可以知道用户用的是什么密码,这显然不太行。当然最严重的还是万一被拖库了,坏人老弟就可以随便登录任何一个用户了,还可以顺便拿去其他系统撞库,到时候用户受损失的可能不止当前这一个系统

任何时候都不要明文存储密码

任何时候都不要明文存储密码

任何时候都不要明文存储密码

0X02 掩耳盗铃:使用编码而非加密/摘要算法

还别说,真的有人用 BASE64 作为「加密算法」应用在用户的密码字段上,但凡了解过一点 BASE64 的应该都知道这东西是没有任何保密功能的,因为但凡有一点技术功底的都能很快猜到某个字符串是经过了 BASE64 编码的,只要猜到了那就跟明文一模一样了。

任何时候都不要明文存储密码

任何时候都不要明文存储密码

任何时候都不要明文存储密码

0X03 盘古之法:例如 md5

经常会有人说「我密码用了 md5 加密」,这里再重申一下 md5不是加密算法,sha-1 和 sha-256 也都不是,他们都是摘要算法

我们都说 md5 不再安全了,但是我们要搞清楚是为什么不安全,以及那里不安全。2004年的国际密码学会议(Crypto’2004)王小云证明了MD5可以被碰撞,至此,MD5不再安全。现在又十几年过去了 md5 碰撞已经可以做到在普通电脑上以比较快的速度实现了,但是也仅限于「碰撞」二字。所以可以注意到在网站上下载软件的时候防止调包的算法也确实从 md5 换成 sha-256 了。

但是这里提到的不安全也只是基于碰撞的,如果想通过碰撞来尝试登录一个使用 md5 保存密码的系统那么首先需要这个数据库已经泄露出去了才有可能。不过既然都泄露了数据库,那么黑客肯定就可以尝试用彩虹表来反查了,这效率要比搞碰撞高得多。

所以说单独使用md5确实是一件非常不安全的事。

0X04 祖宗之法:例如 sha-1

那 md5 不行的话 sha-1 可不可以呢?如果你的系统安全性要求 md5 不行的话,sha-1 其实大概率也不行。因为在证明 md5 可以被碰撞后第二年 sha-1 也就找到了有效的攻击方法,直到 2017 年也成功碰撞了。

因为 sha-1 和 md5 本质上都是散列函数,所以上面说的 md5 的东西也几乎都适用于 sha-1 了。从而也间接证明了单独使用sha-1也是一件不安全的事。

0X05 凑合能用:例如 sha-256

好消息是sha-256还没有被碰撞成功,所以用来校验下载的文件是否正确现在普遍使用 sha-256 了。但是也只是没有被碰撞而已,彩虹表也还是能在网上搜到一些,而且在 cracker 的圈子里肯定还流传着更大甚至大得多的彩虹表,所以说如果你被拖库了那其实也是挺危险的。

0X06 现代手段:例如 AES/PBKDF2

那现代化的安全性很高的加密手段呢?比较常见的是小标题上的 AES/PBKDF2 这种,前者是对称加密后者的核心仍旧是散列算法。

首先来说 AES,AES 是一个对称加密算法,如果想用在密码保存上的话就相当于给密码加密之后存储,所以用于加密密码的密码一定要谨慎保管,否则当被人拖库之后还泄露了这个密码那就跟明文保存无异了。那没有泄露这个关键密码就一定安全了?那可不是噢,如果这个人自己注册了该系统,那他必然知道自己的密码明文是什么,现在还获得了密文,接下来就可以尝试攻击来找到这个关键密文了。(所以说千万要做好各种防护,被拖库是很恐怖的)

然后是 PBKDF2 了,这玩意儿的全称是 Password-Based Key Derivation Function 2,多少有些长了。这个方法除了需要明文以外还需要我们指定:散列算法、盐、重复次数这三个重要参数。这里是 Python 的一个示例,散列算法用了 sha-256 配合超长的盐和超多的重复次数进行了一次计算。

PBKDF2

  • 其中散列算法可以用 md5/sha1/sha256 这种,比较推荐使用 sha256;
  • 盐则需要每个密码都随机生成一个不同的盐,而且盐也一定要长,过短的盐和相同的盐是没什么意义的;
  • 重复次数可以多一些,迭代次数多了自然会更安全一些

0X07 总结

总结下来其实可以发现没有哪种方法是绝对安全的,只是破解的难度不同、泄露密文后的严重性不同罢了。而且别人破解你解你密码也是有成本的,如果说只是一个论坛的密码你用了 md5 + salt 的话其实基本也够安全了,毕竟破解这个系统获得的收益也没有那么大。但是如果涉及到了金钱,比如银行的密码,只有短短 6 位数字就可以保证你的安全,这种情况数据库那边对密码的加密肯定就是不怕复杂了。而且高安全性的系统往往也不止要一个密码就行,还有 OTP 的二次验证、有短信验证码、有人脸校验、还有声纹指纹虹膜校验。

0X08 参考资料

发现我参考的资料比我写的又好又全又早,感觉自己没有写的必要了 😪