我应该使用啥列类型/长度将 Bcrypt 散列密码存储在数据库中?

Posted

技术标签:

【中文标题】我应该使用啥列类型/长度将 Bcrypt 散列密码存储在数据库中?【英文标题】:What column type/length should I use for storing a Bcrypt hashed password in a Database?我应该使用什么列类型/长度将 Bcrypt 散列密码存储在数据库中? 【发布时间】:2011-08-18 09:06:10 【问题描述】:

我想在数据库中存储一个散列密码(使用 BCrypt)。什么是一个好的类型,哪个是正确的长度?使用 BCrypt 散列的密码是否总是相同的长度?

编辑

示例哈希:

$2a$10$KssILxWNR6k62B7yiX0GAe2Q7wwHlrzhF3LqtVvpyvHZf0MwvNfVu

在对一些密码进行哈希处理后,BCrypt 似乎总是生成 60 个字符的哈希值。

编辑 2

抱歉没有提到实现。我正在使用jBCrypt。

【问题讨论】:

另见 Openwall 的 php password hashing framework (PHPass)。它的便携性和强化了针对用户密码的一些常见攻击。编写框架 (SolarDesigner) 的人与编写 John The Ripper 并在 Password Hashing Competition 担任评委的人是同一个人。所以他对密码攻击略知一二。 如果有人为此寻找 scrypt 的解决方案:Gumbo 的回答也适用于 scrypt。我个人在 mysql 中应用了 BINARY(64),它允许我稍后在 Python 下测试字节相等。 【参考方案1】:

bcrypt 的模块化 crypt 格式包括

$2$$2a$$2y$ 识别 hashing algorithm and format 表示成本参数的两位数,后跟$ 一个 53 个字符长的 base-64 编码值(它们使用字母表 ./09AZaz不同于standard Base 64 Encoding 字母表),包括: 22 个盐字符(实际上只有 132 个解码位中的 128 个位) 31 个字符的加密输出(实际上只有 186 个解码位中的 184 个位)

因此总长度分别为 59 或 60 个字节。

当您使用 2a 格式时,您需要 60 个字节。因此对于 MySQL,我建议使用 CHAR(60) BINARYor BINARY(60)(有关差异的信息,请参阅 The _bin and binary Collations)。

CHAR 不是二进制安全的,相等性不仅仅取决于字节值,还取决于实际的排序规则;在最坏的情况下,A 被视为等于a。请参阅The _bin and binary Collations 了解更多信息。

【讨论】:

请注意 - 存储为二进制 (60) 可能会导致字符串相等的意外行为(除其他外)。在 .NET 中,这可以通过使用 String.Equals(fromDataBaseBinary60string,typicalishString, StringComparison.InvariantCulture) 来克服 如果您将列定义为 CHAR(60) CHARACTER SET latin1 COLLATE latin1_bin,您现在可以获得精确字符串比较的优势,而无需二进制列。 @AndreFigueiredo SQL_Latin1_General_CP1_CS_AS 在 MySQL 中是未知的。已知的是latin1_general_cs 我不清楚我们应该存储为不是二进制安全的char 还是具有意外行为的binary(60) ..... @Neon 问题是您可能会比较不同的哈希值是否相等。如果您明确指定它是二进制列(或具有正确排序规则的 VARCHAR),则不会冒在其他地方更改某些设置使其成为不区分大小写比较的风险。它还使您的意图更加清晰,这通常是一件好事——您正在存储二进制数据;您应该将其存储为二进制数据。【参考方案2】:

Bcrypt 哈希可以存储在BINARY(40) 列中。

BINARY(60),正如其他答案所暗示的那样,是最简单和最自然的选择,但如果你想最大化存储效率,你可以通过无损解构哈希来节省 20 个字节。我在 GitHub 上对此进行了更详尽的记录:https://github.com/ademarre/binary-mcf

Bcrypt 哈希遵循称为模块化密码格式 (MCF) 的结构。 二进制 MCF (BMCF) 将这些文本哈希表示解码为更紧凑的二进制结构。对于 Bcrypt,生成的二进制哈希为 40 字节。

Gumbo 很好地解释了 Bcrypt MCF 哈希的四个组成部分:

$<id>$<cost>$<salt><digest>

