2024年PHP日本大会

安全地进行密码哈希

本节解释了使用哈希函数来保护密码的原因,以及如何有效地做到这一点。

为什么用户提供的密码应该进行哈希处理?

密码哈希是设计任何接受用户密码的应用程序或服务时必须考虑的最基本的安全问题之一。如果不进行哈希处理,如果数据存储被破坏,则可以窃取任何存储的密码,然后立即用于破坏应用程序或服务,以及其他服务上的用户帐户(如果他们没有使用唯一的密码)。

通过在存储用户密码之前对其应用哈希算法,任何攻击者都不可能确定原始密码,同时仍然能够在将来将生成的哈希与原始密码进行比较。

但是,重要的是要注意,密码哈希只能保护它们免受数据存储中被破坏的影响,但不一定能保护它们免受注入应用程序或服务本身的恶意代码拦截。

为什么md5和sha1等常用哈希函数不适合用于密码?

MD5、SHA1和SHA256等哈希算法的设计非常快速高效。使用现代技术和计算机设备,对这些算法的输出进行“暴力破解”以确定原始输入已变得微不足道。

由于现代计算机可以非常快速地“反向”这些哈希算法,许多安全专业人员强烈建议不要将其用于密码哈希。

如果常用哈希函数不合适,应该如何对密码进行哈希处理?

在对密码进行哈希处理时,最重要的两个考虑因素是计算成本和盐值。哈希算法的计算成本越高,暴力破解其输出所需的时间就越长。

PHP 提供了一个原生的密码哈希 API,可以安全地处理哈希验证密码

对密码进行哈希处理时建议使用的算法是Blowfish,这也是密码哈希API的默认算法,因为它比MD5或SHA1的计算成本高得多,同时仍然具有可扩展性。

crypt() 函数也可用于密码哈希,但仅推荐用于与其他系统的互操作性。相反,强烈建议尽可能使用原生的密码哈希 API

什么是盐值?

加密盐值是在哈希过程中应用的数据,目的是消除将输出查找在预先计算的哈希及其输入对列表(称为彩虹表)中的可能性。

更简单地说,盐值是一些附加数据,可以使哈希更难以破解。网上有许多服务提供大量的预先计算的哈希列表,以及这些哈希的原始输入。使用盐值使得在这些列表中找到生成的哈希变得不可能或非常困难。

password_hash()如果没有提供盐值,将创建一个随机盐值,这通常是最简单和最安全的方法。

如何存储盐值?

使用password_hash()crypt()时,返回值包含作为生成的哈希一部分的盐值。此值应逐字存储在数据库中,因为它包含有关所使用哈希函数的信息,然后可以在验证密码时直接提供给password_verify()

警告

为了避免计时攻击,应始终使用password_verify(),而不是重新哈希并将结果与存储的哈希进行比较。

下图显示了crypt()password_hash()返回值的格式。可以看出,它们是自包含的,包含将来密码验证所需的所有算法和盐值信息。


        The components of the value returned by password_hash and crypt: in
        order, the chosen algorithm, the algorithm's options, the salt used,
        and the hashed password.

添加备注

用户贡献的备注 3条备注

alf dot henrik at ascdevel dot com
10年前
我觉得我应该评论一下这里作为回复发布的一些说法。

首先,速度确实是MD5尤其是SHA1的问题。我出于娱乐目的编写了自己的MD5暴力破解应用程序,仅使用我的CPU,我就可以轻松地将哈希与每秒约2亿个哈希进行比较。这种速度的主要原因是,对于大多数尝试,您可以绕过算法中64个步骤中的19个。对于更长的输入(> 16个字符),它将不适用,但我相信有一些方法可以解决这个问题。

如果您在线搜索,您会看到有人声称能够使用GPU每秒检查数十亿个哈希。我毫不怀疑,如今仅在一台计算机上就能达到每秒1000亿个哈希,而且只会越来越糟。这需要一个使用4个高端双GPU的巨型耗电机器,但这仍然是可能的。

以下解释了为什么每秒1000亿个哈希是个问题
假设大多数密码包含96个字符的选择。那么一个包含8个字符的密码将有96^8 = 7,213,895,789,840,000,000,000个组合。
以每秒1000亿的速度,则需要7,213,895,789,840,000,000,000 / 3600 = ~20小时才能计算出它实际的内容。请记住,您还需要添加1-7个字符的数字。如果您想针对单个用户,20小时不算多。

因此,本质上
较新的哈希算法之所以专门设计成不容易在GPU上实现,是有原因的。

哦,我看到有人提到了MD5和彩虹表。如果您阅读这里的数字,我希望您意识到彩虹表在MD5方面已经变得多么愚蠢和无用。除非MD5的输入非常大,否则您将无法与GPU竞争。到存储介质能够产生远远超过3TB/s的速度时,CPU和GPU的速度将达到更高的水平。

至于SHA1,我相信它比MD5慢约三分之一。我无法自己验证这一点,但根据MD5和SHA1的数字来看,情况似乎就是这样。速度问题在这里基本上也是一样的。

这里的寓意
请按照指示操作。永远不要再使用MD5和SHA1来哈希密码。我们都知道大多数人的密码不会那么长,这是一个主要缺点。添加长盐值肯定会有所帮助,但是除非您想添加数百字节的盐值,否则肯定会有快速的暴力破解应用程序可以反向工程您的密码或用户的密码。
swardx at gmail dot com
8年前
一篇很棒的文章。。

https://nakedsecurity.sophos.com/2013/11/20/serious-security-how-to-store-your-users-passwords-safely/

Serious Security: 如何安全地存储您的用户密码

总而言之,以下是我们对安全存储用户密码的最低建议

使用强大的随机数生成器创建长度为16字节或更长的盐值。
将盐值和密码输入PBKDF2算法。
使用HMAC-SHA-256作为PBKDF2内部的核心哈希。
执行20,000次或更多迭代。(2016年6月)
从PBKDF2获取32字节(256位)的输出作为最终密码哈希。
将迭代次数、盐值和最终哈希存储在您的密码数据库中。
定期增加迭代次数,以跟上更快的破解工具。

无论您做什么,都不要尝试自己编写密码存储算法。
tamas at microwizard dot com
3年前
在阅读评论时,我想到了一些旧的数学课,并开始思考。在数学算法中使用常数不会改变算法本身的复杂度。

加盐的原因是为了避免使用彩虹表(抱歉,伙计们,这是唯一的原因),因为它加快了(捷径)“实际”处理能力。
(((更长的存储哈希值和更长的密码会增加破解的复杂性,而不仅仅是添加盐。)))

PHP 加盐函数返回检查密码所需的所有信息,因此从更远的角度来看,此信息应被视为常数。它也是彩虹表的攻击目标(当然:对于大得多的彩虹表)。

解决方案是什么?
解决方案是将密码哈希和盐存储在不同的位置。
实现由你决定。任何两个不同的位置就足够了。

是的,这会给黑客制造麻烦。他/她需要理解你的系统。没有速度提升的密码破解方法对他不/她有效,除非重新实现你的整个系统。

这就是我的拙见。
To Top