客户端密码哈希化

15
我和一位朋友正在讨论在将用户密码发送到我们的服务器之前,是否应该对我们Web应用程序的用户密码进行预哈希。我知道已经有多个问题处理了这个主题,但它们都是关于如何安全地传输到服务器的。我们的想法不是关于传输安全(我们使用SSL),而是要客户端哈希以防止“真实”密码到达我们的服务器。这个想法来源于Twitter宣布他们的错误导致密码以明文形式打印到日志文件中。我们目前正在讨论这个概念是否有意义,以及如果我们使用SHA512对其进行哈希,它会对密码安全性(暴力破解方面)产生什么影响。 TL;DR:我们想要客户端哈希密码,以防止我们的服务器获得它们的明文形式(我们使用SSL进行传输)。这有意义吗?使用哪种算法进行散列最好?然后,服务器端的哈希密码将再次使用bCrypt进行哈希。

1
是的,出于您所讨论的原因,这绝对是良好的实践。您应该避免将原始密码发送到服务器。密码应该尽可能接近用户进行盐和拉伸处理。有关如何实现此操作的完整讨论,请参见https://stackoverflow.com/a/31386549/97337。 - Rob Napier
在客户端和服务器端都进行哈希处理总是一个好习惯。您可以像这里描述的那样进行操作:https://medium.com/@harwoeck/password-and-credential-management-in-2018-56f43669d588,即在客户端使用哈希sha(password + constant domain string)进行哈希处理,在服务器端再次使用随机盐对客户端的哈希值进行哈希处理。 - Leszek Szary
这是一个重复的问题,链接为https://dev59.com/Eo3da4cB1Zd3GeqP15Rn。 - h2stein
4个回答

12

这个概念是完全有道理的:实际上,很多人都提出过这个概念,但正确实现它却很困难。如果你做错了,会遇到很多问题,其中最直接的问题是易受"pass-the-hash"攻击,正如@swa66所描述的那样。为了防止这种情况的发生,你需要在两端进行哈希处理。客户端哈希应该是慢速的(bcrypt、scrypt、argon2或pbkdf2),而服务器端哈希应该是快速的(sha256)。

编辑: 很多人在不理解这个方法的情况下就对此进行了负面评价,因此我在这里提供基本详细信息(之前我只提供了如何操作的链接)。这个想法是在客户端应用一个慢速哈希算法,例如bcrypt,然后在服务器端应用一个快速哈希算法,例如SHA256。 快速哈希算法是必需的,以防止"pass-the-hash"攻击。 在数据库泄漏的情况下,攻击者要么需要反转快速哈希(不可能—违反了密码哈希函数的单向属性),要么需要暴力破解快速哈希的原像(不可能—其大小等于慢速哈希输出的长度,例如bcrypt的输出为184位),要么需要暴力破解慢速哈希和快速哈希的组合—这会使攻击者回到与整个计算都在服务器端进行相同的位置。因此,通过将重量级的计算转移到客户端,我们并没有在数据库泄漏的情况下降低密码攻击的安全性。

我曾经调查过一些类似于这样的提案,详情请参见 《保护Web应用程序数据库中密码的方法》。此外,我还分析了其优缺点,并发现了以前未被发现的弱点(账户枚举),并提出了一种独特的安全方法。该研究基于多个来源,包括:

你引用了 Twitter 的例子,GitHub 也做了类似的事情。当我撰写上述论文时,最显著的防止服务器看到明文密码的例子是 Heartbleed,我在论文中对此进行了评论(请参见第 1.3 节底部)。

其他人后续进行了类似的研究,发现了相似的思想--例如:客户端加服务器密码哈希作为一种潜在的改进安全性的方法,可以防止暴力攻击而不会过载服务器。没有一个人应该获得全部荣誉,但主要的结论是:如果你安全地实施,这确实是个好主意,但你真的需要了解风险(如果你没有阅读研究,则很容易不安全地实施)。


