哈希密码,从破碎的方法到现在最安全

Posted

技术标签:

【中文标题】哈希密码,从破碎的方法到现在最安全【英文标题】:Hashing Password, from broken methods to most secure now 【发布时间】:2013-04-02 17:51:04 【问题描述】:

我对社区有一个简单的问题。曾几何时,当我开始编程时,我使用 md5 作为哈希密码,后来发现 md5 很容易被破解,我应该使用 salt 来确保它的安全。

我对 md5 没有信心,想使用 sha1,sha256,sha512 加密。但问题是现在我有加密形式的密码,即

md5("password"+"salt")

那时我不知道用户的密码。所以我做了什么

sha1(md5("password"+"salt"))

现在在这个领域经过几次后,我发现 sha1 也不太安全并且被破坏了我应该做的是使用 bcrypt() 来确保密码安全。

所以从现在开始我将使用

 crypt(sha1(md5("password"+"salt")))

密码现在非常安全,但主要问题仍然是它用于创建哈希值的时间总是大于使用 bcrypt("password")

现在我想说的是,假设如果 bcrypt 被黑客入侵并被发现被破坏,并且将来会出现更安全的新加密功能并且。比这种方式从旧值创建密码总是很耗时。

这可能是什么解决方案。据我所知,邮寄用户更改密码并不总是 100% 成功。 另一件事是在数据库中添加一个新字段来存储新的哈希值,如果所有字段都已填充,则从 db 中删除 md5 值。但是通过这种方式,以前的哈希值仍然可见。

那么这件事会继续吗,或者你们有一些解决方案。 :)

【问题讨论】:

***.com/questions/4795385/… ***.com/questions/401656/… @ZulkhaeryBasrul:看不出这些有什么关系 见 OWASP 的Password Storage Cheat Sheet。请务必关注约翰史蒂文对威胁和对策的分析的链接。另请参阅 Openwall 的 php password hashing framework (PHPass)。它的便携性和强化了针对用户密码的一些常见攻击。编写框架 (SolarDesigner) 的人与编写 John The Ripper 并在Password Hashing Competition 担任评委的人是同一个人。所以他对密码攻击略知一二。 使用 password_needs_rehash() 怎么样? youtube.com/watch?v=ZH3V1kObpec 【参考方案1】:

PHP 5.5 introduces the Password API 解决了这个问题:

PHP 5.5 中新的安全密码哈希 API

新的简单易用的密码哈希 API 的 RFC 刚刚被 PHP 5.5 接受。由于 RFC 本身是相当技术性的,并且大多数示例代码都是您不应该使用的,所以我想快速概述一下新 API:

为什么我们需要新的 API?

每个人都知道您应该使用 bcrypt 对他们的密码进行哈希处理,但仍有数量惊人的开发人员使用不安全的 md5 或 sha1 哈希(看看最近的密码泄露)。造成这种情况的原因之一是 crypt() API 非常难以使用并且很容易出现编程错误。

通过添加一个新的、非常简单易用的 API,我们希望将更多的开发人员推向 bcrypt。

如何对密码进行哈希处理

创建密码哈希再简单不过了:

  $hash = password_hash($password, PASSWORD_DEFAULT);

这将使用默认算法(当前为 bcrypt)、默认加载因子(当前为 10)和自动生成的 salt 创建密码哈希。使用的算法和盐也将成为结果哈希的一部分,因此您根本不需要担心它们;)

如果您不想坚持使用默认值(将来可能会更改),您还可以自己提供算法和负载因子:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);

验证密码

验证密码同样简单:

<?php
// $password from user, $hash from database
if (password_verify($password, $hash)) 
    // password valid!
 else 
    // wrong password :(

记住:盐和算法是哈希的一部分,所以你不需要单独提供它们。

重新散列密码

随着时间的推移,您可能想要更改密码哈希算法或负载因子,或者 PHP 可能会更改默认设置以更安全。在这种情况下,应使用新选项创建新帐户,并在登录时重新散列现有密码(您只能在登录时执行此操作,因为您需要原始密码进行重新散列)。

这样做也很简单:

<?php
function password_verify_with_rehash($password, $hash) 
    if (!password_verify($password, $hash)) 
        return false;
    

    if (password_needs_rehash($hash, PASSWORD_DEFAULT)) 
        $hash = password_hash($password, PASSWORD_DEFAULT);

        // update hash in database
    

    return true;

上面的 sn-p 将使您的哈希值与 PHP 默认值保持一致。但是您也可以再次指定自定义选项,例如password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' =&gt; 12']).

旧 PHP 版本的兼容层

新的 API 只会在 PHP 5.5 中引入,但您现在已经可以使用相同 API 的 PHP 实现了!升级到 5.5 后,兼容性实现会自动禁用。

【讨论】:

