加盐密码哈希:如何正确使用

加盐密码哈希:如何正确使用

参考:加盐密码哈希:如何正确使用

⚠️ 不要自己写加密函数

为什么密码需要进行哈希?

hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

哈希算法是一个单项函数,它可以将任意大小的数据转为定长的“指纹”,并且无法反向计算。数据源修改一点,哈希的结果也完全不同。

基于哈希加密的账户系统,通常的认证流程

  • 用户注册账号
  • 密码经过哈希加密存储在数据库中,只要密码被写入磁盘,任何时候都不允许是明文
  • 当用户登录时,从数据库去除已经加密的密码,和经过哈希的用户输入进行对比
  • 如果哈希值相同或,用户获得登录权限,否则告知用户输入无效的登录信息
  • 每当用有用户尝试登录,重复以上两步

✅ 保护密码的哈希函数和数据结构中的哈希函数是不同的,哈希表之类的哈希函数,目的是为了快速查找,而不是高安全性。只有加密的哈希函数才能保护密码安全,例如 SHA256 SHA512 RipeMD WHIRLPOOL

如何破解哈希加密

  • 字典攻击

    字典攻击需要使用一个字典文件,它包含单词、短语、常用密码以及其他可能用作密码的字符串。其中每个词都是进过哈希后储存的,用它们和密码哈希比对,如果相同,这个词就是密码。

  • 暴力攻击

    暴力攻击会尝试每一个在给定长度下各种字符的组合。这种攻击会消耗大量的计算,也通常是破解哈希加密中效率最低的办法,但是它最终会找到正确的密码。

我们没有办法阻止字典攻击和暴击攻击,尽管可以降低它们的效率,但那也不是完全阻止。

加盐

查表法和彩虹表只有在所有密码都以相同方式进行哈希加密时才有效。如果两个用户密码相同,那么他们密码的哈希值也是相同的。我们可以通过“随机化”哈希来阻止这类攻击,于是当相同的密码被哈希两次之后,得到的值就不相同了。

比如可以在密码中混入一段“随机”的字符串再进行哈希加密,这个被字符串被称作盐值。为了校验密码是否正确,我们需要储存盐值。通常和密码哈希值一起存放在账户数据库中,或者直接存为哈希字符串的一部分。

盐值并不需要保密,由于随机化了哈希值,查表法、反向查表法和彩虹表都不再有效。攻击者无法确知盐值,于是就不能预先计算出一个查询表或者彩虹表。这样每个用户的密码都混入不同的盐值后再进行哈希,因此反向查表法也变得难以实施。

⚠️实现加盐哈希的过程中通常会犯哪些错误

  • 盐值重复
    重复的盐值是无用功,因为不变的盐值每次hash的密码还是一样的,因此用户创建账户或每次修改密码时,都应该重新生成新的盐值进行加密
  • 短盐值
    如果盐值太短,攻击者可以构造一个查询表包含所有可能的盐值。
    同样地,用户名也不应该被用作盐值。尽管在一个网站中用户名是唯一的,但是它们是可预测的,并且经常重复用于其他服务中。
    一个好的做法是使用和哈希函数输出的字符串等长的盐值。
  • 两次哈希和组合哈希

    人们经常不由自主地认为将不同的哈希函数组合起来,结果会更加安全。实际上这样做几乎没有好处,仅仅造成了函数之间互相影响的问题,甚至有时候会变得更加不安全。

  • 哈希碰撞

    即使在MD5这样脆弱的哈希函数中找到碰撞也需要耗费大量的计算,因此这样的碰撞“意外地”在实际中出现的可能性是很低的。

正确的使用哈希加密

基本要素:加盐哈希

盐值应该使用基于加密的伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)来生成。

CSPRNG 专门被设计成用于加密,它能提供高度随机和无法预测的随机数。

一个好的标准的是:盐值至少和哈希函数的输出一样长;盐值应该被储存和密码哈希一起储存在账户数据表中。

存储密码的步骤

  • 使用 CSPRNG 生成一个长度足够的盐值
  • 将盐值混入密码,并使用标准的加密哈希函数进行加密,如SHA25
  • 把哈希值和盐值一起存入数据库中对应此用户的那条记录

校验密码的步骤

  • 从数据库中取出用户的密码哈希值和盐值
  • 将盐值混入用户输入的密码,并且使用相同的哈希函数进行加密
  • 比较上一步中的结果和数据库中哈希值是否相同

在Web程序中,永远在服务器端进行哈希加密

即使在web端进行了加密,也需要在服务端再次加密。

在web端加密需要注意:

  • 客户端加密不能代替HTTPS,如果浏览器和服务器之间的连接不是安全的。那么中间人可以修改js代码,删除加密函数,从而获取用户密码。
  • 浏览器可以禁用js,你的程序应该检测JavaScript是否可用,如果答案为否,需要在服务端模拟客户端的加密。

  • 客户端哈希同样需要加盐,很显然的办法就是向服务器请求用户的盐值,但是不要这么做。因为这给了坏蛋一个机会,能够在不知道密码的情况下检测用户名是否有效。既然你已经在服务端对密码进行了加盐哈希,那么在客户端把用户名(或邮箱)加上网站特有的字符串(如域名)作为盐值是可行的。

让密码更难破解

慢哈希函数

为了降低攻击者的效率,我们可以使用一种叫做密钥扩展的技术。这种技术的思想就是把哈希函数变得很慢,于是即使有着超高性能的GPU或定制硬件,字典攻击和暴力攻击也会慢得让攻击者无法接受。最终的目标是把哈希函数的速度降到足以让攻击者望而却步,但造成的延迟又不至于引起用户的注意。

无法破解的哈希加密:密钥哈希和密码哈希设备

只要攻击者可以检测对一个密码的猜测是否正确,那么他们就可以进行字典攻击或暴力攻击。因此下一步就是向哈希计算中增加一个密钥,只有知道这个密钥的人才能校验密码。有两种办法可以实现:将哈希值加密,比如使用AES算法;将密钥包含到哈希字符串中,比如使用密钥哈希算法HMAC

当用户忘记密码的时候,怎样进行重置?

大多数网站为那些忘记密码的用户发送电子邮件进行身份认证。首先,需要随机生成一个一次性的令牌,它直接关联到用户账户,然后将这个令牌混入到一个重置密码的连接中,发送到用户的电子邮箱。最后当用户点击这个包含有效令牌的链接时。提示他们可以重置新的密码。需要确保这个令牌只对一个账号有效,以防止攻击者获取到邮箱令牌后,用来重置其他用户的密码。

令牌必须在15分钟内使用,并且一旦被使用就立即失效。当用户重新请求令牌时,或用户登录成功时(说明他还记得密码),使原令牌失效也是一个好做法。如果一个令牌始终不过期,那么它一直可以用于入侵用户的帐号。