如何*正确*存储密码?

Posted

技术标签:

【中文标题】如何*正确*存储密码?【英文标题】:How to store passwords *correctly*? 【发布时间】:2010-10-24 05:03:02 【问题描述】:

我在 SO 中偶然发现的一个 article 提供了指向 other articles 的链接,而后者又提供了指向 even more articles 等的链接。

最后我完全被难住了 - 那么在数据库中存储密码的最佳方法是什么?据我所知,您应该:

使用长(至少 128 个完全随机位)盐,以明文形式存储在密码旁边; 对加盐密码使用多次 SHA-256(或更高的 SHA 级别)迭代。

但是……我对密码学的了解越多,我就越明白我什么都不懂,而且我多年来一直认为是正确的事情实际上是完全错误的。这里有这方面的专家吗?

补充:似乎有些人没有抓住重点。我重复上面给出的最后一个链接。这应该可以澄清我的担忧。

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2007/july/enough-with-the-rainbow-tables-what-you-need-to-know-about-secure-password-schemes/

【问题讨论】:

你说得对。不要存储密码——存储密码的哈希值。你真正的问题是什么?加盐和散列的哪一部分令人困惑?或者你只是不开心,因为这对你来说是新的?您发现了哪些相互矛盾的建议? 这是用于在您自己的程序中进行用户验证,还是您希望创建一个需要返回密码的密码管理器? 没有“正确”答案。安全级别很大程度上取决于应用程序的上下文。与面向 Web 的数据库相比,隔离的机器(未物理连接到网络)的密码散列可能更少。再说一次,你的数据库永远不应该被网络直接访问,用户和数据库之间应该总是有一个访问层。如果密码以纯文本格式发送到数据库/访问层,则密码的哈希/加盐是没有用的。 你是对的。我错过了重点。您已经定义了最佳实践。您的链接定义了最佳实践。每个人都说盐和哈希。你有什么问题?你不喜欢什么?你不能做什么?什么坏了? 不要使用“几个”哈希迭代,使用“几个”。 【参考方案1】:

我认为不需要对密码进行额外的迭代,只需确保有一个盐和一个复杂的;) 我个人使用 SHA-1 和 2 个盐关键词。

【讨论】:

1-pass hashing 有一个漏洞。您可以枚举一组很可能导致散列的候选者。多次通过导致彩虹表快速增长,因此 256x 散列。 en.wikipedia.org/wiki/Rainbow_table 我使用 SHA-512,因为空间(和算法时间)不是问题,并且有一篇论文显示 SHA-1 中的冲突......但是我只是偏执。 单遍散列是可笑的。至少使用 1000,还不如使用 2000。【参考方案2】:

事实上,这取决于密码的用途。您应该小心存储任何密码,但有时需要比其他密码更加小心。作为一般规则,所有密码都应该经过哈希处理,并且每个密码都应该有一个唯一的盐。

说真的,盐不需要那么复杂,即使是很小的盐也会对试图进入系统的破解者造成真正的噩梦。它们被添加到密码中,以防止使用 Rainbow 表破解多个帐户的密码。我不会在密码中添加一个字母并将其称为盐,但您也不需要将其设为在数据库中其他位置加密的唯一 guid。

关于盐的另一件事。在散列时使密码+盐起作用的关键是两者组合的复杂性。如果你有一个 12 个字符的密码并在其中添加一个 1 个字符的盐,盐不会起多大作用,但破解密码仍然是一项不朽的壮举。反之亦然。

【讨论】:

【参考方案3】:

盐的长度并不重要,只要它对用户是唯一的。使用盐的原因是,在哈希匹配中给定的生成尝试仅对数据库中用户表的单行有用。

【讨论】:

这很重要。你的 salt 正在有效地做的是增加必须用来破解你的哈希的彩虹表的大小。短盐并没有那么大的作用。 (盐要克服的主要问题之一是用户选择短密码。) 这很重要,但只是在一定程度上。每添加一点盐,字典攻击所需的空间需求就会增加一倍(或者攻击者必须将他的字典减半)。如果有一个好的密码选择策略(最小长度、混合大小写的要求、数字、标点符号),那么使用旧的 12 位盐可能是安全的。但是,不妨使用 64 位并在任何地方都安全。没有人真的有空间容纳 10^19 的字典,即使它们真的很小。【参考方案4】:

