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

Bcrypt内置盐,以防止彩虹表攻击。

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

OpenBSD从arcfour生成128位bcrypt盐 (arc4random(3))密钥流,在内核中播种随机数据 从设备计时收集。

我不明白这是怎么回事。在我对盐的概念中:

对于每个存储的密码,它都需要是不同的,这样就必须为每个密码生成一个单独的彩虹表 它需要存储在某个地方,以便它是可重复的:当用户试图登录时,我们获取他们的密码尝试,重复与最初存储密码时相同的盐和哈希过程,并进行比较

当我使用bcrypt的设计(Rails登录管理器)时,数据库中没有盐列,所以我很困惑。如果盐是随机的,并且没有存储在任何地方,我们如何可靠地重复哈希过程?

简而言之,bcrypt如何具有内置盐?


我认为这句话应这样措词:

Bcrypt在生成的散列中内置了盐,以防止彩虹表攻击。

bcrypt实用程序本身似乎没有维护盐的列表。相反,盐是随机生成的,并附加到函数的输出中,以便稍后记住它们(根据bcrypt的Java实现)。换句话说,bcrypt生成的“哈希”不仅仅是哈希。相反,它是哈希和盐的结合。


这是bcrypt:

生成一个随机的盐。“成本”因素已预先配置。收集密码。

使用盐和成本因子从密码派生加密密钥。使用它来加密一个众所周知的字符串。存储成本、盐和密文。因为这三个元素有一个已知的长度,所以很容易将它们连接起来并存储在一个字段中,但之后还可以将它们分开。

当有人尝试验证时,检索存储的成本和盐。从输入的密码、成本和盐派生一个密钥。加密相同的知名字符串。如果生成的密文与存储的密文相匹配,则表示密码匹配。

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


存储在数据库中的bcrypt“哈希”可能是这样的:

2美元$ vI8aWBnW3fID.ZQ4 / zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa 10美元

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

2a标识使用的bcrypt算法版本。 10是成本因子;使用了210次密钥推导函数迭代(顺便说一下,这是不够的。我建议12美元或更多。) vI8aWBnW3fID.ZQ4 / zo1G.q1lRps。9cGLcZEiGDMVr5yUP1KUOYTa是盐和密文,在修改的Base-64中连接和编码。前22个字符解码为盐的16字节值。其余字符为密文,用于比较验证。

这个例子摘自Coda Hale ruby实现的文档。


为了让事情更清楚,

注册/登录方向->

password + salt使用由:cost、salt和密码生成的密钥进行加密。我们称这种加密值为密文。然后将salt附加到这个值并使用base64对其进行编码。将cost附加到它,这是bcrypt生成的字符串:

2美元成本BASE64美元

这个值最终会被存储。

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

如果攻击者控制了DB,攻击者将很容易解码base64值,然后他将能够看到盐。盐并不是秘密。尽管它是随机的。 然后他需要解密密文。

更重要的是:在这个过程中没有哈希,而是CPU昂贵的加密-解密。因此,彩虹表在这里不太相关。


这是一个简单的术语…

Bcrypt没有存储盐的数据库…

盐以base64格式....添加到哈希中

问题是bcrypt如何在没有数据库的情况下验证密码…?

bcrypt所做的是从密码哈希中提取盐…使用提取的盐来加密普通密码,并将新哈希与旧哈希进行比较,看看它们是否相同……


假设有一个表有一个散列密码。如果黑客获得访问权限,他会知道盐,但他将不得不计算所有常用密码的一个大列表,并在每次计算后进行比较。这需要时间,他只会破解1个密码。

假设在同一个表中有第二个散列密码。盐是可见的,但同样的计算需要再次发生,因为盐是不同的。

如果不使用随机盐,事情会简单得多,为什么?如果我们使用简单的哈希,我们可以只生成普通密码哈希1次(彩虹表),只做一个简单的表搜索,或简单的文件搜索db表哈希和我们预先计算哈希找到普通密码。