我认为只有你答案的倒数第二部分实际上与问题相关...... @Frank:“哈希密码,从破碎的方法到现在最安全”?单击该问题时,我希望得到问题提出的问题的答案,最好链接到提供的要点等其他信息 即使我能想到(没有 PHP 也没有程序员专家),那是你拥有的最好的 PHP 开发人员吗?感谢约翰康德的精彩回答(;【参考方案2】:

实际上,MD5,如果使用得当,仍然被认为对于密码散列是完全安全的。虽然确实存在针对 MD5 的实用 collision attacks,这使得它对于数字签名之类的东西不安全,但破解密码哈希需要 preimage attack,而目前所有已知的针对 MD5 的此类攻击都纯粹是理论上的。

(也就是说,用 Bruce Schneier 的话说,“攻击只会变得更好”,因此开始从 MD5 转向更可靠的哈希函数,例如 SHA-2 或 SHA-3,当然不是一个坏主意,即使你还没有需要这样做。)

问题是 MD5 单独不适合散列密码,原因有两个,这两个实际上都是经过深思熟虑的设计功能(并且由其他散列函数如 SHA-2 和 SHA-3 共享):

    MD5 是确定性的,这意味着用 MD5 散列相同的输入总是产生相同的输出。

    这是密码散列的问题,因为有人可以(实际上是some people have)只编译一个包含常见(但不那么常见)密码的 MD5 散列的巨大数据库,允许任何知道普通 MD5 散列的人在这些数据库中找到的任何密码,只需查找并找到原始密码。

    解决方案很简单,您已经知道了:在散列之前将密码与随机的salt 组合在一起,并将盐作为最终散列的一部分,以便以后可以用来验证密码。有足够多的可能盐(例如,至少几十亿)可供随机选择,编译散列数据库变得不可能,因为任何单个密码都可以散列到数十亿个不同的值。方便的是,这也意味着,即使您碰巧有两个用户使用相同的密码,也无法仅通过查看哈希值来判断。

    MD5 快速。通常这被认为是一件好事,但在密码散列中,事实证明,使过程过快只会帮助攻击者:合法用户并不真正关心散列他们的密码需要 10 纳秒或 10 毫秒,而攻击者尝试通过蛮力对数百万个密码进行散列来猜测密码将欣赏每次散列计算所节省的每一纳秒。

    同样,解决方案很简单且众所周知:只需将密码重新散列几千(或更多)次以减慢计算速度。甚至是标准化的方法,例如PBKDF2 方法。或者,也可以使用专门构建的密码哈希函数,例如 bcrypt 或 scrypt,它们通常带有内置的加盐和可调整的迭代计数。

无论如何...所有这一切的重点在于,事实上,将您的密码哈希计算为例如

hash = salt + bcrypt( sha1( md5( password + salt ) ) )

非常好,即使有些复杂。此外,对于该哈希链,几乎所有时间都被 bcrypt 消耗,因为它是三个故意设计为慢的哈希函数中唯一的一个。因此,该哈希链与 bcrypt 本身之间不应该有任何明显的速度差异——而且,无论如何,您希望密码哈希尽可能慢。

【讨论】:

【参考方案3】:

所以您必须更新数据库中所有用户的密码?如果您 twit 登录脚本,您将无需执行任何操作。看看这个:

将 Md5 密码哈希更新为 BCRYPT 哈希::

$passwordFromDatabase = "0d107d09f5bbe40cade3de5c71e9e9b7"; // md5  hash of "letmein"
$passwordFromForm = $_POST['password']; // $_POST['password'] == "letmein"

if(password_needs_rehash($passwordFromDatabase, PASSWORD_BCRYPT, ["cost" => 12]) && md5($passwordFromForm) === $passwordFromDatabase)
    // generate new password
    $newPasswordHash = password_hash($passwordFromForm, PASSWORD_BCRYPT, ["cost" => 12]);
    // update hash from database - replace old hash $passwordFromDatabase with new hash $newPasswordHash
    // after update login user
    if(password_veryfi($passwordFromForm, $newPasswordHash))
        // user has logged in successfully and hash was updated
        // redirect to user area
    else
        // ups something went wrong Exception
    
else
    if($password_veryfi($passwordFromForm, $passwordFromDatabase))
        // user password hash from database is already BCRYPTed no need to rehash
        // user has logged in successfully
        // redirect to user area
    else
        // wrong password
        // no access granted - stay where you are
    

上面的例子是通用的。而不是

... && md5($passwordFromForm) === ...)

您可以使用您对存储密码所做的任何嵌套散列组合。无论如何,它最终都会成为一个 BCRYP 哈希。 下面详细了解加密和安全性,以及如何定义正确的成本参数值来散列用户密码。

慢速算法

目前的标准是使用慢散列算法。 PBKDF2、bcrypt 或 scrypt 都将密码和盐作为输入和可配置的工作因子 - 将此工作因子设置为用户在登录时使用服务器硬件时接受的最高值。 Reference

