time()是否是一个好的盐值?

59

我正在查看一些我自己没有编写的代码。代码尝试使用SHA512对密码进行哈希,并仅使用time()作为盐值。这个盐值是否太简单,或者这段代码是否安全?

感谢回答和评论。我将在此总结一下给新读者:

  • 每个用户的盐值应该不同,所以如果2个用户同时注册,他们的盐值将不是唯一的。这是一个问题,但不是很严重。
  • 但盐值不应与用户有任何关联,因此time()不是一个好的盐值。
  • "使用一个随机、均匀分布、高熵的盐值。"--这是一个专业术语,那么什么代码可以生成一个random, evenly distributed, high entropy 盐值呢?

那么,我用一个32个字符长的随机字符串替换time()如何?这个随机字符串可以通过循环32次来遍历一组字母表字符来生成。听起来不错吧?


11
我建议使用两部分的盐,一个是每个用户独特的盐,另一个则存储在配置文件中并保持机密。这样一来,攻击者需要同时访问文件系统和数据库,而不仅仅是数据库。 - CodesInChaos
在基于Unix的系统上,您可能可以访问/dev/random,否则,您可以...嗯,这是一个值得拥有自己线程的好问题。 - Jacco
你的最后一个问题:mt_rand不是密码学强度,但它会给你比time更多的“随机性”。PHP目前没有提供一种获取密码学强度随机数的方法(但正在研究中)。所以你应该要么坚持使用mt_rand,要么分叉/dev/(u)random - NikiC
9个回答

87

简短回答:

time()不是一个好的盐。

详细回答:

摘自我的Salt Generation and open source software的答案

什么是盐?

盐是添加到哈希算法输入中的固定长度随机字节集。

为什么盐(或种子)对哈希有用?

将随机盐添加到哈希中可以确保相同的密码会产生许多不同的哈希。盐通常与哈希函数的结果一起存储在数据库中。 添加盐到哈希的好处有:

  1. 盐大大增加了预计算攻击(包括彩虹表)的难度/成本。
  2. 盐确保相同的密码不会产生相同的哈希。这确保您无法确定两个用户是否具有相同的密码。更重要的是,您无法确定同一人是否在不同的系统上使用相同的密码。
  3. 盐增加了密码的复杂性,从而极大地降低了字典生日攻击的效果。(仅当盐与哈希分开存储时才成立。)
  4. 正确的盐处理极大地增加了预计算攻击的存储需求,直到它们变得不再实用。(大小写敏感的8个字符的字母数字密码,带有16位盐,哈希为128位值,需要接近200 艾字节而不进行彩虹表简化)。

盐无需保密。

盐不是一个秘密密钥,而是通过使哈希函数对每个实例具体化来“工作”。使用盐哈希,没有一个哈希函数,而是每个可能的盐值都有一个。这可以防止攻击者攻击小于攻击一个密码所需成本的N个哈希密码。这就是盐的作用。
“秘密盐”不是盐,它被称为“密钥”,这意味着您不再计算哈希,而是计算消息认证码(MAC)。计算MAC是棘手的业务(比仅将密钥和值组合到哈希函数中要复杂得多),它是完全不同的主题。

必须对每个实例都是随机的。这确保攻击者必须单独攻击每个盐哈希。
如果您依赖盐(或盐处理算法)保密,那么您就进入了安全性通过混淆的领域(不起作用)。最可能的是,您不会从盐保密中获得额外的安全性;您只会得到安全感。因此,它不是使您的系统更安全,而是让您远离现实。

那么,为什么盐必须是随机的?

技术上,盐应该是唯一的。盐的重点是每个哈希密码都要有所不同。这意味着全球范围内。由于没有分配唯一盐的中央组织,我们必须依赖下一个最好的选择,即使用具有不可预测性的随机选择器进行随机选择,最好在盐空间内,使碰撞变得不


