哈希密码字段应该使用哪种数据类型以及使用什么长度?

317

我不确定密码哈希的工作原理(稍后将实现它),但现在需要创建数据库模式。

我考虑将密码限制在4-20个字符内,但我了解到加密后的哈希字符串长度会有所不同。

那么,如何在数据库中存储这些密码呢?


另外,还请查看Openwall的PHP密码哈希框架 (PHPass)。它是便携且针对许多常见用户密码攻击进行了加固。编写此框架的人(SolarDesigner)也是编写John The Ripper以及在Password Hashing Competition中担任评委的人。因此,他对密码攻击有所了解。 - jww
3
请不要对密码设置上限。您正在将它们进行哈希处理,因此没有存储上限的原因。如果您担心DoS攻击使用密码哈希,那么1000或1024是合理的上限。 - Iiridayn
为什么要限制密码长度?至少让用户创建一个100个字符的密码 :) - Andrew
密码长度只有4个字符是非常危险的,因为这些密码很容易被破解。至少使用8个字符,但14或16个字符更好。 - quikchange
这是一个非常古老的问题,答案已经过时。请参考Gilles的答案获取最新信息。 - kelalaka
10个回答

490
更新:仅使用哈希函数来存储密码是不够安全的。你应该阅读 Gilles 在这篇文章中的回答,以获得更详细的解释。
对于密码,使用像 Bcrypt 或 Argon2i 这样的密钥加强哈希算法。例如,在 PHP 中,使用 password_hash() 函数,它默认使用 Bcrypt。
$hash = password_hash("rasmuslerdorf", PASSWORD_DEFAULT);

结果是一个60字符的字符串,类似于以下内容(但数字会因生成一个唯一的盐而变化)。
$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

使用SQL数据类型CHAR(60)来存储Bcrypt哈希的编码。请注意,该函数不会将其编码为十六进制数字字符串,因此我们不能像将其解码为二进制字符串那样容易地进行解码。
其他哈希函数仍然有用途,但不适用于存储密码,因此我将保留下面原始答案,它是在2008年编写的。
这取决于您使用的哈希算法。无论输入是什么,哈希始终产生相同长度的结果。通常将二进制哈希结果表示为文本,即一系列十六进制数字。或者您可以使用UNHEX()函数将十六进制数字字符串减半。
  • MD5生成128位哈希值。您可以使用CHAR(32)或BINARY(16)
  • SHA-1生成160位哈希值。您可以使用CHAR(40)或BINARY(20)
  • SHA-224生成224位哈希值。您可以使用CHAR(56)或BINARY(28)
  • SHA-256生成256位哈希值。您可以使用CHAR(64)或BINARY(32)
  • SHA-384生成384位哈希值。您可以使用CHAR(96)或BINARY(48)
  • SHA-512生成512位哈希值。您可以使用CHAR(128)或BINARY(64)
  • BCrypt生成一个实现相关的448位哈希值。您可能需要使用CHAR(56),CHAR(60),CHAR(76),BINARY(56)或BINARY(60)
截至2015年,NIST建议使用SHA-256或更高版本进行任何需要互操作性的哈希函数应用程序。但是,NIST不建议使用这些简单的哈希函数来安全地存储密码。
较小的哈希算法具有其用途(例如在应用程序内部使用,而不是用于交换),但它们已知是可破解的

59
请不要使用用户名作为盐值,每个用户应该生成一个随机的盐值。 - Bill Karwin
12
没问题,可以翻译。即使攻击者获取了你的数据库,也没有理由不把盐值存储在同一行。这样,他们必须基于该盐值构建彩虹表,而这与猜测密码所需的工作量是一样的。请注意,我的翻译尽可能保留了原意和用词。 - Bill Karwin
13
如果您将盐存储在相同的表中(或任何具有相同访问权限的其他位置),则使用用户名作为盐是没有问题的,因为每个用户的用户名都是唯一的。但是,任何已知的盐都会使哈希加密弱于如果不存在已知盐的情况下。仅当盐也是未知的时,盐才会增加价值。 - fijiaaron
10
我不理解已知盐和未知盐的区别。如果你在实施一个网站,那么登录页面/脚本/服务需要知道盐才能测试密码。所以,支持“未知”盐的人们是否假定攻击者无法得知登录流程的代码?否则,无论盐是随机的、唯一的、与散列密码一起存储还是分开存储,攻击者不都会始终知道盐吗? - mattstuehler
5
回复@fijiaaron关于“没有理由不使用用户名作为盐”的问题,请参见http://security.stackexchange.com/a/41618/2572 。盐应该_全局_唯一,即在整个互联网范围内唯一。将盐与密码一起存储在数据库中是_可以的_。使用用户名作为盐会削弱其安全性。 - Iiridayn
显示剩余33条评论

29

始终使用密码哈希算法:Argon2, scrypt, bcryptPBKDF2

Argon2赢得了2015年的密码哈希竞赛。Scrypt, bcryptPBKDF2是较旧的算法,现在被认为不太受欢迎,但仍然基本上是可靠的,所以如果您的平台还不支持Argon2,现在使用其他算法也可以。

永远不要直接将密码存储在数据库中。也不要加密它:否则,如果您的网站被攻击,攻击者会获得解密密钥,从而可以获取所有密码。密码必须进行哈希处理。

