我不确定密码哈希的工作原理(稍后将实现),但现在需要创建数据库架构。
我正在考虑将密码限制为4-20个字符,但是据我了解,加密哈希字符串后的长度将不同。
那么,如何将这些密码存储在数据库中呢?
-
另请参见Openwalls PHP密码散列框架(PHPass)。 它具有可移植性,并且可以抵抗多种常见的用户密码攻击。 编写框架的人(SolarDesigner)与编写John The Ripper的人相同,并担任密码哈希竞赛的评委。 因此,他知道有关密码攻击的一两件事。
-
请不要对密码设置上限。 您正在对它们进行哈希处理,没有存储上限的原因。 如果您担心使用密码哈希进行DoS攻击,则1000或1024是合理的上限。
-
为什么限制密码长度? 至少让用户创建一个100个字符的密码:)
-
4个字符对于密码来说是一个非常危险的下限,因为这些密码很难破解。 至少使用8,但最好使用14或16。
-
这是一个很老的问题,答案已经过时。 有关最新信息,请参见Gilles的答案。
更新:仅使用哈希函数不足以存储密码。您应该阅读Gilles在该主题上的答案,以获取更详细的解释。
对于密码,请使用增强密钥的哈希算法,例如Bcrypt或Argon2i。例如,在PHP中,使用password_hash()函数,该函数默认使用Bcrypt。
1
| $hash = password_hash("rasmuslerdorf", PASSWORD_DEFAULT); |
结果是一个类似于以下内容的60个字符的字符串(但是数字会有所不同,因为它会生成唯一的盐)。
1
| $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不建议使用这些简单的哈希函数来安全地存储密码。
较小的散列算法有其用途(例如在应用程序内部,而不是用于交换),但众所周知它们是可破解的。
-
可以通过在每个密码前添加一个唯一值(例如用户名)来实现加盐。以下是一篇文章,解释了为什么有必要这样做的情况:codinghorror.com/blog/2007/09/rainbow-hash-cracking.html。简而言之,它有助于防止使用Rainbow攻击方法的密码破解尝试。
-
@河马:请不要使用用户名作为盐。为每个用户生成随机盐。
-
随机生成的盐是否也存储在同一表行中?
-
是的,没有理由不将其存储在同一行中。即使攻击者可以访问您的数据库,他们也必须基于该盐构建彩虹表。这和猜测密码一样有效。
-
@Bill Karwin:所以您要说的只是将盐设置为密码后面的x个字符?当用户输入密码登录时,您如何知道所使用的随机盐是什么,以便获得等效的哈希值?
-
@SgtPooki:您需要另一列以纯文本格式存储盐。然后,您可以在用户输入密码时用相同的盐对用户密码进行哈希处理,然后将结果与存储在表中的哈希摘要进行比较。
-
@Bill Karwin:如果我要破解一个密码数据库(我自由地承认我无法做到),那么我肯定会尝试一些不明身份的字段。我了解一些彩虹表,但是可能不足以认为有人能够编写替代方法/盐脚本,只需将密码表中的每一列作为盐应用,然后运行彩虹表。因此,对于没有盐的表,不是用一个彩虹表,而是每行有多少列彩虹表?我感到困惑吗?还是让黑客为所有用户担心行*列花费了太多精力?
-
@SgtPooki:请记住,为给定的盐生成彩虹表需要大量的工作和大量的空间。通过穷举搜索实际上更容易猜出密码,即使您知道密码也是如此。
-
如果您将盐存储在同一表(或具有相同访问权限的任何其他位置)中,则没有理由不使用用户名作为盐,因为用户名是唯一的。但是,与没有已知盐相比,任何已知盐都使哈希算法在密码上较弱。盐只有在还未知的情况下才能增加价值。
-
同样,较低位的哈希值也更不容易破解。可能性较小,因此蛮力破解更可行,但是尽管有许多方法可以模拟MD5或SHA1哈希,但它们的算法仍然没有经过验证的缺陷。简单的strlen()检查使重复密码散列变得不可行。并不是说我说MD5或SH1是一个完全安全的解决方案,只是出于脚。
-
我不了解与已知盐与未知盐的交易。如果您要实施站点,则需要在登录页面/脚本/测试密码的服务中知道该密码。因此-您是"未知的"盐倡导者-您是否假设攻击者未知登录过程的代码?否则-攻击者是否总是知道盐,它是随机的,唯一的,与哈希密码一起存储还是分开存储?
-
我对为什么您需要CHAR的两倍多感到困惑,例如CHAR(64),当char只有一个字节长时,您是否可以在CHAR(32)中放入256位值?
-
@ Sja91,是的,如果要在一个字节中存储0x00-0xFF,请使用BINARY()。这是没有字符集的字符串数据类型。但是,如果您直接读取不可打印的二进制字符串,则可能会使屏幕发疯。如果要存储显示友好的十六进制字符串,则可以使用CHAR()。但是它需要两个十六进制数字字符来表示0x00-0xFF范围内的值。
-
存储PBKDF2加密密码时要使用哪种数据类型?看来nvarchar(max)存在性能问题,您认为可以使用128长度来改变长度吗?
-
@stom,是的,长度为128可以容纳SHA512哈希,这样就足够了。
-
响应@fijiaaron,wrt"没有理由不将用户名用作盐",请参阅security.stackexchange.com/a/41618/2572。盐应在整个Internet上具有全球唯一性。将盐与密码一起存储在数据库中很好。使用用户名作为盐会削弱它。
-
@mattstuehler您的登录页面不应该进行密码测试,如果您不使用SSL,则可以在登录页面中进行初步的哈希处理,但是实际的密码哈希不应该离开存储空间,因此您将登录详细信息传递给服务器返回是否成功,
-
@Iiridayn这是你们俩都是对的情况,唯一的盐总是比用户名更好,但是如果您不保留盐,则保护级别会急剧下降,直到其可比较的程度,但仍然比使用用户名更好
-
@MikeT,您可以使用用户名作为常见用户名的盐来有效地预先计算彩虹表。防止计算前攻击可提高安全性。请参阅security.stackexchange.com/a/5506/2572和owasp.org/index.php/,其中建议使用32或64个随机字节。用户名是可预测的且简短。
-
@Iiridayn,您可以使用已知的盐进行预计算,因此,如果您知道站点X使用123的盐来计算密码,则您可以使用salt预计算密码列表,因为结果表不能在站点之间使用,因此略有改进过多的用户名,但是当您考虑到攻击者可能一次将一个网站作为目标时,用户名就不多了,因此不建议使用已知盐
-
@MikeT,您是否认为给定站点中的每个用户都使用相同的盐?否-每位用户随机使用盐。然后,必须为每个用户创建一个不同的Rainbow表,这使其与蛮力密码猜测相比不那么昂贵(考虑到存储,可能会更高)。
-
@BillKarwin,没有账单即时消息说如果您不正确使用盐,它们比使用用户名要好得多,某些系统会使用共享或预定义的盐来散列密码,这是一个常见错误,会导致创建不安全的系统,这意味着您必须通过为每个密码创建新密码以及使盐不属于公共域来使盐未知,理想情况下,密码哈希和盐都不应该离开您的服务器,如果服务器受到威胁,则应要求所有登录名更改密码并创建他们做的时候放新盐
-
这意味着,如果fijiaaron正在使用用户名作为已知盐与任何其他已知盐进行比较,它们完全正确,则几乎没有区别,但是,如果您谈论的是正确使用的未知盐,则Iiridayn是正确的,并且用户名无用
-
是的,没有理由公开盐。我认为没有人建议这样做。服务器端代码需要查询各个用户的salt,然后将其用于哈希用户输入,然后将该结果与先前存储在数据库中的密码的哈希版本进行比较。盐值绝不会离开服务器或公开。
-
食盐总比不食盐要好,但仍不足够,在提供MD5之类的尺寸时,您忘记考虑到这一点。 MD5,SHA-256和其他"普通"哈希算法不适用于密码(而且,NIST不建议将它们中的任何一种用作密码)。您必须存储盐,并且必须使用密码哈希算法,例如PBKDF2,bcrypt,scrypt或Argon2。
-
@Gilles,请参阅我在上面的答案中写的更新。这是否更好?
-
否。MD5/ SHA2 /…的大小无关紧要,因为数据库至少需要存储盐。在2008年使用普通的哈希函数作为密码已经不是一个好习惯了:1979年在Unix中出现了慢盐哈希,此后发生的变化是如何制作慢盐哈希,PBKDF2在2000年被编纂。
-
@吉尔斯,很公平,我又尝试了一次。您的答案当然会更彻底,但是我想大多数读者都会关注我的答案,因为它是公认的答案。好吧,一起尝试引导开发人员使用最佳方法。
-
那就更好了,谢谢!只是一个小问题,"缺少强大的哈希函数"是不正确的。 SHA-256之类的功能与Bcrypt之类的功能之间的差异实际上是一个自然问题,而不仅仅是程度上的问题。密码散列是与加密散列不同的一种加密机制,而不是一种更强大的加密散列。不幸的是,他们没有一个公认的名称。
-
香港专业教育学院将其更改为"其他哈希函数"。
实际上,您可以使用CHAR(哈希的长度)来为MySQL定义数据类型,因为每种哈希算法始终会得出相同数量的字符。例如,SHA1始终返回40个字符的十六进制数字。
您可能会发现有关增盐的Wikipedia文章值得。这个想法是添加一组数据来随机化您的哈希值。如果有人未经授权访问密码哈希,这将保护您的密码免受字典攻击。
-
这确实是非常值得的(+1),但是它不能回答问题! (-1)
-
是的,但在这种情况下绝对相关(+1)
-
好点,但评论多于答案
作为固定长度的字符串(VARCHAR(n)或MySQL调用它)。
哈希始终具有固定长度,例如12个字符(取决于您使用的哈希算法)。因此,将20个字符的密码减少为12个字符的哈希,而4个字符的密码也将产生12个字符的哈希。
-
或者,但是MySQL称之为-MYSQL称之为CHAR。此类型用于固定长度值。因此,我认为CHAR比VARCHAR更好。
始终使用密码哈希算法:Argon2,scrypt,bcrypt或PBKDF2。
Argon2赢得了2015年密码哈希竞赛。 Scrypt,bcrypt和PBKDF2是较旧的算法,现在被认为较不受欢迎,但从根本上讲还是不错的,因此,如果您的平台尚不支持Argon2,则现在可以使用其他算法。
切勿将密码直接存储在数据库中。也不要对其进行加密:否则,如果您的站点遭到破坏,攻击者将获得解密密钥,因此可以获得所有密码。密码必须被散列。
密码哈希具有与哈希表哈希或密码哈希不同的属性。切勿在密码上使用普通的密码哈希,例如MD5,SHA-256或SHA-512。密码哈希算法使用唯一的盐(不用于其他任何用户或其他任何人的数据库)。盐是必不可少的,这样攻击者就不能仅预先计算常用密码的哈希值:使用盐,他们必须为每个帐户重新开始计算。密码哈希算法本质上很慢-尽可能地慢。慢速攻击对您的伤害要比对您的伤害大得多,因为攻击者必须尝试许多不同的密码。有关更多信息,请参见如何安全地对密码进行哈希处理。
密码哈希编码四个信息:
-
使用哪种算法的指标。这对于敏捷性是必需的:加密建议会随时间而变化。您需要能够过渡到新算法。
-
难度或硬度指示器。该值越高,则需要更多的计算来计算散列。在密码更改功能中,该值应该是常数或全局配置值,但是随着计算机变得越来越快,它应该随着时间的推移而增加,因此您需要记住每个帐户的值。一些算法只有一个数字值,其他算法那里有更多参数(例如分别调整CPU使用率和RAM使用率)。
-
盐。由于盐必须是全局唯一的,因此必须为每个帐户存储。应在每次更改密码后随机生成盐。
-
适当的散列,即散列算法中数学计算的输出。
许多库都包含一对函数,可方便地将此信息打包为单个字符串:一个带有算法指示符,硬度指示符和密码,生成随机盐并返回完整的哈希字符串的函数;另一个将密码和完整的哈希字符串作为输入,并返回一个布尔值,指示密码是否正确。没有通用标准,但是通用的编码是
1
| $algorithm$parameters$salt$output |
其中algorithm是数字或编码算法选择的短字母数字字符串,parameters是可打印的字符串,并且salt和output在Base64中编码而没有终止=。
16个字节足以容纳盐和输出。 (例如参见有关Argon2的建议。)以Base64编码,每个21个字符。其他两个部分取决于算法和参数,但是典型的是20–40个字符。总共大约有82个ASCII字符(CHAR(82),并且不需要Unicode),如果您认为以后很难扩展该字段,则应在其中添加安全边距。
如果您以二进制格式对哈希进行编码,则算法的哈希值可以减少到1个字节,硬度值可以减少到1-4个字节(如果您对某些参数进行了硬编码),salt和output可以分别减少到16个字节,共37个字节。说40个字节(BINARY(40))至少有几个备用字节。请注意,这些是8位字节,不是可打印字符,尤其是该字段可以包含空字节。
请注意,哈希的长度与密码的长度完全无关。
为了向前兼容,应使用TEXT(存储不限数量的字符)。 随着时间的推移,散列算法(需要)变得更强大,因此,随着时间的推移,此数据库字段将需要支持更多的字符。 另外,根据您的迁移策略,您可能需要在同一字段中存储新哈希和旧哈希,因此不建议将长度固定为一种哈希。
哈希是一个位序列(128位,160位,256位等,具体取决于算法)。如果MySQL允许(SQL Server数据类型为binary(n)或varbinary(n)),则列应为二进制类型,而不是文本/字符类型。您还应该给哈希加盐。盐可能是文本或二进制,并且您将需要相应的列。
-
正义在这里是完全正确的-MySQL将把它们存储为数字值,并使在此列上的搜索比进行字符串匹配更为有效,但是盐不应该在盐化数据旁边存储在数据库中,这消除了盐提供的安全性。
-
盐不是秘密。唯一的秘密是密码。只需确保每个新密码都得到一个新的答案。每次用户更改密码时,系统都会为该密码生成一个新的盐。盐应该是长且随机的,例如从加密安全的PRNG生成的16个字节。
-
@TonyMaro不确定在SQL级别上是否匹配密码字符串是一个好策略。换句话说,您不应在数据库中搜索密码,而应根据用户名检索用户并在代码而不是SQL中比较密码。
这实际上取决于您使用的哈希算法。如果我没记错的话,密码的长度与哈希的长度无关。在您使用的哈希算法上查找规格,运行一些测试,然后在其上方截断。
我一直在测试以找到加密字符串的最大字符串长度,并将其设置为VARCHAR类型的字符长度。根据您将要拥有的记录数,它确实可以帮助数据库扩大规模。
对于md5 vARCHAR(32)是合适的。对于那些使用AES的人,最好使用varbinary。