安全密码哈希

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

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

密码哈希是设计任何接受用户密码的应用程序时必须考虑的最基本的安全问题之一。如果没有哈希,如果数据库受到攻击,存储在应用程序数据库中的任何密码都可能被盗,然后立即用于攻击您的应用程序,以及用户在其他服务上的帐户(如果他们没有使用唯一的密码)。

通过在将用户的密码存储到数据库之前对它们应用哈希算法,您可以使任何攻击者无法确定原始密码,同时仍然能够在将来将生成的哈希与原始密码进行比较。

但是,重要的是要注意,密码哈希只保护它们免受数据存储中受到攻击,但并不一定保护它们免受注入到应用程序本身的恶意代码的拦截。

为什么像 md5()sha1() 这样的常见哈希函数不适合密码?

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

由于现代计算机可以非常快地“逆转”这些哈希算法,因此许多安全专业人士强烈建议不要将它们用于密码哈希。

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

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

PHP 提供了 本机密码哈希 API,它可以安全地处理 哈希验证密码,以安全的方式进行。

另一个选择是 crypt() 函数,它支持多种哈希算法。使用此函数时,您可以保证您选择的算法可用,因为 PHP 包含每个支持算法的本机实现,以防您的系统不支持一个或多个算法。

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

请注意,如果您使用 crypt() 来验证密码,您需要注意使用恒定时间字符串比较来防止计时攻击。PHP 的 == 和 === 运算符 以及 strcmp() 都没有执行恒定时间字符串比较。由于 password_verify() 会为您执行此操作,因此强烈建议您尽可能使用 本机密码哈希 API

什么是盐?

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

更简单地说,盐是额外的少量数据,可以使您的哈希值难以破解。网上有很多服务提供广泛的预先计算哈希列表,以及这些哈希的原始输入。使用盐可以使在这些列表中找到生成的哈希值变得不可能或不可能。

password_hash() 会在没有提供盐的情况下创建一个随机盐,这通常是最简单、最安全的方法。

我应该如何存储我的盐?

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

下图显示了 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,21389578984e+15 个组合。
如果每秒 1000 亿次,那么需要 7,21389578984e+15 / 3600 = 大约 20 个小时才能弄清楚它实际表示什么。请记住,您还需要添加 1 到 7 个字符的数字。如果您想针对单个用户,20 个小时不算太长。

所以本质上
有充分的理由说明为什么较新的哈希算法是专门设计为不容易在 GPU 上实现的。

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

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

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

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

严肃的安全:如何安全地存储用户的密码

总之,以下是我们对安全存储用户密码的最低推荐

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

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

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

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

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

是的,这会给黑客造成麻烦。他/她需要了解你的系统。如果没有重新实现你的整个系统,任何加速密码破解的方法对他/她都没有用。

这是我的两分钱。
To Top