为什么不使用 MD5 进行密码散列?

Why not use MD5 for password hashing?

我有一个朋友是白帽黑客。他说 md5 并没有那么糟糕,实际上非常安全,只要我们正确使用它。

我相信他是对的。据我所知,有 3 种方法可以破解哈希:

  1. 使用 Rainbow 表(可通过 long/random salt 加以保护)
  2. 冲突(可以通过多个盐或哈希来防止——如下例所示)
  3. 生成时间(如果我们为每个用户使用足够长的盐值,这并不重要 - AFAIK)

我和我的朋友认为 Blowfish 并不是真正需要的,它也可能是有害的,因为它会减慢密码验证过程,并且它可以与 DDOS 攻击一起使用以破坏服务器,即使攻击资源较少。

所以,我想确定以下算法是否真的安全?而且,是否有真正的理由去使用 Blowfish 哈希算法?

// return a 256 bit salt + 128 bit md5 binary hash value
function hash(password, salt=null)
{
    salt = (salt != null) ? salt : Random256BitBinaryValueGenerator();
    // What about using another user-specified parameter, like email address as salt?

    return salt + md5(salt + password) + md5(password + salt);

    // Or just use a non-cryptographic hash algorithm like crc32 to prevent collisions:
    // return salt + md5(salt + password) + crc32(salt + password);

    // Or even use two different salts:
    // return salt + md5(salt + password) + md5('C' + salt + password);
}

// check password
function check(password, hash_value)
{
    return hash(password, substring(hash_value, 0, 32)) == hash_value;
}

您说减慢验证是一个问题,但这是防止散列泄露和暴力攻击的唯一方法。现代解决方案反复(即:数千次)哈希值只是为了增加计算成本。

MD5的问题就在于它太快了,用普通硬件可以计算出9 Giga MD5/s左右。要暴力破解一本包含大约 200000 个单词的完整英语词典,您只需要几分之一毫秒。

这就是为什么像 BCrypt 这样的合适的哈希算法提供了一个成本因素。成本因子定义了计算哈希需要多少时间,并且可以在未来增加。登录 50 毫秒几乎不是障碍,但对于暴力破解来说却是致命的。

collision resistance property of MD5 has been broken for a long time. Note that that preimage resistance 和第二个原像阻力尚未被破解,但是由于有更好的算法 (SHA-2),明智的做法是转向这些算法而不是依赖加密散列已经开始失去其加密属性。注意:存储散列密码时,抗碰撞性 属性 无关紧要 - 你需要确保原像抗性 属性 是正确的 - 它在给定特定哈希值(和盐)的情况下,要找到原始密码在计算上是不可行的。正如我所提到的,由于其中一个加密属性已经被破坏,我担心其他的很快就会被破坏。

当您存储密码哈希值时,您应该建立一些保护措施,以防止攻击者设法提取这些哈希值时无法检索原始密码。这很重要,因为如果攻击者设法检索到密码 table,那么他们就可以使用这些数据直接登录到您的系统,或者登录到用户重复使用相同密码的其他系统。

存储密码时,使用 slow 算法很重要,例如 bcrypt、scrypt 或 pbkdf2。合法用户只应在首次登录时经历一次延迟。攻击者将不得不经历他们猜测的每个密码的延迟 - 请记住 rainbow tables 不会在这里使用,因为密码是加盐的。攻击者将根据您选择的算法和迭代次数对每个密码猜测进行哈希处理。

调整系统的迭代次数很重要,这样正确的 "strength" 就不会在合法用户登录您的系统时引起任何真正的烦恼。这被称为 "rounds" 或 "iteration count" 的数量。例如,迭代大约一秒钟就足够了。可以安全地假设攻击者可以 运行 以系统硬件速度的十倍通过哈希。因此,这将攻击者限制为每秒 10 次猜测,而不是 MD5 的 20 亿次。

关于 DoS 攻击

是的,您的应用程序在登录之前执行的额外处理可能成为攻击者的目标,攻击者可以向您的应用程序提交非常长的密码,或者通过登录请求反复攻击它以消耗 CPU 和服务器上的内存资源。 You are right to be concerned.

可以通过以下方式缓解此类攻击:

  • 记录每次登录尝试的用户名和 IP 地址。假设 6 次尝试失败后,如果再次重复该用户名或 IP,则您的应用程序会延迟响应。这也有助于减轻一般的密码猜测攻击。
    • 例如,您可以人为地延迟 1 秒,然后 2 秒,然后 4 秒,直到一个合理的值(例如 16 秒)。
    • 这样做的好处是攻击者无法故意锁定另一个帐户,因为合法用户只需等待 16 秒。
    • 攻击者可以使用僵尸网络和随机用户名来绕过这些检查,但是他们需要比没有这种控制时更多的 IP 地址,而且更随意的攻击者也不会意识到延迟反应是人为的。
  • 监控系统上的登录尝试次数。一旦超过设定的阈值速率(例如每秒 10 次),引入一个 CAPTCHA 来解决以继续登录过程。您选择的门槛率在很大程度上取决于您系统的用户群和容量。
  • 实施双重身份验证。验证一次性密码后,才继续通过散列来验证密码。