6

我对社区只有一个简单的问题。曾几何时,当我开始编程时,我使用 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 值。但是以这种方式,以前的散列值仍然可见。

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

4

3 回答 3

8

PHP 5.5 引入了密码 API来解决这个问题:

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

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

为什么我们需要一个新的 API?

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

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

如何散列密码

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

  $hash = password_hash($password, PASSWORD_DEFAULT);

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

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

$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;
}

上面的代码片段将使您的哈希值与 PHP 默认值保持同步。但是您也可以再次指定自定义选项,例如password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 12']).

旧 PHP 版本的兼容层

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

于 2013-04-10T18:23:58.530 回答
6

实际上,如果使用得当, MD5对于密码散列仍然被认为是完全安全的。虽然确实存在针对 MD5 的实际冲突攻击,这使得它对于数字签名等内容不安全,但破解密码哈希将需要原像攻击,并且所有目前已知的针对 MD5 的此类攻击都是纯理论的。

(也就是说,用 Bruce Schneier 的话说,“攻击只会变得更好”,因此开始从 MD5 转向更值得信赖的哈希函数,例如SHA-2SHA-3,即使你不这样做也肯定不是一个坏主意'不需要_。)

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

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

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

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

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

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

无论如何......所有这一切的重点是,事实上,计算你的密码散列例如

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

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

于 2013-04-10T20:00:04.760 回答
2

所以你必须更新数据库中用户的所有密码?如果您 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 都将密码和盐作为输入和可配置的工作因子 - 将此工作因子设置为用户在使用服务器硬件登录时接受的最高值。参考

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

好密码

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

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

设置密码 8 字符长,大小写和特殊字符能够创建:6 634 204 312 890 625 组合。但是,如果您的密码是星期,让我们说 6 个字符的小写字母,您只会得到:308,915,776 组合。为了确保您的帐户安全,建议使用超过 12 个字符的密码长度。单击密码组合计数模拟器

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

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

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

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

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

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

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

解决方案

您可以通过控制成本来自定义算法的速度。成本越高,对密码进行编码和编码所需的时间就越长。最好的目标可能是大约 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";
?>

参考

上面的函数将返回 X 数量的成本,我们需要使用这些成本来满足我们的安全要求。

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

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

于 2016-02-07T09:47:48.223 回答