如何在数据库中安全地存储用户密码
如何在数据库中安全地存储用户密码
演进
一个账号 + 密码的登录体系在数据库中的存储历经了以下几个阶段:
-
表中存储账号和密码, 登录时使用
select count(*) from user where username = ${username} and password = ${input}
, 如果查出来有记录说明密码正确用户的明文密码被暴露在了数据库中, 如果数据库泄露或者DB管理员偷看, 用户的密码就会泄露. 对于大部分习惯在多个平台使用相同密码的用户来说, 一旦密码泄露造成的后果将是灾难性的. 就算没有人尝试撞库, 修改所有平台的密码也是一个繁重的任务
-
表中储存密码的MD5值和账号, 登录时对用户填写的密码进行MD5处理, 得到inputMd5, 然后
select count(*) from user where username = ${username} and password_md5= ${inputMd5}
, 如果查出来有记录说明密码正确这样避免了在数据库中明文储存密码, 还能实现密码的正确性验证, 但是在今天来看仍然是不安全的
因为一个字符串永远只对应一个MD5值, 所以理论上来说可以建立一个"MD5值 -> 明文"的反向映射, 对于一些常见的密码来说, 其MD5值很可能已经被收录进"彩虹表". 一些网站已经可以轻易破解MD5, 例如:
我们在一个MD5在线生成器中生成"123456"的MD5值
然后我们去一个破解MD5的网站查找该MD5值的明文
如果数据库泄露, 黑客拿着你的MD5处理后的密码也能轻而易举地破解出你的明文密码
下面是cmd5.com首页的宣传语, 可见这种存储密码的方式也是不安全的
本站针对md5、sha1等全球通用公开的加密算法进行反向查询,通过穷举字符组合的方式,创建了明文密文对应查询数据库,创建的记录约90万亿条,占用硬盘超过500TB,查询成功率95%以上,很多复杂密文只有本站才可查询。自2006年已稳定运行十余年,国内外享有盛誉。
-
MD5或其他散列算法(如SHA256) + 随机盐值
注册时:
- 将用户的password和一段随机生成的盐(建议长度不短于最终的hash值)进行拼接
- 将以上拼接结果进行MD5, 得到hash值
- 将hash值和盐一同存入数据库, 不保存明文密码
登录时:
- 将用户填写的password和数据库中储存的盐按照注册时的相同方法进行字符串拼接
- 将以上拼接结果进行MD5, 得到hash值
- 将以上的hash值与数据库中存储的hash值进行比对, 如果一致则认为密码正确, 反之则错误
就算黑客拿到了数据库中的hash值和盐值, 也很难查到原密码, 因为加过盐之后的组合太复杂太少见了, 彩虹表中几乎不可能查得到
个人认为这是网络上众多方案中兼顾简洁与安全性的一种. 也有人会把原密码hash一把, 把随机盐值也hash一把, 然后拼接或者像洗扑克牌一样穿插在一起, 然后再hash一把…或者组合使用不同的散列算法. 个人认为没必要了, 反而会提高编码难度, 降低性能
补充
MD5是一种hash算法(或称为散列算法), 它能够将任意长度的信息hash成长度固定为128位的摘要(常用一个长32字符的16进制字符串表示)
注意, MD5不是加密算法, 因为并没有可用于解密的正常手段
与MD5类似的散列算法还有SHA1, SHA2. 目前SHA1已被证实不安全, 因为谷歌成功制造出了两份SHA1值相同但内容不同的pdf文件
SHA2是一种标准, 输出长度为256位的SHA2算法被称为SHA256, 是更安全的算法