How can bcrypt have built-in salts?
Coda Hale的文章"如何安全存储密码"声称:
bcrypt has salts built-in to prevent rainbow table attacks.
他引用了这篇文章,其中说在OpenBSD的bcrypt的实现中:
OpenBSD generates the 128-bit bcrypt salt from an arcfour
(arc4random(3)) key stream, seeded with random data the kernel
collects from device timings.
我不明白这是怎么回事。在我对盐的概念中:
- 对于每个存储的密码,它需要是不同的,因此必须为每个存储的密码生成一个单独的彩虹表。
- 它需要存储在某个地方,这样它才是可重复的:当用户试图登录时,我们会尝试使用他们的密码,重复最初存储他们的密码时所做的相同的salt和hash过程,并比较
当我将DEVICE(一个Rails登录管理器)与Bcrypt一起使用时,数据库中没有salt列,所以我很困惑。如果盐是随机的,并且没有存储在任何地方,那么我们如何可靠地重复散列过程呢?
简而言之,Bcrypt怎么能有内置盐呢?
这是BCRIPT:
生成随机盐。预先配置了"成本"系数。收集密码。
使用salt和成本系数从密码派生加密密钥。使用它加密一个众所周知的字符串。存储成本、盐和密码文本。因为这三个元素有一个已知的长度,所以很容易将它们连接起来并存储在一个字段中,但是稍后可以将它们分开。
当有人试图验证时,检索存储的成本和盐。从输入密码、成本和salt派生一个键。加密相同的已知字符串。如果生成的密码文本与存储的密码文本匹配,则密码是匹配的。
BCRYPT的运行方式与基于PBKdf2等算法的更传统的方案非常相似。主要区别在于它使用派生密钥加密已知的纯文本;其他方案(合理地)假定密钥派生函数是不可逆的,并直接存储派生密钥。
存储在数据库中的bcrypt的"hash"可能如下所示:
$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa
这实际上是三个字段,由"$"分隔:
- 2a标识所使用的bcrypt算法版本。
- 10是成本因素;使用210次关键派生函数迭代(顺便说一下,这还不够)。我建议花费12英镑或更多。)
- vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa是盐和密码文本,在修改的base-64中连接和编码。前22个字符解码为salt的16字节值。其余字符是要进行身份验证比较的密码文本。
这个例子取自CodaHale的Ruby实现文档。
- 你能更详细地解释一下为什么10的成本系数不够吗?在Grails中,我注意到10是bcrypt的成本因素/日志轮次的默认值,因此考虑到您的建议,它可能值得更新。
- @埃里克森花了16块钱在我的主人身上花了将近10秒。我不能每次有人登录都这样。我的硬件真的那么糟糕吗?
- @爆炸药你用什么语言和图书馆?如果一个解释器正在执行大部分算法,它可能会慢得多。
- @Erickson php,带crypt(CRYPT_BLOWFISH)。我搞砸了吗?
- @我恐怕对PHP不太了解。据我所知,现在大多数PHP部署都使用预编译的字节代码,这应该相当快。你知道这个EDOCX1 3函数是否由更多的PHP代码实现,或者实际上是由一个编译的库(用C或C++编写并为主机体系结构编译)实现的?如果是后者,似乎应该是快速的。
- @爆炸药片我做了一些基准测试,bcrypt密钥生成算法非常慢,比我推荐成本因素时假设的具有sha-1 HMAC的pbkdf2慢得多。成本系数为16的bcrypt在我的机器上大约需要5.6秒。在那个时候,我可以做近400万次pbkdf2迭代。因此,BCRYPT的成本系数可以安全地降低到12。即使在主机上,这也需要不到1秒钟的时间。
- BCRYPT的成本系数是指数的,或者更确切地说,成本系数10表示2^10轮(1024),成本系数16表示2^16轮(65536)。那自然需要5-10秒。它应该是成本系数10的64倍。为了清除其他错误信息,PHP的crypt函数使用了在C语言中实现的unix crypt库。
- @Thomasrutter感谢您回答我关于PHP的crypt()实现的问题。不过,我不知道有什么"误传"。你能澄清一下吗?
- 很抱歉,只是参考您的猜测,crypt可以作为php实现,并作为字节码执行,而不是(它称为C库)。我意识到你并不是说实话,因为你不确定。只是清理一下。
- 操作模式是有意义的,但是如果我有权访问数据库,并且可以为我生成密码(在device中),那么现在我似乎可以将它复制到任何其他用户的加密密码字段条目中,并且我"知道"他们的密码。我错过了什么?
- @TjChambers是正确的;如果您可以在帐户上设置密码,那么您将能够进行身份验证。密码散列并不是为了防止这种攻击。它旨在防止对密码表具有只读访问权限的攻击者进行身份验证。例如,您得到一个带有表的备份磁带。
- 谢谢@Erickson。我想我的理论(如果我理解正确的话)是,如果有人闯入足够远的地方来访问密码数据库本身,并且可以写入数据库,他们可以建立自己的密码到一个已知的帐户(他们自己的),然后他们可以通过将加密的密码复制到该帐户并记录来访问任何人的帐户。在。至少在对方意识到他们无法登录并重置密码之前。似乎将主键(即用户名或电子邮件)包含到嵌入的salt进程中可以消除这一点。
- 我必须假设攻击者可以使用相同的已知输入计算散列值。否则,我依靠的是"通过默默无闻的安全",而不是强大的密码术。为了阻止能够写入表的攻击者,我需要能够隐藏一些可以用来验证表中信息的主秘密。例如,计算表或其条目上的数字签名或MAC。而且要对这样的攻击者隐瞒大师的秘密是很困难的。
- 如果我理解正确,@erickson,那么就需要一个"未知"输入来阻止访问。或者至少是一个未知的验证结果(比如数字签名),以至少检测到入侵。
- @TjChambers是的。但大多数密码认证系统都不包含这一点,因为如果攻击者有写访问权,您就已经丢失了。
- @埃里克森关于这个陈述的问题——"使用派生密钥加密已知的纯文本的主要区别;其他方案(合理地)假定密钥派生函数是不可逆的,并直接存储派生密钥。"因此,准确地说,破坏bcrypt将取决于如何突破两种算法吗?s——既有kdf又有cipher,但是像pbkdf2这样的东西只需要破坏kdf?因此,从这个意义上说,BCRYPT更安全?
- @Aerovistae是的,如果密码和密钥派生功能都被破坏,那么bcrypt只能被"破坏"(在恢复密码的快捷方式中)。
- 把盐和刨花放在一起安全性不好吗?如果有人得到了散列值,有了足够的计算,它就可以被破解。如果他不知道盐的含量,那几乎是不可能的。
- @不,不是真的。如果你能保守秘密,你就不会使用这种方法,你只需要存储密码。密码验证方案基于攻击者已经发现您所知道的一切的假设。Salt需要单独攻击每个密码。测试密码所需的计算工作由迭代控制。如果用户选择了好的密码,他们将是安全的,即使当盐被泄露。在某些情况下,隐藏salt可以帮助密码不好的人,但我会先研究密码质量。
- 我还是不明白。这里的"众所周知的字符串"是什么?
- @NLV是在bcrypt规范中定义的字符串:"OrpheanBeholderScryDoubt"。
- 啊!现在一切都有意义了。这也有助于-crypto.stackexchange.com/a/41958
我认为该短语的措辞如下:
bcrypt has salts built into the generated hashes to prevent rainbow table attacks.
bcrypt实用程序本身似乎没有维护盐的列表。相反,随机生成盐并附加到函数的输出,以便以后记住它们(根据EDCOX1(0)的Java实现)。换句话说,bcrypt生成的"hash"不仅仅是hash。相反,它是哈希和盐的连接。
- 相关:stackoverflow.com/questions/5881169/…
- 好的,所以我注册一个网站,然后选择一个密码"foo"。Bcrypt添加了随机盐"akd2!*",结果是"fooakd2!*",这是散列和存储的。稍后,我尝试用密码"bar"登录。为了看我是否正确,它需要散列"barakd2!*如果盐是随机生成的,那么在散列和比较之前,它如何知道如何将其添加回"bar"中?
- @nathan:Bcrypt知道如何从生成的输出(存储在数据库中)中提取盐。当需要进行身份验证时,Bcrypt将原始输出分离为散列和salt组件。salt组件应用于用户输入的密码。
- 回答内森·朗的评论,一个很好的思考方法是盐不应该是秘密的。这就是为什么BCrypt函数的输出中包含salt的原因,正如上面指出的答案之一。盐是用来防止彩虹表,这是常见的密码列表,或只是蛮力,等等…不同的密码,但散列。如果没有salt,数据库A中密码的散列值将与数据库B中密码的散列值相同。salt只会更改散列值,使窃取数据库的人更难解密(unhash)密码。
- @但是攻击者能否删除所有密码中的已知盐类,然后用它们创建一个表?
- 我正在等待有关@oscar comment的回复。而且我还没有得到最后的声明。所以,当匹配输入的密码时,我们只需要调用相同的函数(在加密时使用),它将再次生成相同的哈希密码?这是正确的陈述还是我遗漏了什么?
- @Amarmishra看到了Joseph的评论:密码仍然与应用的salt一起存储,salt是每个用户的随机值。这意味着攻击者不能使用预先存在的彩虹表。他/她必须分别对每个密码进行暴力破解。
- 但是假设一个用户是一个总统,他的盐是已知的,一个彩虹表可以专门为盐生成,对吗?当然要花很长时间,但在这种情况下,一种神秘的盐听起来更好。
- 你不需要建立一个彩虹表来破解一个密码。如果您想将盐存储在数据库之外的其他地方,请继续添加您自己的第二个盐。不过,将其存储在与密码相同的数据库中是毫无意义的。
- @Josephastrahan如果有人偷了数据库,那么他们可以查看哈希表,并使用正确的盐进行彩虹攻击?还是我错过了什么?
- 啊,没关系。他们指出:彩虹表是一个加密密码列表(不含salt),因此它是一个预先计算的哈希列表。用盐,这些桌子一文不值。但是,有了足够的计算能力,当他们拥有数据库时,他们仍然可以根据常用密码和现在已知的盐生成加密值。所以破解需要更多的努力,所以它为您提供了一些时间来缓解问题并通知受影响的用户。
- 我就是这样理解的:我的想法是每个密码都有一个独特的salt。salt包含在密码散列中,因此黑客必须为每个密码创建一个彩虹表。对于一个适度的数据库来说,这将花费大量的时间。这一切都是为了减慢攻击者的速度,从而使野蛮的强迫毫无意义。
- 是什么阻止黑客仅仅从土豆泥中取出盐,并将其与未加盐的土豆泥进行比较?
- @按照我的理解,这是不可能的,因为哈希不是哈希和盐的串联。salt本身是散列的(即将salt添加到密码的串联过程发生在散列之前)。换句话说,即使salt是"salt",字符串"salt"也有可能出现在salt密码的哈希中(如果是,则是随机的,而不是实际的salt)。
- @hemal如果运行这个relp.it链接,可以看到bcrypt实际上只是将生成的salt直接添加到散列中。您可以看到,因为在生成salt时,两个具有相同salt(用户名和密码)的不同哈希字符串的开头完全相同。如果它与散列混合在一起,那么当散列为散列时,输出应该是不同的。但它不是,只有包含哈希的字符串的结尾是:s
- @凯文弗罗斯塔德,你说得对——那是一段时间前的事了(从那以后我一直在读这篇文章),所以我不知道当我最初回答这个问题时,我是直接误解了一些事情,还是说得很糟糕——不管是哪种方式,我都道歉。但是回到你原来的QN:是什么阻止了黑客仅仅从散列中移除盐,并将其未加盐的散列与它进行比较?如果我是错的,请纠正我(我不确定"它"指的是什么),但我理解你的问题是盐散列和不盐散列的比较,对吗?…1/2
- 2/2如果一个bcrypt密码被黑客入侵,黑客将在此时拥有一个盐散列-他可以从中提取盐(如您的repl所示)和哈希密码。但是,他不会拥有密码本身,也不会拥有没有salt的密码哈希-也就是说,他将没有任何东西可以将salt哈希与用于创建哈希的原始密码进行比较。换句话说,盐+pwd=salted散列。散列是一个单向函数,它不允许黑客生成pwd,即使使用salt。
- @我知道salt是用来防止彩虹表攻击数据库中的哈希密码的。但是当salt连接到哈希的前面时,黑客可以将他的哈希彩虹表与数据库中的bcrypt哈希进行比较,以发现bcrypt哈希的子字符串包含密码的真正哈希输出…1/2
- 2/2换句话说,因为salt与散列输出相连,所以黑客不需要花费太多的精力来确定彩虹表输出的密码,彩虹表包含常见密码的未加保护的bcrypt散列。他只需要把盐去掉。这可能需要一些迭代,但似乎不是延迟获取密码的主要因素。所有盐的长度都相同的可能性很高。然后,一旦找到第一个匹配项,他就可以为每个密码替换salt。
- @赫马尔就是一个例子。如果我有3个最常用密码的bcrypt散列,那么我能找到与前1000个散列的子字符串匹配的可能性是好的,几乎可以确定与前10000个散列的匹配。现在,如果每个生成的salt字符串的长度与我在第一次与最常用的密码之一匹配时发现的长度相同。然后,我只需要从数据库中去掉每个密码的第一部分,我知道其中包含salt。
- Kevin,散列是从salt+password+key生成的,所以对于不同的密码,即使使用相同的salt,散列也是不同的。这意味着你不可能为三个最常用的密码提供方便的哈希——这是不存在的。您需要使用已知的即时盐破解一个新的哈希,但是您需要第三个元素:加密密钥,它本身就是一个"强密码"。随机的salt增加了一层困难,因为你不能用预先散列的密码来攻击它。您必须散列所有密码才能尝试每个salt,即o(n^2)。
- @Kevinfrostad您找到了密码哈希列表。"123456"是一个非常常见的密码,您可以生成它的散列值,并针对每个条目进行尝试:即时访问大量帐户。有了salt,所有蹩脚的"123456"实际上都会被修改为前缀为一个唯一的salt(纯文本密码,而不是哈希)。现在需要为每个条目生成sha1散列("ak5t123456"、"j3rt123456"、"7w8d123456")。您需要更多的时间来访问同一组帐户,从而使服务有更多的时间来发现并对违规行为作出反应。
- @kevinfrostad保护salt,虽然可能会增加另一层安全性,但它不会减慢身份验证过程,但不一定会减慢攻击者的速度(这也可能会鼓励一些人减少工作因素)。您仍然可以为Bcrypt Afaik提供您自己的盐。但是,我不确定保护salt out的成本是否会加重不保护它的成本—如果auth服务器受到影响,而salt的目的没有受到影响,那么这一切都将浪费。
- @我明白了。我认为像bcrypt这样的哈希算法应该是如此安全,以至于你可以存储它们供任何人查看。我猜一个密码只有明文版本那么强。如果我计算出盐的长度,找到123456的明文只需要几秒钟左右;如果盐没有29个字符的致命长度,这可能只需要几个小时。从那时起,找到弱密码只需要散列速度(并比较与ofc匹配的散列)。
- @知道盐的长度并不能以任何有意义的方式降低努力的程度。您仍然必须单独攻击每个密码。每一次成功的攻击都独立于下一次攻击。破解一个弱密码不会获得任何优势,只会破解一个弱密码。
这是来自Spring Security的密码编码器接口文档,
1 2 3 4 5 6
| * @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编码密码匹配。