Java|如何安全保存用户密码及哈希算法

Java|如何安全保存用户密码及哈希算法

前言在很多网站的早期 , 甚至是现在仍然有一些网站 , 当你点击忘记密码功能时 , 你的邮箱会收到一封邮件 , 然后里面赫然写着你的密码 , 很多普通用户还会觉得庆幸 , 总算是找回来了 , 殊不知 , 这是多么可怕地一件事 , 说明了网站是“几乎是”明文存储你的密码 , 一旦数据用户数据泄露或者被拖库 , 那么用户密码将赤裸裸的暴露了 , 想想之前几次互联网密码泄露事件 。
那么如何解决呢?
加密为了不让密码明文存储 , 我们需要对密码进行加密 , 这样即使数据库用户密码暴露 , 也是加密后的 。 但是如何让加密后的数据难以解密呢?我们现在比较流行的做法就是把密码进行Hash存储 。
Hash哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值 , 这个小的二进制值称为哈希值 。 哈希值是一段数据唯一且极其紧凑的数值表示形式. 典型的哈希算法包括 MD2、MD4、MD5 和 SHA-1
Hash算法是给消息生成摘要 , 那么什么是摘要呢?
举个例子:


比如你给你女朋友写了一封邮件 , 确保没被人改过 , 你可以生成这样一份摘要 “第50个字是我 , 第100个字是爱 ,第998个字是你” , 那么你女朋友收到这个摘要 , 检查一下你的邮件就可以了 。
Hash算法有两个非常主要的特征:
  • 不能通过摘要来反推出原文
  • 原文的非常细小的改动 , 都会引起Hash结果的非常大的变化
因此 , 这个比较适合用来保存用户密码 , 因为不能反推出用户密码 , Hash结果一致就证明原文一致 , 我们来用Ruby代码试一下上面的第二点 (MD5是一种常用的Hash算法)
2.2.3 :003 > require 'digest/md5.so'=> true2.2.3 :004 > puts Digest::MD5.hexdigest('I love you')
e4f58a805a6e1fd0f6bef58c86f9ceb3
=> nil2.2.3 :005 > puts Digest::MD5.hexdigest('I love you!')690a8cda8894e37a6fff4d1790d53b33
=> nil2.2.3 :006 > puts Digest::MD5.hexdigest('I love you !')
b2c63c3ca6019cff3bad64fcfa807361
=> nil2.2.3 :007 > puts Digest::MD5.hexdigest('I love you')
e4f58a805a6e1fd0f6bef58c86f9ceb3
=> nil2.2.3 :008 >

那么我们在使用MD5保存密码时候的验证流程是什么呢?
  • 用户注册时 , 把用户密码是MD5(password)后保存到数据库 。
  • 用户输入用户名和密码
  • 服务器从数据库查找用户名
  • 如果有这个用户 , A=MD5(input password) B=Database password
  • 如果A==B 那么说明用户密码输入正确 , 如果不相等 , 用户输入错误 。
为什么Hash(MD5)后仍然不够安全?穷举但是 , 如果你认为就只是这样密码就不会被人知道 , 那么就不对了 , 这只是比明文更安全 , 为什么?
因为 , 大部分人的密码都非常简单 , 当拿到MD5的密码后 , 攻击者也可以通过比对的方式 , 比如你的密码是4218
2.2.3 :008 > puts Digest::MD5.hexdigest('4218')d278df4919453195d221030324127a0e

那么攻击者可以把1到4218个数字都MD5一下 , 然后和你密码的MD5对比一下 , 就知道你原密码是什么了 。
曾经我的密码箱密码忘了 , 我把锁给撬了 , 后来我才想起可以用穷举法 , 最多就999次不就打开了?那么问题来了 , 你的密码箱还安全吗?
彩虹表除了穷举法外 , 由于之前的密码泄露 , 那么攻击者们 , 手上都有大量的彩虹表 , 比如\"I love you\"生日等等 , 这个表保存了这些原值以及MD5后的值 , 那么使用时直接从已有库里就可以查出来对应的密码 。