对于随机盐的好来源
还可以阅读:什么是最安全的随机数生成种子?
在没有专用硬件随机生成器的情况下,获取随机数据的最佳方法是向操作系统请求(在Linux上,这称为 /dev/random/dev/urandom [两者都有优点和问题,请自行选择]; 在Windows上,调用 CryptGenRandom()

如果由于某种原因您无法访问上述随机源,则在PHP中,您可以使用以下函数:
来自 phpass v0.3 的源代码

<?php
/**
 * Generate pseudo random bits
 * @copyright: public domain
 * @link http://www.openwall.com/phpass/
 * @param int $length number of bits to generate
 * @return string A string with the hexadecimal number
 * @note don't try to improve this, you will likely just ruin it
 */
function random_bits($entropy) {
    $entropy /= 8;
    $state = uniqid();
    $str = '';
    for ($i = 0; $i < $entropy; $i += 16) {
        $state = md5(microtime().$state);
        $str .= md5($state, true);
    }
    $str = unpack('H*', substr($str, 0, $entropy));
    // for some weird reason, on some machines 32 bits binary data comes out as 65! hex characters!?
    // so, added the substr
    return substr(str_pad($str[1], $entropy*2, '0'), 0, $entropy*2);
}
?>

你应该将其引用为代码块。并考虑将其设为社区Wiki,因为仅引用其他答案并不是很重要的输出。 - Gumbo
2
值得注意的是,在 PHP 5.3 的(标准)OpenSSL 扩展中有一个新函数:openssl_random_pseudo_bytes(),它是一个快速、有效、经过充分测试的熵生成器。 - Charles
2
长答案并没有直接回答为什么时间戳是一个不好的选择。通读全文,似乎时间戳的唯一问题在于它是可预测的。攻击者可以创建一个针对特定时间创建的所有用户的表,但只有在同时创建了几个用户时才会出现问题。如果不是这种情况,那么时间戳看起来就像一个随机盐一样好用。那么真正的答案似乎是“没有必要,因为很容易获得一个随机值,即使是防止那种攻击也是如此”?*尽管PHP time()可能不是这样。 - jmathew
@jmathew 如果你的电脑在你创建账户之前就被攻击了,那么你面临的问题不仅仅是盐的问题 ;) - clockw0rk
2
@clockw0rk 不,时间永远不是一个好的盐,因为时间不是均匀分布的。因此,攻击者只需要攻击所有可能值的子集。 - Jacco
显示剩余6条评论

2

更新

虽然它不是一个非常好的盐,但可能足以击败除了最坚决和足智多谋的攻击者之外的所有人。一个好的盐需要满足以下要求:

  • 对于每个用户都是不同的
  • 足够长(至少包含8个字母数字字符),使得盐和(潜在弱密码)的连接过长,无法进行暴力破解攻击。

time()值并不足够长,因为它们只有10个数字字符,而没有字母。此外,有时候两个用户在同一秒钟内创建时可能会获得相同的值。但这只有在许多用户在同一秒钟内自动创建的情况下才会成为问题。

无论如何,比完美的盐更重要的是使用好的哈希函数,而SHA512是目前我们可用的最好的哈希函数之一。


3
另一个要求是盐不能与用户有任何关联。因此,像time()这样的函数并不理想,正如你之前所说的那样。 - NikiC
2
8个字符的长度是从哪里来的? - zerkms
1
@Jacco 为什么在密码哈希中使用的盐要具有不可预测性这么重要?攻击者知道哈希值,因为它与哈希一起存储在数据库中。 - CodesInChaos
1
@cyberkiwi:攻击者知道盐值,那又怎样?在这种情况下,除了暴力破解外,没有其他方法可行。 - zerkms
1
@Jacco:你不需要预测任何东西,你知道每个用户的盐值。除非在同一秒钟内自动创建了大量用户,否则聚类根本不是问题。当然,如果您的哈希函数存在弱点,也会出现问题。 - Michael Borgwardt
显示剩余10条评论

