bcrypt如何内置盐?


615

Coda Hale的文章“如何安全存储密码”声称:

bcrypt具有内置的盐,可以防止彩虹表攻击。

他引用了这篇论文该论文说在OpenBSD的实现中bcrypt

OpenBSD从arcfour(arc4random(3))密钥流生成128位bcrypt盐,该密钥流以内核从设备时序收集的随机数据作为种子。

我不知道这怎么工作。在我的盐概念中:

  • 每个存储的密码都需要不同,因此必须为每个密码生成一个单独的彩虹表
  • 它需要存储在某个地方以便可重复:当用户尝试登录时,我们尝试输入密码,重复与最初存储密码时相同的盐和哈希程序,然后进行比较

当我将Devise(Rails登录管理器)与bcrypt一起使用时,数据库中没有salt列,因此感到困惑。如果盐是随机的并且没有存储在任何地方,我们如何可靠地重复哈希过程?

简而言之,bcrypt如何内置盐

Answers:


789

这是bcrypt:

产生随机盐。已经预先配置了“成本”因素。收集密码。

使用盐和成本因数从密码中得出加密密钥。用它来加密一个众所周知的字符串。储存成本,盐,和密文。由于这三个元素的长度已知,因此很容易将它们连接起来并将它们存储在一个字段中,但是以后可以将它们分开。

当有人尝试进行身份验证时,请取回存储的成本和费用。从输入的密码,费用和费用中得出密钥。加密相同的知名字符串。如果生成的密文与存储的密文匹配,则密码为匹配项。

Bcrypt与基于PBKDF2之类的算法的传统方案非常相似。主要区别在于它使用派生密钥来加密已知的纯文本。其他方案(合理地)假定密钥派生函数是不可逆的,并直接存储派生密钥。


存储在数据库中的bcrypt“哈希”可能看起来像这样:

$ 2a $ 10 $ vI8aWBnW3fID.ZQ4 / zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa

这实际上是三个字段,以“ $”分隔:

  • 2a标识使用的bcrypt算法版本。
  • 10是成本因素;2使用了10个密钥派生函数的迭代(顺便说一句,这还不够。我建议花费12或更多)。
  • vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa是盐和密文,在修改后的Base-64中连接并编码。前22个字符解码为salt的16字节值。其余字符是要进行身份验证的密文。

本示例摘自Coda Hale的ruby实现文档。


6
您是否要详细了解为何成本因子10不够?在Grails中,我注意到bcrypt的成本因子/对数回合的默认值为10,因此根据您的建议可能值得更新。
pm_labs 2012年

57
bcrypt的成本因子是指数的,或者说,成本因子10意味着2 ^ 10轮(1024),成本因子16意味着2 ^ 16轮(65536)。那自然会花费5-10秒。它的成本系数应该是10的64倍。为了清除其他错误信息,PHP的crypt函数使用在c中实现的unix crypt库。
thomasrutter

3
@TJChambers是的;如果您可以在帐户上设置密码,则可以进行身份​​验证。密码哈希并不是为了防止这种攻击。这是为了防止对密码表具有只读访问权限的攻击者进行身份验证。例如,您将获得一张带表的备份磁带。
erickson 2014年

8
@LobsterMan不,不是。如果您可以保密,则不会使用此方法,只需存储密码即可。密码验证方案基于以下假设:攻击者已发现您所知道的一切。在那里,要求每个密码都被单独攻击。测试密码所需的计算工作量由迭代决定。如果用户选择了正确的密码,即使密码被泄露,它们也将是安全的。隐藏盐可能会在某些情况下帮助密码错误的人,但是我会首先处理密码质量。
埃里克森

1
@NLV这是bcrypt规范中定义的字符串:"OrpheanBeholderScryDoubt"
erickson

181

我认为该措词的措词如下:

bcrypt 在生成的哈希表中内置了盐,以防止彩虹表攻击。

bcrypt实用程序本身似乎没有维护盐清单。相反,盐是随机生成的,并附加到函数的输出中,以便以后记住(根据的Java实现bcrypt)。换句话说,所产生的“散列” bcrypt只是哈希值。相反,它是哈希盐串联在一起的。


20
确定,所以我注册一个站点并选择密码“ foo”。Bcrypt添加随机盐“ akd2!*”,结果为“ fooakd2!*”,将其哈希并存储。稍后,我尝试使用密码“ bar”登录。要查看我是否正确,它需要对“ barakd2!*”进行哈希处理。如果从一开始就随机生成了盐,它如何知道在哈希和比较之前如何将其重新添加到“ bar”中?
内森·朗

46
@Nathan:bcrypt知道如何从生成的输出(存储在数据库中)中提取盐。当需要进行身份验证时,bcrypt将原始输出分成其哈希和盐成分。salt组件将应用于用户键入的输入密码。
亚当·佩恩特

22
为了回答内森·朗的评论,对此的一种好的思考方法是,盐并不是秘密的。这就是为什么salt包含在bcrypt函数的输出中,作为上面指出的答案之一。盐是用来防止彩虹表的,彩虹表是常见密码的列表,或者只是蛮横的密码等不同密码但经过哈希处理的列表。如果没有salt,数据库A中密码的哈希将与数据库B中密码的哈希相同。Salt仅改变哈希值,使得偷数据库的人更难解密(取消哈希)密码。
约瑟夫·阿斯特拉罕

11
@Nathan,但是攻击者可以删除所有密码中的已知盐,然后使用它们创建表吗?
奥斯卡

3
这就是我的理解:想法是每个密码都有一个唯一的符号。密码哈希中包含了盐,因此黑客必须为每个密码创建一个彩虹表。对于中等数据库,这将花费大量时间。这一切都是为了让攻击者放慢速度,从而使暴力破解变得毫无意义。
PVermeer

0

为了使事情更加清晰,

注册/登录方向->

密码+ salt是使用由以下各项生成的密钥加密的:费用,salt和密码。我们称该加密值为cipher text。然后将盐附加到该值,并使用base64对其进行编码。附加成本,这是从产生的字符串bcrypt

$2a$COST$BASE64

该值最终被存储。

攻击者需要做什么才能找到密码?(其他方向<-)

如果攻击者控制了数据库,则攻击者将轻松解码base64值,然后他就能看到盐。盐不是秘密。虽然是随机的。然后,他将需要解密cipher text

更重要的是:此过程中没有散列,而是CPU昂贵的加密-解密。因此,彩虹表的相关性较低。


-2

这来自Spring Security的PasswordEncoder接口文档,

 * @param rawPassword the raw password to encode and match
 * @param encodedPassword the encoded password from storage to compare with
 * @return true if the raw password, after encoding, matches the encoded password from
 * storage
 */
boolean matches(CharSequence rawPassword, String encodedPassword);

这意味着,需要匹配rawPassword,该密码用户将在下次登录时再次输入该密码,并将其与在先前登录/注册期间存储在数据库中的Bcrypt编码密码匹配。


这根本没有回答问题……它没有说bcrypt如何具有内置的盐
spencer.sm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.