解码为 BMCF 如下:

    $&lt;id&gt;$ 可以用 3 位表示。 &lt;cost&gt;$,04-31,可以用 5 位表示。将它们放在一起 1 个字节。 22 个字符的盐是 128 位的(非标准)base-64 表示。 Base-64 解码产生 16 个字节。 31 个字符的散列摘要可以 base-64 解码为 23 个字节。 将它们放在一起 40 个字节:1 + 16 + 23

您可以在上面的链接中阅读更多内容,或查看my PHP implementation,也可以在 GitHub 上查看。

【讨论】:

较长字段的成本:20 字节乘以甚至一百万条记录+:20MB,一旦达到一百万条记录+。在高度复杂的安全和工程领域中不正确地实施缩短的字段长度的成本: $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$你算算。 @Kzqai,就像我说的,更大的 60 字节列是最自然的选择,但如何积极地追求存储效率取决于项目。例如,尝试将整个数据库放入内存是很常见的,在内存受限的环境中,这里的 20 MB 和那里的另外 20 MB 可以很快加起来。 你的例子符合我的观点。 --- 如果要将数据库放入内存,请在接触 bcrypt 存储列之前优化每隔一列。 --- 如果你已经将其他列优化到了疯狂的程度,并且只剩下 bcrypt 哈希列,那么再为 bcrypt 获取另一块内存。 --- 如果您已经完成了上述两项... ...停下来,您还没有优化其他每一列容易获得的成果,并且您即将弄乱经过测试的有效加密安全系统,并替换它具有更复杂的本土系统,实施失败的可能性。 @Kzqai 这里没有削弱 Bcrypt 库安全性的风险。这是一种数据编码,在密码检查之前从存储中检索时会被撤消。这不是“不要推出自己的加密货币”领域。 很好的解释。 :) 虽然你的解释给出了一个好主意,但为了安全起见,我只想使用 60 个字符,甚至 100 个字符。很好的辩论@Kzqai 和 AndreD【参考方案3】:

如果您使用 PHP 的 password_hash()PASSWORD_DEFAULT 算法来生成 bcrypt 哈希(我认为这是阅读这个问题的人的很大比例)请务必记住,在未来 password_hash()可能会使用不同的算法作为默认值,因此这可能会影响哈希的长度(但不一定会更长)。

来自手册页:

请注意,此常数旨在随着时间的推移而变化 PHP 中添加了更强大的算法。因此,长度 使用此标识符的结果可能会随着时间而改变。所以, 建议将结果存储在一个数据库列中,该列可以 超过 60 个字符(255 个字符是一个不错的选择)。

使用 bcrypt,即使您有 10 亿用户(即您目前正在与 facebook 竞争)来存储 255 字节的密码哈希,它也只会存储大约 255 GB 的数据——大约是一个小型 SSD 硬盘的大小。存储密码哈希极不可能成为应用程序的瓶颈。但是,如果由于某种原因存储空间确实问题,您可以使用PASSWORD_BCRYPT 强制password_hash() 使用bcrypt,即使这不是默认设置。只要确保随时了解 bcrypt 中发现的任何漏洞,并在每次发布新的 PHP 版本时查看发行说明。如果默认算法发生变化,最好回顾一下为什么并做出是否使用新算法的明智决定。

【讨论】:

【参考方案4】:

我不认为你可以使用任何巧妙的技巧来存储它,例如使用 MD5 哈希。

我认为最好的办法是将其存储为 CHAR(60),因为它总是 60 个字符长

【讨论】:

虽然,PHP 文档指出列应该能够容纳更多数据,以备将来发布... 没有理由镀金。如果您使用的软件需要 60 个字节,则分配 60 个字节。如果您的软件的未来版本会改变这一点,那么当该版本发生时您可以担心它。您不应该自动安装更改功能的更新。 我认为这是最好的答案。无需像其他答案那样深入研究算法的复杂性。所有关于二进制、排序规则等的细节都应该由正在使用的任何库处理。 60 个字符。这就是答案。 请注意,对于某些数据库 (postgresql),“大小”列不是强制性的。

以上是关于我应该使用啥列类型/长度将 Bcrypt 散列密码存储在数据库中?的主要内容,如果未能解决你的问题,请参考以下文章

使用 bcrypt 散列密码迁移系统

Golang 中的 Bcrypt 密码散列(与 Node.js 兼容)?

如何使用散列 bcrypt 版本更新数据库中的每个密码?

bcrypt 哈希究竟如何防止彩虹表查找?

Bcrypt 是用于散列还是加密?有点迷茫

使用密码哈希时生成的哈希的最大长度?