这是PHP中的一个很好的哈希密码函数吗?如果没有,为什么不呢?

时间:2013-04-16 16:19:39

标签: php security passwords cryptography password-hash

我想知道这个功能(部分取自~2岁的phpBB版本)是否足够好。

如果没有,为什么?
您将如何更改它(使现有用户无缝转换)?

hash_pwd()的结果将保存在数据库中。

function hash_pwd($password)
{
    $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

    $random_state = $this->unique_id();
    $random = '';
    $count = 6;

    if (($fh = @fopen('/dev/urandom', 'rb')))
    {
        $random = fread($fh, $count);
        fclose($fh);
    }

    if (strlen($random) < $count)
    {
        $random = '';

        for ($i = 0; $i < $count; $i += 16)
        {
            $random_state = md5($this->unique_id() . $random_state);
            $random .= pack('H*', md5($random_state));
        }
        $random = substr($random, 0, $count);
    }

    $hash = $this->_hash_crypt_private($password, $this->_hash_gensalt_private($random, $itoa64), $itoa64);

    if (strlen($hash) == 34)
    {
        return $hash;
    }

    return false;
}


function unique_id()
{
    $val = microtime();
    $val = md5($val);

    return substr($val, 4, 16);
}

function _hash_crypt_private($password, $setting, &$itoa64)
{
    $output = '*';

    // Check for correct hash
    if (substr($setting, 0, 3) != '$H$')
    {
        return $output;
    }

    $count_log2 = strpos($itoa64, $setting[3]);

    if ($count_log2 < 7 || $count_log2 > 30)
    {
        return $output;
    }

    $count = 1 << $count_log2;
    $salt = substr($setting, 4, 8);

    if (strlen($salt) != 8)
    {
        return $output;
    }

    /**
    * We're kind of forced to use MD5 here since it's the only
    * cryptographic primitive available in all versions of PHP
    * currently in use.  To implement our own low-level crypto
    * in PHP would result in much worse performance and
    * consequently in lower iteration counts and hashes that are
    * quicker to crack (by non-PHP code).
    */
    if (PHP_VERSION >= 5)
    {
        $hash = md5($salt . $password, true);
        do
        {
            $hash = md5($hash . $password, true);
        }
        while (--$count);
    }
    else
    {
        $hash = pack('H*', md5($salt . $password));
        do
        {
            $hash = pack('H*', md5($hash . $password));
        }
        while (--$count);
    }

    $output = substr($setting, 0, 12);
    $output .= $this->_hash_encode64($hash, 16, $itoa64);

    return $output;
}

function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
{
    if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
    {
        $iteration_count_log2 = 8;
    }

    $output = '$H$';
    $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
    $output .= $this->_hash_encode64($input, 6, $itoa64);

    return $output;
}

function _hash_encode64($input, $count, &$itoa64)
{
    $output = '';
    $i = 0;

    do
    {
        $value = ord($input[$i++]);
        $output .= $itoa64[$value & 0x3f];

        if ($i < $count)
        {
            $value |= ord($input[$i]) << 8;
        }

        $output .= $itoa64[($value >> 6) & 0x3f];

        if ($i++ >= $count)
        {
            break;
        }

        if ($i < $count)
        {
            $value |= ord($input[$i]) << 16;
        }

        $output .= $itoa64[($value >> 12) & 0x3f];

        if ($i++ >= $count)
        {
            break;
        }

        $output .= $itoa64[($value >> 18) & 0x3f];
    }
    while ($i < $count);

    return $output;
}

4 个答案:

答案 0 :(得分:52)

您提供的代码是PHPASS的端口,特别是“便携式”算法。请注意portable的资格。如果您将phpass作为第二个构造函数参数传递,则这仅适用于true库。从这里开始,在这个答案中,phpass ONLY 引用到便携式算法,而不是库本身。如果您没有明确指定portable ...

,默认情况下,库将执行bcrypt

PHPBB团队并没有自己开发这个(非常好的东西),而是直接从phpass移植它(可论证)。

我们应该在这里提出几个问题:

不好吗?