1
展示一个世界知名专家推荐这个方法。在此之前:不好的想法。 例如:你如何在客户端上加盐哈希?它在攻击者的控制下。因此,你需要一个挑战-响应机制。如果没有:你的密码的慢哈希可以被彩虹表攻击,并且基本上没有什么作用。 例如:你从服务器上删除了慢哈希并用快哈希替换它:影响:你容易受到暴力破解攻击,因为“哈希就是密码”没有扩大搜索域(它仍然与可能输入的字典大小相同),你现在更糟了... - user3277192
2
如果这是可能的话,那么哈希算法就会被普遍破解。但这是不可能的——据我所知,最大的暴力攻击是一个大规模分布式的尝试,只有64位。PPF输出在实践中至少为128位(通常更长),这比任何可以在宇宙寿命内计算的东西都要难2^64倍。风险在于暴力破解密码,而不是哈希值,这就是为什么我们对密码进行PPF处理的原因。 - TheGreatContini
1
@TheGreatContini我看了§4。它说客户端发送慢哈希(“PPF'd”),服务器快哈希(“SHA”)它存储在数据库中。-我的意思是-如果数据库泄漏,黑客可以解密泄漏的快速哈希,并提交已解密的哈希,假装它是慢哈希,以进行身份验证。这是“客户端哈希变成纯密码”的问题。请问我读错了什么? - Константин Ван
1
@КонстантинВан "去哈希"泄露的快速哈希意味着打破哈希函数的单向性质,根据密码哈希函数的定义,这是不可能的。没有人能够反演SHA算法中的任何一个。除非您考虑穷举法,但要穷举的价值大小是慢哈希的输出大小。例如,在bcrypt的情况下,它是184位。这太大了,无法通过穷举来破解--最大的尝试是由分布式网络进行的2^64(对于rc6-64)。 - TheGreatContini
1
请注意,@КонстантинВан,MysteriousPerson在上面的评论中也提出了同样的建议,我已经回复了他。为了更清楚地表达这一点,我现在已经更新了原始答案,说明该攻击为什么不会使设计更加脆弱。请注意,包括著名的密码学家Thomas Pornin在内的许多人也说过同样的话,我的原始答案链接到了他们的讨论。我同意我的答案应该在此处提供这些细节而不是链接到其他地方,这就是我更新答案的原因。 - TheGreatContini
显示剩余14条评论

1

不行

密码学的第一条规则:不要自己发明,否则你会犯下可怕的错误。

这并不是针对你个人的,远非如此:即使顶尖专家在精心设计新系统时也会犯错。这就是为什么他们在任何东西成为标准之前都要进行多次同行评审。许多这样的专家提出的标准建议由于同行评审期间发现的问题而被重新制定。那么为什么我们其他普通人不能设计:因为没有人足够优秀来进行同行评审,而专家也不会碰它。

在客户端哈希密码

在客户端哈希真的很糟糕,因为哈希变成了密码,现在你将其以明文形式存储在服务器上。

如何处理密码

  1. 只存储哈希密码(暗示:将密码发送到服务器,但不要存储它)
  2. 使用盐并将其与密码(未加密)一起存储。盐本质上是一个随机字符串,您在对其进行哈希之前将其连接到密码中(以存储和验证密码)
  3. 使用慢哈希。使用快哈希是常见且致命的错误,即使使用盐也是如此。大多数人知道的哈希函数,例如SHA-256、SHA-3等都是快哈希,完全不适合哈希短、可预测的项目,例如密码,因为它们可以在惊人的短时间内被反转。

    有多慢:尽可能慢。慢哈希的示例: bcrypt、PBKDF-2(本质上是高轮次的快哈希,使其变慢)

根据您的编程环境,可能会有预先制作的例程,请使用它们!

参考:


4
虽然这对于许多(甚至大多数)用例来说是正确的,但有时候客户端密码哈希可能实际上是有意义的。另外,为什么服务器要将哈希后的密码明文存储(而不再次进行哈希)?显然,它不应该这样做,但这并不意味着永远不应该在客户端上哈希密码。这只是一个特殊情况,可以缓解对大多数应用程序通常不相关的威胁,但对某些应用程序却是相关的。 - Gabor Lengyel
3
谢谢提示!我不想自己创建一种安全存储密码的方式。我想要防止密码以明文形式发送到服务器(如果我没有它们的明文,那么在出现导致密码以明文形式打印出来的错误时,情况就会少得多,这种情况下只有预先哈希的密码会显示出来)。正如之前提到的:预先哈希的密码将在服务器端再次进行哈希。 - MysteriousPerson
1
事实上,网络上有许多关于如何做到这一点的建议,包括至少5个例子明确说明需要在两侧进行哈希处理以防止传递哈希攻击。 - TheGreatContini
1
我不同意这个答案。这会将像argon2这样的重型计算负担放在服务器上,使自己容易受到CPU攻击的威胁。 - jjxtra

1
虽然 @swa66 概述了如何安全地管理密码,但我想指出,确实存在一种有效的情况,您可以考虑客户端密码哈希,因此不要盲目遵循“最佳实践”,请先尝试理解。
假设我有一个标准的Web应用程序,可以存储用户数据。在我的威胁模型中,我甚至不希望我的用户必须信任我,换句话说,即使我的服务器完全被攻破,我也希望我的用户数据是安全的。因此,我让他们选择密码,并在将数据发送到应用程序之前在客户端对其进行加密。他们可以使用他们的用户 ID 检索其加密数据。那听起来并不太安全,我只需下载任何人的加密数据并进行离线攻击。因此,让他们使用密码访问其加密数据(我不希望他们记住两个不同的密码)是更好的选择。但这样做并不好,因为我需要他们的密码才能解密他们的数据。所以一种简单的解决方案是使用他们的密码加密他们的数据,并将其与已散列的密码一起发送到服务器,正如答案中正确指出的那样,对于服务器而言,这是新密码(因此,服务器应该再次存储它的散列值等等)。然而,服务器无法解密客户端数据,因为它从未拥有过原始密码,仅有效的人可以下载他们的加密文件,而且他们只需要记住一个密码。(请注意,这是一个非常简化的模型,在现实中需要更多内容,例如适当的密钥派生函数,而不仅仅是散列,但这是另一个更长的故事。)
不要误解我的意思,我并不是说你应该通常在客户端进行密码哈希 - 不,另一个答案在这方面是正确的。我只是想展示至少有一种用例可以在客户端进行密码哈希。请参阅知名密码管理器,一些工作方式类似。

0

是的,除了服务器端哈希之外,在客户端也进行哈希处理是有意义的。然而,与保护服务器的服务器端哈希相比,客户端哈希是保护用户而不是服务器的东西。客户端哈希的整个重点在于许多用户在其他网站上重复使用他们的密码,因此客户端哈希可以帮助保护这些密码。因此,客户端哈希永远不能替代服务器端哈希,但实施它作为服务器端哈希的补充是有意义的。我认为客户端哈希有几个有用的点:

  • 您和您的用户都可以放心睡觉,因为您从未收到过他们的明文密码,所以无法泄露
  • 如果坏人完全控制了您的服务器,用户登录时也不会收到明文密码
  • 如果您雇用一些外部公司来管理您的服务器,他们也看不到明文密码
  • 如果您的客户端应用程序是移动应用程序,则更有意义,因为在这种情况下,即使黑客完全控制了您的服务器,他也无法删除客户端哈希(对于 Web 应用程序,如果他从您的 Web 应用程序中删除了客户端哈希,您可能会更快地注意到有人黑了您)
  • 您永远不会意外记录明文密码在日志文件中
现在的问题是如何实现客户端哈希?假设这个客户端哈希只是为了用户的安全性而添加的(不是为了服务器的安全性),并且你将其视为与服务器端哈希完全不同的东西,那么人们提出的最简单的方法就是使用常数域字符串来生成哈希值,就像https://medium.com/@harwoeck/password-and-credential-management-in-2018-56f43669d588中所建议的那样。如果你使用缓慢的哈希算法,则可以在某种程度上保护用户的密码,尤其是在他在其他网站上重复使用密码时。但是,你可以通过使用用户的用户名/电子邮件作为他们的客户端伪盐来进一步改进它,如https://dchest.com/authbook/中所述。由于用户需要在登录屏幕/页面上输入用户名/电子邮件,因此这仍然很容易实现,因为它不需要对服务器进行任何更改,也不需要存储这个伪客户端伪盐。它还增加了每个客户端哈希对于具有相同密码的用户而言是不同的好处。这本书的作者建议使用一些缓慢的哈希函数进行客户端哈希,以使它们更难被暴力破解。对我来说,这似乎是一种相当好的简单的实现客户端哈希的方式,因为在登录时不需要从服务器请求任何用于客户端哈希的盐。所以总而言之,在客户端执行类似slowhash(domain_string+user_email+user_password)的操作看起来是一个好的简单解决方案。如果你的服务器仍然有一个适当的随机生成的盐值存储在你的数据库中,那么我认为这样做没有任何缺点。请记住,现代硬件可以非常快地计算出像SHA512这样的哈希值。例如,根据我刚刚查看的一些基准测试,RTX4090以7409.5 MH/s的速度计算它们-每秒计算7409亿个SHA512哈希值!这就是使用不同的更慢的哈希方法比较安全的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接