2
这篇文章可能会偏离你最初的问题,但我希望你会发现它有用;
安全性是通过提高障碍和防御深度来实现的。没有真正安全的哈希解决方案,只有难以破解的解决方案。就像在你的房子里安装防盗警报器和窗户锁一样,使你的网站比别人的更不容易被攻破。
对于加密算法的盐只是安全问题的一个小部分。单个盐意味着在尝试破解多个用户的密码时少了一件事情要考虑。低熵盐(如服务器的时间)使得破解变得更难,而高熵盐使得破解更加困难。使用哪种盐,以及是否需要主要关注这一点,取决于你正在保护的数据的敏感性,以及你采取的其他安全措施。一个只为选定城市提供个性化天气预报的网站显然比一个具有你家地址、母亲的婚姓、出生日期和其他可用于身份识别目的的信息的网站具有更少的敏感数据。
所以这就是问题所在;即使是高熵盐,如果很容易获得,仍然是一个糟糕的盐。
在现实世界中,将盐存储在数据库中(随机或非随机)可能比使用常量盐并将其埋藏在无法通过网络浏览器访问的文件中不太安全。虽然唯一和高熵的盐更难猜测,但如果你允许从任何服务器上的MySql进行root登录,并将密码设置为“password”,那么这并不重要!对比一下破解数据库和获取有效登录到你的服务器有多容易——这可能更难以隐蔽地做到,因为你可以根据你的设置放置fail2ban和大量其他攻击向量监视器。
你可以通过在数据库中存储包含用户特定盐的文件位置而不是盐本身来结合这两种方法。是否需要破解文件系统和数据库取决于你试图保护的数据的敏感性是否需要这种开销。
安全专家的另一个替代建议是将用户名存储在与密码不同的数据库(最好是不同的技术)中,并使用UUID之间的引用。例如同时使用MySQL和SQLite。这意味着必须破解两个数据库(也是为什么,为了举例说明,你不应该在同一个数据库中存储用户详细信息和信用卡号码,因为一个没有另一个就没有用)。
请注意,像SHA-512和Blowfish这样的算法可以作为它们的哈希的一部分返回盐。对于这些算法要小心,因为如果你存储完整的哈希值,你就泄露了算法,这意味着黑客需要破解的东西就少了两个(盐也暴露了算法)。
确保强制使用强密码和用户名,这样字典攻击将会失败;我知道有针对MD5的所有6个字母数字组合的用户名/密码条目的字典,我怀疑还有更多的字典可用于各种算法。随着低成本云计算和CPGPU计算的爆炸式增长,可用字典的大小和复杂性将会爆炸。

最安全的方法是从不以编程方式生成盐,而是要求用户在 SSL 链接上输入它们的用户名和密码(因此无法被窃听),但从不存储它。这是信用卡公司采取的方法;即您在每次在线购买时都必须输入的 3 位 CSV 安全密钥,因为它不应存储在任何数据库中。如果您真的想生成盐,请将其单独发送给他们(例如通过 SMS 消息或电子邮件),并仍然让他们每次手动输入。采用这种方法虽然更安全,但需要权衡复杂性与用户是否会因为您使网站过于复杂而停止使用它。

所有以上内容仍然依赖于您是否还有防止会话劫持、跨站点脚本等保护措施。世界上最强大的密码算法是无关紧要的,如果我只需要计算一个已登录用户的有效 PHPSESSID 并劫持它!

我不是安全专家,但已经尽力阅读了很多相关内容。有这么多关于此主题的书籍表明您的问题的答案非常庞大。

以下是一些非常棒的书籍,您可能会喜欢尝试,我发现它们非常有价值:

《Web 应用程序漏洞检测、利用、预防》- ISBN-13: 978-1-59749-209-6

《使用 Apache 防止 Web 攻击》- ISBN-13: 978-0-321-32128-2


3
虽然总体上你的答案是正确的,但其中一些陈述是明显错误的:“如果高熵盐很容易获得,它仍然是不好的盐。”例如,这个说法就是不真实的。 - Jacco

1

是的。
似乎将Unix时间戳存储在用户数据库中作为“成员自”字段会产生不错的盐。