简短的回答是不,这不错。它提供了非常好的安全性。如果你现在有这方面的代码,我不会急于下车。它适用于大多数用途。但话虽如此,如果你开始一个我不会选择它的新项目,还有更好的选择。

有哪些缺点?

  • 相对pbkdf2phpass算法使用hash(),其中pbkdf2()使用hash_hmac()。现在,HMAC在内部为每个调用运行2个哈希,但是PHP实现只需要执行hash()的单个调用的大约1.6倍(不是C很好吗?)。因此,我们在hash_hmac中获得2个哈希,占hash()执行2个哈希值的时间占62%。

    这是什么意思?好吧,对于给定的运行时pbkdf2将比phpass算法运行大约37.5%更多哈希值。在给定时间内更多哈希==好,因为它会导致执行更多计算。

    因此,pbkdf2使用相同的原语(在这种情况下为37.5%)时比phpass大约md5。但是pbkdf2也可以采用更强的原语。因此,我们可以将pbkdf2sha512一起使用,以获得比phpass算法更显着的优势(主要是因为sha512更难算法计算比md5)。

    这意味着pbkdf2不仅能够在相同的时间内生成更多的计算,而且能够生成更难的计算。

    据说,差异不是过分显着。这是非常可衡量的,pbkdf2肯定比phpass“更强”。

  • 相对于bcrypt:这比较难以进行比较。但让我们来看看它的表面。 phpass使用md5和PHP中的循环。 pbkdf2使用任何原语(在C中)和PHP中的循环。 bcrypt在C中使用自定义算法(意味着它与任何可用哈希的算法不同)。因此,对于蝙蝠来说,bcrypt具有明显的优势,因为该算法全部在C中。这允许每单位时间更多的“计算”。从而使其成为一种更有效的慢速算法(在给定的运行时间内进行更多的计算)。

    但与计算的次数一样重要的是计算的质量。这可能是一篇完整的研究论文,但简而言之,它归结为bcrypt在内部使用的计算比普通的哈希函数更难执行。

    bcrypt更强大的本质的一个例子是bcrypt使用比普通哈希函数大得多的内部状态。 SHA512使用512位内部状态来计算1024位的块。 bcrypt使用大约32kb的内部状态来计算单个576位的块。 bcrypt的内部状态比SHA512(以及md5phpass)大得多的事实部分地解释了bcrypt的强大性质。< / p>

应该避免

对于新项目,绝对。这不是坏事。事实并非如此。这是有明显更强的算法(按数量级)。那么为什么不使用它们呢?

有关bcrypt如何更强大的进一步证明,请查看Slides from Password13 (PDF),它启动了25 GPU集群以破解密码哈希值。以下是相关结果:

  • md5($password)
    • 每秒180亿次猜测
    • 9.4小时 - 所有可能的8个字符密码
  • sha1($password)
    • 每秒61亿次猜测
    • 27小时 - 所有可能的8个字符密码
  • md5crypt非常类似于phpass,费用为10):
    • 每秒7700万次猜测
    • 2。5年 - 所有可能的8个字符密码
  • bcrypt,费用为5
    • 每秒71次猜测
    • 2700年 - 所有可能的8个字符密码

注意:所有可能的8个字符的密码都使用94个字符集:

