什么是密码散列?

hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542

散列算法是单向函数。它们将任意长度的数据转换为固定长度「指纹」,且无法逆转。它们还具有一种特性,即使输入发生一点点变化,生成的散列值也完全不同(参见上面的示例)。这对于保护密码来说非常有用,因为我们希望以一种即使密码文件本身被泄露也能保护密码的形式来存储密码,但同时,我们需要能够验证用户的密码是否正确。

基于散列的账户系统中账户注册和认证的一般工作流程如下:

  1. 用户创建账户。
  2. 他们的密码被散列并存储在数据库中。这一过程的任何时候都不会将纯文本(未加密)密码写入硬盘。
  3. 当用户尝试登录时,他们输入密码的散列值会与他们真实密码的散列值(从数据库中检索)进行对比。
  4. 如果散列匹配,则授予用户访问权限。否则,用户会被告知他们输入了无效的登录凭据。
  5. 每次有人尝试登录其账户时,都会重复第 3 步和第 4 步。

在第 4 步中,永远都不要告诉用户他们是弄错了用户名还是密码。应该始终显示诸如「无效的用户名或密码」之类的通用消息。这样可以防止攻击者在不知道密码的情况下枚举有效的用户名。

应该注意的是,用于保护密码的散列函数与您可能在数据结构课程中看到过的散列函数不同。用于实现散列表等数据结构的散列函数为快速而设计,而不是为安全而设计。只能使用 加密散列函数(Cryptographic Hash Function) 来实现密码散列。SHA256、SHA512、RipeMD 和 WHIRLPOOL 等散列函数属于加密散列函数。

很容易认为,您所要做的就是把密码丢进加密散列函数,您的用户密码就安全了。这与事实相去甚远。有很多方法可以非常快速地从普通散列中恢复密码。但是,有几种易于实现的技术可以使这些「攻击」的效果大打折扣。为了激发对这种技术的需求,请考虑这个网站(译者注:指原文所在的 CrackStation)。在首页,您可以提交需要破解的一组散列,并在不到一秒钟的时间内收到结果。很显然,简单地对密码进行散列并不能满足我们的安全需求。

下一节将讨论一些用于破解普通密码散列的常见攻击。