然而,盐的问题是最微不足道的。 你需要注意的更重要的事情有很多:

  1. 最有可能成为您网站最薄弱环节的不是密码、盐或哈希算法。一些无聊的文件注入、XSS或CSRF肯定是。所以,不要过于重视它。
    在典型的Web应用程序中,谈论真正的32个字符长的随机字符串就像在木制谷仓中谈论32英寸装甲门。

  2. 说到密码,最重要的事情是密码复杂性。没有强密码,即使是超级聪明、难以置信的哈希算法也无济于事。要求用户使用复杂密码很痛苦,但如果没有它,其他所有东西都变得一团糟。
    因此,您的第一个关注点应该是密码复杂性。包括数字和标点符号在内的12-16个不同大小写字母的字符是必需的。

  3. 至于盐,我认为使用时间没有任何好处,因为您必须将其与其他用户数据一起存储。最好使用电子邮件——它已经足够随机了,而且您已经拥有它了。不要忘记重新哈希密码,如果用户更改了他们的电子邮件。 看起来Unix时间戳会是一个不错的盐,不需要使用电子邮件或其他任何东西。

更新
我发现很多人仍然无法理解重点。
就像评论中的那个人所说:

许多用户使用弱密码(我们应该教育他们,或者至少继续尝试),但这并不是借口;他们仍然应该得到良好的安全保护。

毫无疑问,他们应该得到良好的安全保护。但是如果密码太弱,任务就会变得不可能完成。

如果您的密码太弱,那么即使加盐也无法保护它。

虽然盐对于花费10千字的文章来说并不那么重要。


13
许多网站没有保护任何有价值的东西。但由于用户倾向于经常重复使用密码,因此这些密码本身成为这些网站通常可用的最有价值的数据集。因此,在我的看法中,无论情况如何,正确保护密码都很重要。许多用户使用弱密码(我们应该教育他们,或者至少继续努力),但这并不是借口;他们仍然应该享有良好的安全性。此外,使用随机盐易于实现;建议其他方法只是建议别人停止关心。 - Jacco
我认为这个答案有很多错误。a) 密码复杂度并不像密码长度那样重要。在密码中使用标点符号比好处更多。b) 对于大多数琐碎的网站,密码是该网站拥有的最重要的资产。c) 即使是弱密码,也有很多事情可以做来增强其安全性。但是,我确实同意没有盐本身无法保护弱密码。您还需要至少一个适当的哈希策略。 - eis
在密码中使用标点符号有什么危害吗? @eis - René Roth
1
@RenéRoth,长期来看,你很可能不会正确地记住它们,因此你把它们写下来的可能性增加了。 - eis

1

不,time() 不是好的盐值

在身份验证方面最好不要重复造轮子,但回答你的问题,。 time() 的问题:

  • 它是可预测的,并且与潜在可发现的事物相关联。这些问题使得更容易交叉匹配不同的哈希结果。
  • 可能的取值并不多。由于高位比特不改变,它比一开始看起来还要窄的盐。
  • 使用它会重复以前的错误。如果该应用程序是第一个将 time() 用作盐的应用程序,至少需要进行新的攻击。

0
不!永远不要使用当前时间作为盐值。你可以使用像Java中的“SecureRandom”这样的东西来生成一个安全的随机盐值。始终使用不可预测的随机数作为盐值。
使用时间作为盐值只能帮助你在一定程度上消除冲突(因为两个用户可以在同一时间提供相同的密码),但仍然会使密码可恢复。

0

盐用于通过打破密码和预计算哈希之间的匹配来防止彩虹攻击。因此,盐的主要任务是对每个用户/密码记录都不同。盐的随机化质量并不重要,只要盐对不同的用户不同即可。


0

一个成员加入论坛/网站的日期通常是公开可访问的,这与time()相同,因此使您的盐无效。


-2

用户名应该足够,并且可能需要注册时间戳,但是您应该将其存储在数据库中的某个位置。无论如何,您用于加盐密码哈希的每个值都应该以某种方式存储,以便您可以重新计算哈希。

使用用户名+时间戳进行加盐是否足够安全?应该是的。破解SHA512哈希通常使用Rainbow表。用户名+时间戳应该是足够唯一的盐,因此在网络上没有预先计算出使用这种方式加盐的密码哈希的Rainbow表。


我误读了他想要对密码进行哈希处理。就像我说的,用户名和注册时间戳应该足够了。 - kalkin

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