PBKDF2 只是一个迭代的快速哈希(即仍然有效 可并行化)。 (这是一个可以与不同的方案一起使用的方案 基础算法。使用你正在使用的任何算法 系统。) Bcrypt 需要一些 (4KB) 工作内存,因此效率较低 可在每处理器缓存少于 4KB 的 GPU 上实现。 Scrypt 还使用(可配置的)大量内存 处理时间,这使得并行化成本非常高 GPU 或自定义硬件,而“普通”计算机通常有足够的 可用内存。

好密码

您的密码长度不应少于 8 个字符,并且至少应使用一个:

大写 一个数字和 一个特殊字符

设置密码8个字符长,大小写和特殊字符能够创建:6 634 204 312 890 625个组合。但是,如果您的密码是一周,那么只说 6 个字符的小写字母,您只会得到: 308,915,776 组合。为了确保您的帐户安全,建议使用超过 12 个字符的密码长度。 CLICK for Password Combination Count Simulator

破解速度(每年都在变化,为破解者提供更多的 GPU 处理能力或更强大的云计算)

在设计密码时,请考虑未来处理能力的提高以及黑客将获得的工具。

该程序 IGHASHGPU v0.90 声称能够在单个 ATI HD5870 GPU 上每秒执行大约 13 亿次 SHA-1 哈希(即超过 2^30)。

假设密码为 40 位熵,这需要 2^10 秒,也就是大约 17 分钟。

一个 44 位熵的密码(就像著名的 XKCD 漫画中的密码)需要 68 分钟(最坏情况,平均情况是这个时间的一半)。

在多个 GPU 上并行运行会按比例加快速度。

因此,使用快速哈希进行暴力破解是一种真正的危险,而不是理论上的危险。而且许多密码的熵要低得多,这使得暴力破解更快。 Reference

解决方案

您可以通过控制成本来自定义算法的速度。成本越高,对密码进行编码和编码所需的时间就越长。最好的目标可能是大约 500 毫升,这使得攻击者很难暴力破解我们的密码。

具有 12 个字符和更长的密码 + 更慢的算法将保证在密码被破解之前暴力破解相当数量的组合。一旦我们有了合适的密码,我们就可以通过将密码验证过程减慢到会使其变得非常困难和耗费时间/资源的速度来让想要进入我们系统的人的生活变得更加困难。将成本设置为一个数字,该数字将影响验证用户密码所需的大约 0.5 秒时间。

自定义成本值

由于每个服务器的脚本执行会根据处理能力和流量而有所不同,您怎么知道应该将成本设置多高?

那么您应该衡量验证过程所需的时间,并根据您的需要定制成本。

<?php
/**
 * This code will benchmark your server to determine how high of a cost you can
 * afford. You want to set the highest cost that you can without slowing down
 * you server too much. 8-10 is a good baseline, and more is good if your servers
 * are fast enough. The code below aims for ≤ 50 milliseconds stretching time,
 * which is a good baseline for systems handling interactive logins.
 */
$timeTarget = 0.50; // 500 milliseconds 

$cost = 8; //start to measure from cost = 8
do 
    $cost++;
    $start = microtime(true);
    password_hash("Ajd_hsk-K87&", PASSWORD_BCRYPT, ["cost" => $cost]);
    $end = microtime(true);
 while (($end - $start) < $timeTarget);

echo "Appropriate Cost Found: " . $cost . "\n";
?>

Reference

上述函数将返回 X 个我们需要用来满足安全要求的成本。

Appropriate Cost Found: 13 //this result will be different based on your server machine.

此脚本取自 php 手册并经过增强,处理时间延长了 10 倍。在大多数情况下,这通常是安全的方式,但对于管理员和超级管理员登录,我会考虑让它更加耗时(大约 1 秒),因为这些地方对真正的黑客更感兴趣。

【讨论】:

与here 相同的错误。根据站点 0.5s 的单次登录可以使您的服务器变平,更实际的目标是大约 0.05s。 也许对于繁忙的流量来说你是对的,对于繁忙的应用程序来说可能太多了。它将需要一些可用性测试来查看用户不讨厌的内容。您可以选择用于您的应用程序的内容。我肯定会为管理面板推荐这个。但同样,每个人都应该根据特定的用例自行决定。 对于您自己的服务器当然取决于您,您能负担多少时间,我更多地考虑共享主机和繁忙的网站。

以上是关于哈希密码,从破碎的方法到现在最安全的主要内容,如果未能解决你的问题,请参考以下文章

C# 存储数字的安全方式?

生成唯一哈希的最安全方法?

是O.K向客户端发送密码哈希?

哈希密码的最佳实践

哈希机制真的安全吗?

安全技术2:用户密码及哈希算法