一个密码哈希与哈希表哈希或加密哈希具有不同的属性。绝不要在密码上使用普通的加密哈希,例如MD5、SHA-256或SHA-512。密码哈希算法使用唯一的盐(不用于任何其他用户或任何其他数据库)。盐是必需的,以便攻击者不能预先计算常见密码的哈希值:有了盐,他们必须为每个帐户重新启动计算。密码哈希算法本质上是缓慢的——尽可能缓慢。缓慢会对攻击者造成更大的伤害,因为攻击者必须尝试许多不同的密码。有关更多信息,请参见如何安全地哈希密码
密码哈希编码了四个信息片段:
  • 一个指示所使用算法的指标。这对于灵活性是必要的:加密建议随时间而变化。您需要能够转换到新算法。
  • 困难或硬度指标。该值越高,计算哈希所需的计算量就越大。这应该是密码更改函数中的常量或全局配置值,但随着计算机变得更快,它应该随时间增加,因此您需要记住每个账户的值。有些算法只有一个数字值,而其他算法则有更多参数(例如单独调整CPU使用率和RAM使用率)。
  • 盐。由于盐必须全局唯一,因此必须为每个账户存储它。盐应在每次更改密码时随机生成。
  • 哈希本身,即散列算法中数学计算的输出。

许多库包括一对函数,将此信息方便地打包为单个字符串:一个函数接受算法指示符、硬度指示符和密码,生成随机盐并返回完整的哈希字符串;另一个函数接受密码和完整哈希字符串作为输入,并返回一个布尔值,表示密码是否正确。没有通用标准,但常见编码是

$algorithm$parameters$salt$output

其中algorithm是一个数字或短的包含算法选择信息的字母数字字符串,parameters是可打印的字符串,saltoutput以Base64编码,不包含终止符=

盐和输出需要16个字节足够了(参见例如Argon2的推荐)。以Base64编码,每个部分需要21个字符。另外两个部分取决于算法和参数,但通常为20-40个字符。总共大约82个ASCII字符CHAR(82),不需要Unicode),如果您认为将来可能需要扩大字段,则应添加一些安全余量。

如果您以二进制格式编码哈希值,则可以将其缩小到1个字节的算法,1-4个字节的硬度(如果硬编码某些参数),以及16个字节的盐和输出,总共37个字节。说是40个字节BINARY(40))以便至少有几个备用字节。请注意,这些是8位字节,而不是可打印字符,特别是该字段可以包含空字节。

请注意,哈希值的长度与密码的长度完全无关。


15

你实际上可以使用 CHAR(哈希长度) 来定义 MySQL 中的数据类型,因为每个哈希算法始终会计算出相同数量的字符。例如,SHA1 始终返回一个由40个十六进制数字组成的字符串。


2
SHA-1 不适合用于密码哈希。 - Gilles 'SO- stop being evil'

13

你可能会发现这篇关于盐值的维基百科文章有价值。其思想是添加一组数据来随机化哈希值,从而保护你的密码免受字典攻击,即使有人未经授权访问了密码哈希。


2
这确实非常有价值(+1),但它并没有回答问题!(-1) - Bill Karwin
5
是的,在这个语境中绝对是相关的(+1)。 - Treb

11

为了确保向前兼容性,您应该使用TEXT(可存储无限数量的字符)。哈希算法随着时间的推移变得越来越强大,因此这个数据库字段需要随着时间的推移支持更多的字符。此外,根据您的迁移策略,您可能需要在同一字段中存储新旧哈希值,因此不建议将长度修复为一种类型的哈希值。


1
使用TEXT字段的另一个支持理由是我认为密码哈希值没有必要建立索引。 - HardlyNoticeable

10
作为固定长度的字符串(VARCHAR(n)或 MySQL 的其他称呼),哈希值始终具有固定长度,例如12个字符(取决于所使用的哈希算法)。因此,一个20个字符的密码将被缩短为一个12个字符的哈希值,而一个4个字符的密码也会产生一个12个字符的哈希值。

3
“或者MySQL称它为什么都可以” - MYSQL 称其为 CHAR。这种类型的值是固定长度的,因此我认为 CHAR 类型比 VARCHAR 更好。 - bujals

6

哈希是一系列位(128位、160位、256位等,取决于算法)。如果MySQL允许,则您的列应为二进制类型,而不是文本/字符类型(SQL Server数据类型为binary(n)varbinary(n))。您还应该对哈希进行盐处理。盐可以是文本或二进制,并且您需要相应的列。


这里Justice是完全正确的 - MySQL将把它们存储为数字值,并且在该列上进行搜索比执行字符串匹配更有效率,但盐不应该与加盐数据一起存储在数据库中 - 这会消除盐提供的安全性。 - Tony Maro
6
盐值并非机密信息,唯一的机密是密码。请确保每个新密码都有一个新的盐值。每当用户更改密码时,系统应为该密码生成一个新的盐值。盐值应该是长且随机的,例如由加密安全 PRNG 生成的 16 字节。 - yfeldblum
1
@TonyMaro 不确定在 SQL 级别上进行密码字符串匹配是否是一个好策略。换句话说,您不应该在数据库中搜索密码,而是根据用户名检索用户,并在代码中比较密码,而不是在 SQL 中比较。 - bart

4

这真的取决于你使用的哈希算法。密码的长度与哈希的长度关系不大,如果我没记错的话。查找你正在使用的哈希算法的规格,运行几个测试,并在上面截断。


2

我一直在测试加密字符串的最大长度,并将其设置为VARCHAR类型的字符长度。根据你将要拥有的记录数量,这可能会真正帮助减小数据库大小。


-1

对于 MD5,VARCHAR(32) 是合适的。对于使用 AES 的用户最好使用 VARBINARY。


1
MD5和AES都不适合用于密码哈希。 - Gilles 'SO- stop being evil'

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