HMAC - 为什么不用 HMAC 来存储密码?

信息安全 密码 验证 密码学 哈希
2021-08-16 14:52:07

注意事项:我知道安全密码存储的好答案scryptbcrypt这个问题不是为了在实际软件中实现,而是为了我自己的理解。

假设 Joe Programmer 的任务是将最终用户密码安全地存储在 Web 应用程序的数据库中;或将密码存储在磁盘上以登录某个软件。他很可能会:

  1. $password从最终用户处获取。
  2. 创建$nonce为大约 64 或 128 位大小的随机值。
  3. 一起创建$hash = SHA256($nonce$password)并存储在数据库中。$nonce$hash

问题一:

为什么以下内容没有明显优于上述内容?

  1. 创建$long_string一次且仅一次。将此作为常量存储在应用程序代码中。$long_stringfx 可以是 2 KB 的随机字符。
  2. $password从最终用户处获取。
  3. 创建$mac = HMAC-SHA256($long_string)[$password](即使用最终用户密码作为密钥创建 MAC)并将其存储$mac在数据库中。

我想 HMAC 有以下好处?

  • 碰撞少了?
  • 它在计算上比普通散列更昂贵?(但当然不是 scrypt 附近的任何地方。)
  • 为了在合理的时间内成功进行蛮力攻击,攻击者需要获得对两件事的访问权:1)数据库,$mac存储位置,2)应用程序代码,原始$long_string存储位置。这比散列函数更好,攻击者只需要访问数据库?

但是,似乎没有人建议使用 HMAC,所以我一定是误解了什么?

问题二:

添加盐值的含义$nonce是什么?

  1. 创建$long_string一次且仅一次。将此作为常量存储在应用程序代码中。
  2. $password从最终用户处获取。
  3. 创建$nonce为大约 128 位大的随机值。
  4. 创建$mac = HMAC-SHA256($long_string)[$nonce$password]存储在数据库中。$nonce$mac
2个回答

加盐的目的是防止攻击成本分摊:如果攻击者想要攻击两个密码,那么它的成本应该是攻击一个密码的两倍。

根据您的建议(您的“问题 1”),具有相同密码的两个用户最终将使用相同的 MAC。如果攻击者对您的数据库具有读取权限,他可以“尝试”密码(通过重新计算 MAC)并查找数据库以查找匹配项。然后,他可以以攻击一个密码为代价并行攻击所有密码。如果您long_string是应用程序源代码中的硬编码常量,则应用程序的所有已安装实例共享该常量,并且(对于攻击者)预先计算密码到 MAC 对的大字典变得值得,也称为“a彩虹桌”。

使用随机数(您的“问题 2”),您可以避免成本分摊。nonce 通常被称为“盐”。long_string和 HMAC 的使用在这里并没有给您带来太多好处(顺便说一下,您并没有将 HMAC 用于它的设计目的,所以从密码学上讲,您的基础并不稳固)。使用盐是一个非常好的主意(好吧,至少不使用盐是一个非常糟糕的主意),但它只完成了一半的工作。您还必须有一个缓慢的散列过程。这里的重点是盐可以防止成本分摊,但不能防止攻击单个密码。攻击密码意味着尝试可能的密码直到匹配(这是“字典攻击”),并且,考虑到普通人类用户的想象力,字典攻击往往会起作用:使用可以猜到的密码。解决方法是使用本质上很慢的散列过程,通常通过迭代散列函数几千次。这个想法是让密码验证更加昂贵:让用户等待 1ms 而不是 1µs 并不困难(用户不会注意到它),但它也会使字典攻击的成本增加 1000 倍。如果它真的很长(不是 2 KB,而是 20 兆字节),long_string 可能会被使用。

可以使用HMAC代替原始哈希函数来加强密码验证系统,但设置不同。给定一个使用盐和迭代哈希函数检查密码的系统,您可以使用密钥K将哈希函数替换为 HMAC 。这可以防止离线字典攻击,只要您可以保密K。保持一个值的秘密并不容易,但保持一个 128 位的K秘密比保持一个完整的数据库更容易。

HMAC 在这里并没有给你带来太多好处,尽管我猜它是一个可替换的散列函数(可以通过更改密钥来替换)。无论如何,这里似乎有点矫枉过正。

但是,如果 $long_string 被攻击者知道,您在上面提出的第一个方案将灾难性地失败,这可能是因为代码可能不被视为机密(无论如何依赖代码的机密性通常是不好的做法)。

您应该使用第二种方案,因为需要 $nonce 来防止预先计算的攻击。