a-zA-Z0-9~`!@#$%^&*()_+-={}|[]\:";'<>,.?/

底线

因此,如果您正在编写新代码,请毫无疑问使用bcrypt。如果您现在正在制作phpasspbkdf2,那么您可能需要升级,但这并非明确“您非常容易受到攻击”。

答案 1 :(得分:19)

快速回答:

使用ircmaxell中的bcrypt (when it is ready)password_compat库 - 这是一个bcrypt库。

答案很长:

这对于当前的技术而言过于复杂和过时。应该避免Md5,只是盐腌不够好。使用bcrypt并避免头痛。

您可能会问自己为什么要使用特定的库?那么相同的功能将在PHP 5.5中提供,因此您不必更改任何编码。祝你好运,并保持简单高效。登录和密码也很慢。

  
    

更新1

  

@ Gumbo - &gt;没有MD5没有被破坏,但现在和过去像MD5这样的哈希的主要目的是用于文件检查(如你所知,检查文件的内容是否可以信任而不是密码存储),因为哈希很快就能解密你不会使用Bcrypt来检查文件的内容,因为你等待30-45秒......所以这意味着哈希特意意味着要快速阅读。与bcrypt相比,甚至SHA512仍然完全劣质。这就是为什么必须在PHP中推动像Blowfish / Bcrypt这样的强密码算法。我们作为用户和程序员都必须扩展普通密码存储或低级哈希算法不是答案的知识 - 绝不应该用于这些敏感信息。

现在向OP转换到新系统,您首先会向所有用户发送通知,说明“为了您的安全,密码加密系统已更新........”,那么您会要求他们进行一次性密码更新,一旦你进行密码更新,你就会使用名为password_verify的函数,如果你想最大限度地控制你的成本比率,你可以使用password_needs_rehash,如果由于某种原因你选择改变相关的成本用密码。

这不需要大量的代码来执行此操作,因为在密码保护中获得的收益会影响必须“重新编码”新密码系统的负面影响。

希望这可以回答大多数问题,但是IRCmaxell的答案在不同的算法上进一步详细说明是非常优越的!祝你好运OP,非常感谢ircmaxell!

  
    

更新2

         

另外,这样存储的密码是否真的可以破解?怎么样? (我现在好奇了)

  

我的网络安全教授告诉我,任何事情都被破坏了......我嘲笑他。现在我看到了他眼中的东西......好吧......这绝对是真的!

Bcrypt CURRENTLY 是存储密码的最佳方法!但是,如果你看看Scrypt似乎有希望但PHP不支持。尽管如此,一切都被打破,只是时间问题,直到地下室的一些“极客”才会破解Bcrypt。但是现在我们很安全..就像我们安全,IPv4永远不会耗尽......哦等等?......;)

希望这能解决你提出的先生问题。也只是把它放到上下文我的系统是17的成本,登录需要大约20 - 30秒,但当我向我的教授和我的客户介绍系统时,为什么它应该是强制性的,他们喜欢它和我们感到放心,他们受到了保护。

答案 2 :(得分:1)

我会选择bcrypt.It很好 除了结合使用盐来防止彩虹表攻击之外,bcrypt还是一种自适应功能:随着时间的推移,可以增加迭代次数以使其更慢,因此即使计算能力提高,它仍然可以抵抗暴力搜索攻击。

实施:How do you use bcrypt for hashing passwords in PHP?

答案 3 :(得分:-5)

(对不起我的英语)

几乎没有问题,所有评论者在回答之前都要问:

该代码将在何处使用?

它会保护哪种数据?

它会用于关键级系统吗?

好的,我们有一些用例。我使用简化的密码生成器用于IS,是的,它确实不是'第三次世界大战准备好',但是用户将密码告诉某人,或者它将被他的计算机中的某些恶意软件泄露的机会仍然大得多。

  • md5很弱,使用最新的指纹生成器(sha128,sha256,sha512)
  • 使用随机长度的盐,如:

    private function generateSalt() {
      $salt = '';
      $i = rand(20, 40); //min,max length
      for($length = 0; $length < $i; $length++) {
        $salt .= chr(rand(33, 126));
      }        
      return $salt;
    }
    
  • 然后生成一些密码:

    private function generatePass() {
      $vocabulary = 'abcdefghijklmnopqrstuvwxyz0123456789';
      $password = '';
      $i = rand(7,10);
      for($length = 0; $length < $i; $length++) {
          $password .= substr($vocabulary,rand(0, 35),1);
      }        
      return $password.'-'; // avoid copy`n`paste :)
    }
    
  • 是的,密码应该更强,但用户永远不会记住它,它会保存到浏览器或写在某处。期望和安全与现实。

    $newSalt = $this->generateSalt();
    $newPass = $this->generatePass();
    $newHash = hash('sha512', $newPass.':'.$newSalt); 
    // now store hash and salt into database
    
  • 有新哈希,但还没有结束,真正的工作已经开始:

    1. 记录
    2. 会话保护
    3. user,user + ip,ip temp / perm banning

登录+注销+重新/生成密码:1或2个SQL表和几kb代码

关于安全性的其他事情:很多表,真的超过几kb的代码