简单地说,使用加密安全的哈希算法和一些盐作为密码,这对于 99.99% 的所有用例来说应该足够好了。弱链接将是检查密码以及密码输入的代码。

【讨论】:

只要提供更好的安全性只需再增加 5 行微不足道的代码,我愿意在所有用例中选择更好的选择。 我认为您不会在“另外 5 行微不足道的代码行”中得到它......但我也会在这里阅读其他答案和 cmets,也许他们会证明我错了。【参考方案5】:

用途:

    散列密码存储 128 位以上的用户级盐,随机,重新生成(即,您在创建新密码哈希时创建新盐,您不会始终为给定用户保留相同的盐) 一种强大、计算量大的散列方法 与任何“标准实施指南”(如此类)和其他任何“标准实施指南”都有些不同的方法(散列算法、您使用的散列迭代次数、盐的连接顺序、某些东西)您编写的密码存储实现

【讨论】:

128 位是多余的。即使是 64 位也是矫枉过正,所以这就是我使用的。也不需要“系统级盐”,除非“用户级盐”对于用户来说是可预测的(比如使用他们的名字)。仅使用加密 RNG 作为 salt 会更容易、更安全。 矫枉过正?我喜欢矫枉过正。系统级盐是针对您的数据库内容被破坏而您的源代码没有被破坏的事件的深度防御。同意盐应该是噪音,而不是用户信息。 我不明白。如果处理得当,摘要、盐和源代码都可以公开,攻击者仍然无法发现密码。当您说数据库可能被破坏时,您是什么意思?妥协是什么意思? “妥协”是指攻击者拥有数据库内容或源代码。如果攻击者有你的盐,你就失去了对抗彩虹表生成的好处。因此,通过从不同来源获得多种盐,您可以防止其中任何 n-1 种盐被发现。 说“如果攻击者有你的盐,你就失去了它的好处”是不正确的。盐是公共知识。 Salt 的两个目的是使密码包含一些真正随机的位,并使两个相同的密码散列为不同的值。【参考方案6】:

你没看错。只有两个建议:

    如果有一天 SHA1 变得太弱而您想使用其他东西,则不可能取消旧密码的哈希并使用新方案重新哈希它们。出于这个原因,我建议在每个密码上附加一个“版本”编号,告诉您使用的方案(盐长度、哈希值、次数)。如果有一天您需要从 SHA 切换到更强大的方式,您可以创建新式密码,同时在数据库中保留旧式密码,并且仍然可以将它们区分开来。将用户迁移到新方案会更容易。

    密码仍然在没有加密的情况下从用户传递到系统。如果有问题,请查看SRP。 SRP 太新了,您应该对实施它有点偏执,但到目前为止它看起来很有希望。

编辑:结果 bcrypt 在第 1 个想法上击败了我。存储的信息是(成本、盐、哈希),其中成本是哈希完成的次数。看起来 bcrypt 做对了。无需用户干预即可增加哈希的次数。

【讨论】:

+1 用于记录密码版本的想法。我可以看到这在未来很有用.... 这实际上是一个坏主意,因为它通过解决尚不存在的问题不必要地使解决方案复杂化。经典违反 YAGNI 原则。 +1 用于处理实际问题。 Kiff 的全面否认很容易被 SO 上被问到的问题的例子所反驳。作为一个不得不在短时间内要求成千上万的用户选择新密码的人,我希望我能有一个更顺畅的迁移路径。 @Kiff - YAGNI 对此有何看法?曾经被认为很强大的哈希现在变得很弱(例如 MD5),制定计划来处理有缺陷或被贬低的 SHA1 不是 YAGNI,而是从历史中学习。事实上,专家们已经在质疑 SHA1。

以上是关于如何*正确*存储密码?的主要内容,如果未能解决你的问题,请参考以下文章

存储自动登录密码的正确方法

只有在验证用户名和密码正确后,如何在后台任务中启动活动?

如何散列密码

安全地存储密码,但确定相同的密码

为小组存储生产密码的最佳实践

WPF登陆界面验证登陆的问题