我正在用 PHP 开发一个应用程序,它使用 bcrypt 加密来存储密码。每当用户更改密码时,我都想保留哈希的历史记录。通过这样做,我想阻止用户在某些情况下输入以前的密码。
保留哈希历史记录是否安全?
根据我的观察,如果用户更改了他的密码并保持与以前的密码相同,那么哈希值就会不同。我怎样才能阻止他保留与以前历史记录相同的密码?使用 bcrypt 加密时可以吗?
我正在用 PHP 开发一个应用程序,它使用 bcrypt 加密来存储密码。每当用户更改密码时,我都想保留哈希的历史记录。通过这样做,我想阻止用户在某些情况下输入以前的密码。
保留哈希历史记录是否安全?
根据我的观察,如果用户更改了他的密码并保持与以前的密码相同,那么哈希值就会不同。我怎样才能阻止他保留与以前历史记录相同的密码?使用 bcrypt 加密时可以吗?
存储哈希历史的安全性
保留哈希历史记录是否安全?
相对地。我可以想象一些这会损害用户安全的场景,例如:
superawesome
-> !sup3eraw3s0m3!
)将密码更新为更好的密码,这将导致攻击者能够更容易地破解现在更安全的密码(他们首先破解简单密码,现在将其放在他们的单词列表中,然后对其应用基本规则,例如 e -> 3 等)。所以我不建议保留密码哈希历史。
选择
我怎样才能阻止他保留以前历史记录中的相同密码?
不要保留历史。当用户更改密码时,您仍然在数据库中拥有原始密码哈希。您可以将其与此进行比较以防止完全重复。
但是使用 bcrypt,会发生这种情况:
根据我的观察,如果用户更改密码并保持与以前相同,则哈希值会变得不同
发生这种情况是因为 bcrypt 会自动为您管理盐。因此,当您散列一个新密码时,它会使用不同的盐进行散列,因此散列是不同的。您可以检索旧的 salt,然后将其作为参数传递给password_hash以获得相同的哈希值。
更好的选择
您还可以要求用户在更改密码时提交他们的旧密码(这也提高了安全性[*]),然后您甚至可以检查新密码是否与旧密码相似(例如使用汉明距离或类似方法)。
我的两种选择都不能防止循环变化(例如super-secure-password
-> another-awesome-credential
-> super-secure-password
),但我不确定我是否真的会担心这一点。
[*] 劫持会话的人无法更改密码,CSRF 也无法更改密码(即使存在 XSS 漏洞)。
实际上,关于 Google 如何处理旧密码(仍然识别它们以检索锁定帐户)的评论让我思考了这一点的利弊,以及您是否可以使用它来避免人们重新使用旧密码一种相对安全的方式。并不是说我真的认为你会想要那个(我个人认为 99% 的密码使用“强制最佳实践”只会导致人们写下他们的密码;除非相关人员真的真正认识到密码的重要性,否则这是真的(例如,它允许运行核电厂),在这种情况下,不需要以技术方式执行最佳实践)。
首先让我们看看 Google 是如何以相对安全的方式实施他们的系统的。从上面的评论中我了解到,当一个人无法访问他们的电子邮件并忘记了他们当前的密码时,就会启动某种恢复工作流程,其中一个人会提供他们身份的“证据”。可能在某个时候,人类会决定是否恢复对该帐户的访问。那时他们需要尽可能多的证据来证明你就是你!知道以前的密码可以提供提示。
现在存储旧密码哈希的问题就像@tim 在他精心回复的回复中提到的那样——即使我怀疑谷歌比大多数其他人更好地保护他们的数据库,数据泄露总是会发生。但是,如果不是存储完整的 128 位/256 位密码哈希(加盐),而是只存储前 16 位(用于旧密码),那么攻击者就不可能从这个*中提取密码。同时,如果有人提供了与存储的 4 个 16 位哈希中的 2 个匹配的 2 个密码,这大大暗示了此人不是一个完全随机的黑客**。
假设您的要求是不允许该用户以前在系统上使用过任何密码(例如,您的老板在某处读到这是一个好主意,并且不允许任何人改变主意)。我再次完全同意@tim 的评估,即为旧密码存储哈希是一个非常糟糕的主意。那么,如果我们使用建议的 Google 解决方案,并且只存储一个小的哈希值作为密码呢?问题是,如果我们使用 16 位散列,我们有一个合理的更改,即禁止使用以前未使用但具有相同 16 位散列的密码。生日攻击表明对于 16 位,在 36 个密码之后,有 1% 的机会至少有一个新密码被拒绝——这可能是可接受的,也可能是不可接受的。更多的比特意味着这个机会变得更小,但被黑客入侵的数据库暴露的信息会变得更大。
*如果您的旧密码是“monkey1”、“monkey2”、“monkey3”,黑客仍有可能获取此图案。另一方面,由于谷歌不需要每月更改密码,我希望使用简单模式的人根本不关心更改他们的谷歌密码。
**再次声明,这只是一个提示!旧密码的整个想法是它们已被更改,因为它们可能已被泄露。因此,知道旧密码甚至不是那么强烈的提示。可能他们会希望您在恢复访问权限之前扫描您的身份证或其他东西。然而,它确实为一些 16 岁的孩子试图侵入教师帐户或试图同时侵入多个帐户的计算机脚本添加了第一层保护。16 位意味着 64k 次随机密码尝试中只有 1 次通过。
这里的大多数评论都非常好,并确定了关键问题。但是,需要考虑的一点是,您需要在上下文中评估这些类型的问题,并且对于说明它是好还是坏的概括非常小心。
密码历史问题与密码老化和要求用户定期更改密码的概念有关。曾经,公认的最佳做法是对密码进行老化并维护历史记录,以确保用户不会重复使用以前的密码。在大多数情况下,这适合当时最盛行的时代和环境。然而,事情已经发生了变化,对于许多人来说,被迫定期更改密码并防止密码重复使用并没有什么好处,甚至可能是有害的。
有一些研究提出了这样一个前提,即密码老化和密码历史实际上可能会降低而不是增加安全性。基本假设和想法是
那么,这是否意味着密码老化和历史记录是一个坏主意?可能,也可能不是。这取决于环境。
在我的私人生活中,我使用了大量的网络服务。我尝试采用良好的密码实践——我在不同的网站上使用不同的密码,我使用强密码,并尽可能使用两因素验证。我不会通过不安全的渠道等传递密码。在这种环境下,被迫更改密码并被阻止重复使用密码不太可能提高我的安全性。如果服务提供商没有适当地保护这些信息,它甚至可能会降低我的安全性,因为攻击者可能能够猜出我巧妙的密码“模式”。
另一方面,我曾经在许多员工经常出差并经常使用未知且可能不安全的网络的环境中工作。在这种情况下,密码被泄露的风险要高得多。对于这种情况,要求用户定期更改密码可能是合理的,因为这将减少攻击面,即未经授权的第 3 方可能使用受损密码的时间量。强制执行密码历史记录将确保用户不会重复使用可能已经泄露的密码。(实际上,通过用户教育和教人们如何识别可能存在危险的网络并让他们在能够准确评估风险而不是强制采用一种适合所有类型的解决方案时更改密码,可能会获得更多好处,
密码历史可能成为安全问题的程度实际上取决于历史的安全程度。危险在于人们可能会认为因为它只是过去密码的历史记录,所以它不需要与实际密码数据所需的相同级别的保护。这通常是一个错误。密码历史记录需要与当前密码数据具有相同级别的保护。如果这有效完成,那么它可能不会比实际密码哈希风险更大,当然,如果实际当前密码数据没有得到充分保护,那么对密码历史的担忧可能是无关紧要的。
密码应使用经过批准的扩展算法(例如 PBDKF2 或 bcrypt)进行散列处理。此外,它们应该与唯一的盐结合使用,然后将其与散列一起保存。由于盐是唯一的,因此攻击可能只会针对给定的哈希及其盐发生。
由于散列函数在计算上是禁止的(如果正确实施),保留旧的散列和以前密码的盐不会造成额外的伤害。
由于必须保留历史记录以确保密码最佳实践,因此唯一合适的解决方案是保存散列和它的盐,在用户更新密码时将它们轮换出来。