2015年10月

如何安全地保存用户的密码

最近数据库泄漏事件层出不穷,无数人的上网密码被人破解。本文来探讨一下如何安全地保存用户的密码。

为什么非要安全地保存密码?因为人类使用密码有三大偏好:

  1. 喜欢用简单好记的密码
  2. 喜欢到处用相同的密码
  3. 不喜欢经常地修改密码

为了你的客户不至于因为你的数据库泄漏事故和损失惨重,请保护好他们的密码!

最烂的方式:明文保存

许多早期制作的网站,还有众多的政府网站,都是这么保存密码的,包括著名的CSDN也是。有的时候这么保存密码是不得已的:我曾经接到过一个政府部门的项目,领导要求在他忘记密码的时候能让单位负责IT工作的小王帮助查一下密码是什么。不过还好一般并不难说服领导换另一种方式:如果您忘了密码,可以用手机重设密码。

明文保存密码的方法把密码安全完全交给了运维。任何安全漏洞,不论是操作系统漏洞,还是数据库漏洞,甚至应用程序中的漏洞,都会导致用户的密码大白于天下。

次烂的方式:MD5保存

相对明文密码好一点的方法是把用户的密码直接哈希保存。可惜大多数用这种方式保存密码的人并不是因为意识到明文保存密码有什么不妥,仅仅是因为学习编程的时候教材上是这么做的。利用单向哈希算法保存密码当然能比明文保存好一点点,但其最大的问题却在于会让开发者误以为用户的密码是非常安全的——即使泄漏了数据库,黑客也不可能知晓用户的密码是什么。

且不说MD5已经被证明是非常不安全的哈希算法,即使换成SHA-1或者复杂度更高的哈希算法,也不可能显著地提升用户密码的安全性,因为黑客攻击的方式往往并不是通过数学方法寻找哈希碰撞,而是直接在字典中查询。每个黑客手上都有上千万条记录的密码字典,包括常用的单词、拼音、19xx到20xx年的生日等等。他们只需要把MD5的结果输入,就能在字典库中找到对应的原文。一般一个MD5的密码库泄漏的时候,超过八成密码能在字典中反查得到。

比较好的方式:加盐哈希保存

如果定义一个长字符串,把它插入到用户密码中的某个地方,然后再哈希出结果,这样可以改变用户密码的哈希结果,使字典攻击失效。

比如用户的密码是abc123,直接MD5的结果是e99a18c428cb38d5f260853678922e03,大多数黑客的字典中都有这条记录。如果我们把用户的密码加上这个前缀ask3Kxsk777sA00bdsOo552,变成ask3Kxsk777sA00bdsOo552abc123,然后再进行MD5计算,得到的就是3ee795c4ceadf8b21a12f6e373cb1c56,一般黑客的字典里都不会有这条记录。这里用到的前缀就被称作“盐”

加盐哈希保存的结果是,黑客需要同时取得你的数据库和你的“盐”,并且加盐重新生成整个字典库才能破解你的用户密码。安全性比直接哈希保存要高多了。

更好的方式:随机加盐哈希保存

这是对于上一种方法的改进。在前面的方法中,盐是固定的,加盐的位置是固定的,以当今的计算机速度,黑客只需要多花点心思把你的盐搞到手,然后再花个一两天把密码库加盐跑一遍,还是能破解你大多数用户的密码。

如果在保存密码的时候盐随机生成,并插入到原始密码的随机位置,那么数据库里每条密码记录的盐和加盐位置都不同,黑客如果要破解密码,就需要为每一个密码生成一遍字典库,工作量要大得太多了。

最好的方法:没有密码

最安全的密码保存方法就是完全不保存用户密码。现在各大社交网站都支持账户接入非常发达,完全可以让用户用微博、微信、QQ、豆瓣、淘宝、人人、Google、Yahoo、Twitter、Facebook等等等等各种第三方账户来登录你的系统,再不济也可以让用户用随机短信密码来登录。没有保存密码,就不会丢失密码。

别人推荐的方式:慢哈希

慢哈希是一种特别的哈希算法,它比MD5、SHA1等常见的密码哈希算法要慢得多。它的安全原理是:在用户注册或者登录等正常行为时,哈希函数的运行时间由几毫秒变慢为几百毫秒,在用户感受上不会有太大的差别,而对于攻击者来说,因为需要大量计算哈希值试错,慢哈希函数就能有效延长破解密码所需的时间。