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

时间:2013-04-10 18:20:28

标签: php security hash passwords

我在社区中只有一个简单的问题。曾几何时,当我开始编程时,我使用md5进行散列密码,后来发现md5很容易被破解,我应该使用salt来保证它的安全。

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

md5("password"+"salt")

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

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

现在经过几次在这个领域,我发现sha1也不太安全而且破了我应该怎么做才能使用bcrypt()来保证密码安全。

所以从现在起我将使用

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

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

现在我要说的是假设bcrypt被黑客攻击并被发现被破坏,并且将来会出现更加安全的新加密功能。这样,从旧值创建密码总是很费时间。

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

这件事情会发生,或者你们有一些解决方案。 :)

3 个答案:

答案 0 :(得分:7)

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

  

PHP 5.5中新的Secure Password Hashing 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创建密码哈希。使用的算法和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 :(
}
     

请记住:salt和算法是哈希的一部分,因此您无需单独提供它们。

     

重新密码

     

随着时间的推移,您可能希望更改密码哈希算法或加载因子,或者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后,兼容性实现将自动禁用。

答案 1 :(得分:6)

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

(也就是说,用Bruce Schneier的话说,“攻击只会变得更好”,所以开始从MD5转移到更值得信赖的哈希函数,例如SHA-2SHA-3,当然不是即使你不需要来做这件事,也是个坏主意。)

问题是MD5 单独不适用于散列密码有两个原因,这两个原因实际上都是故意的设计特性(并由其他散列函数共享,如SHA-2和SHA-3):

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

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

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

  2. MD5 。通常这被认为是一件好事,但在密码散列中,事实证明,使进程过快主要只是帮助攻击者:合法用户并不真正关心是否散列密码需要10纳秒或10毫秒,而攻击者则是试图通过蛮力散列数百万个密码来猜测密码将会理解每个哈希值计算的每一分之一秒。

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

  3. 无论如何......所有这一切,实际上就是计算你的密码哈希值。例如。

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

    完全没问题,即使有点复杂。此外,对于那个哈希链,几乎所有的时间都被bcrypt 消耗,因为它是故意设计为慢速的三个哈希函数中唯一的一个。因此,在该哈希链之间应该没有明显的速度差异,而只是bcrypt本身 - 并且,无论如何,您希望密码散列尽可能慢。

答案 2 :(得分:2)

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

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

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

上面的例子是普遍的。而不是

  

...&amp;&amp; md5($ passwordFromForm)=== ......){

您可以使用您对存储密码进行的任何嵌套散列组合。最后它最终会以BCRYP哈希结尾。 下面详细介绍了加密和安全性,以及如何定义成本参数的正确值来散列用户密码。

缓慢的算法

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

  • PBKDF2 只是一个迭代的快速哈希(即仍然有效 并行)。 (这是一个可以与不同的方案一起使用的方案 基本算法。使用你正在使用的任何算法 系统。)
  • Bcrypt 需要一些(4KB)工作内存,因此效率较低 可在GPU上实现,每个处理器缓存少于4KB。
  • Scrypt 另外使用(可配置的)大量内存 处理时间,这使得并行化成本极高 GPU或定制硬件,而#34;正常&#34;电脑通常都够用 RAM可用。

良好的密码

您的密码长度不应少于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上每秒进行大约1300万个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倍。在大多数情况下,这通常是安全的方式,但对于管理员和超级管理员登录,我会考虑使它更耗时(大约1s),因为这些地方对真正的黑客更感兴趣。