22

我想知道这个功能(部分取自大约 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

4 回答 4

52

您提供的代码是PHPASS的一个端口,特别是“便携式”算法。注意 的资格portablephpass如果您true作为第二个构造函数参数传递,这将仅适用于库。从这里开始,这个答案phpass指可移植算法,而不是库本身。如果您没有明确指定,该库将默认执行 bcrypt ...portable

PHPBB 团队并没有自己开发这个(非常好的事情),而是直接从 phpass 移植过来的(有争议)。

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

坏吗?

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

有哪些弱点?

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

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

    Sopbkdf2大约37.5%phpass使用相同原语时强(md5在这种情况下)。但pbkdf2也可以采用更强的原语。所以我们可以使用pbkdf2withsha512来获得相对于算法非常显着的优势phpass(主要是因为sha512它是一种更难的算法,计算量比 多md5)。

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

    话虽如此,差异并不过分显着。它非常可衡量,并且pbkdf2绝对比phpass.

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

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

    一个更强大的例子是使用比普通散列函数大得多的内部状态bcrypt这一事实。使用 512 位内部状态来计算 1024 位块。而是使用大约 32kb 的内部状态来计算单个 576 位块。的内部状态比(和和)大得多的事实部分解释了.bcryptSHA512bcryptbcryptSHA512md5phpassbcrypt

应该避免

对于新项目,绝对。并不是说它不好。它不是。就是那里有明显更强大的算法(按数量级)。那么为什么不使用它们呢?

要进一步证明如何bcrypt更强大,请查看来自 Password13 的幻灯片 (PDF),它启动了一个 25 GPU 集群来破解密码哈希。以下是相关结果:

  • md5($password)
    • 每秒 1800 亿次猜测
    • 9.4 小时 - 所有可能的 8 个字符密码
  • sha1($password)
    • 每秒 610 亿次猜测
    • 27 小时 - 所有可能的 8 个字符密码
  • md5crypt(这与成本为 10 的 phpass非常相似):
    • 每秒 7700 万次猜测
    • 2.5 年 - 所有可能的 8 个字符密码
  • bcrypt费用为5
    • 每秒 7.1 万次猜测
    • 2700 年 - 所有可能的 8 个字符密码

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

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

底线

因此,如果您正在编写新代码,毫无疑问使用bcrypt. 如果您现在已经phpasspbkdf2正在生产中,您可能想要升级,但这并不是一个明确的“你很容易受到攻击”。

于 2013-04-16T18:04:30.017 回答
19

快速回答:

使用bcrypt(当它准备好时)或来自 ircmaxell 的password_compat库——它是一个 bcrypt 库。

长答案:

这对于当前的技术来说太复杂和过时了。应该避免使用 Md5,仅仅加盐是不够的。使用 bcrypt 并免去您的头痛。

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

更新 1

@ Gumbo -> 没有 MD5 没有被破坏,但是像 MD5 这样的哈希现在和过去的主要目的是用于文件检查(你知道检查文件的内容是否可以信任而不是密码存储),因为哈希非常快速破译,因为你不会使用 Bcrypt 来检查文件的内容,因为你等待 30 - 45 秒......所以这意味着哈希是专门用来快速读取的。即使是 SHA512 与 bcrypt 相比仍然完全逊色。这就是为什么在 PHP 中推动像 Blowfish/Bcrypt 这样的强密码算法势在必行的原因。我们作为用户和程序员都必须扩展知识,即普通密码存储或低级散列算法不是答案 - 并且永远不应该用于此类敏感信息。

现在让 OP 过渡到新系统,您首先会向所有用户发送通知,说明“为了您的安全,密码加密系统已更新........”,然后您会问他们对于一次性密码更新,一旦您进行密码更新,您将使用名为 password_verify 的函数,如果您想最大限度地控制成本比率,您可以使用 password_needs_rehash,如果出于某种原因您选择更改与密码相关的成本.

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

希望这能回答大多数问题,但 IRCmaxell 的答案在进一步深入不同算法的细节方面要好得多!祝你好运,非常感谢 ircmaxell!

更新 2

另外,这样存储的密码真的可以破解吗?如何?(我现在很好奇)

我的网络安全教授告诉我的任何事情和一切都被破坏了。我嘲笑他。现在我从他的眼里看到了东西……嗯,是的……那绝对是真的!

Bcrypt CURRENTLY是存储密码的最佳方法!但是,如果您研究 Scrypt,这似乎很有希望,但 PHP 不支持。尽管如此,任何东西都被破坏了,地下室的一些“极客”破解Bcrypt只是时间问题。但是现在我们是安全的.. 就像 IPv4 永远不会用完一样我们是安全的.. 哦等等?... ;)

希望这能回答您提出的问题,先生。也只是为了把它放在上下文中,我的系统的成本是 17,登录需要大约 20 - 大约 30 秒,但是当我将系统展示给我的教授和我的客户以及为什么它应该是强制性的时,他们喜欢它并且对他们受到保护感到放心。

于 2013-04-16T16:25:13.480 回答
1

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

实现:如何在 PHP 中使用 bcrypt 对密码进行哈希处理?

于 2013-04-21T17:01:56.247 回答
-5

(对不起我的英语一开始)

有几个问题,所有评论者在回答之前都必须问:

该代码将在哪里使用?

它将保护什么样的数据?

它会用于关键级别的系统吗?

好的,我们有一些用例。我正在为 out 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 禁止
    4. ETC

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

关于安全性的其他事情:很多表,实际上不仅仅是几 kb 的代码

于 2013-04-20T01:42:15.880 回答