如何正确存储PBKDF2密码哈希

时间:2012-07-31 06:41:09

标签: php encryption hash passwords pbkdf2

我一直在研究如何正确地散列/加密密码并将其存储在数据库中。我知道Salt和Hashing所以我环顾四周,PBKDF2似乎是一个不错的选择。所以我发现this website提供了一个很好的教程,以及适用于PHP的PBKDF2(这是我用于我的网站)。

所以我设置我的网站使用这些功能来生成/创建密码,但正如您在以下代码中看到的那样:

function create_hash($password) {
// format: algorithm:iterations:salt:hash
$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" .  $salt . ":" .
    base64_encode(pbkdf2(
        PBKDF2_HASH_ALGORITHM,
        $password,
        $salt,
        PBKDF2_ITERATIONS,
        PBKDF2_HASH_BYTES,
        true
    )); }

salt在create_hash函数中生成,并存储在生成的哈希中,最终看起来像sha256:1000:salt:hashed_pa​​ssword。这是我必须存储在我的数据库中,并且由于salt包含在生成的哈希中,我不需要将它添加到我的数据库中。但是,在用这个生成一些测试用户之后,我想知道在数据库中使用哈希密码中的PBKDF2设置实际上是一件好事。他们的方式我的新手自己看到它是一个黑客,破解我的数据库后,会看到这些sha256:1000:盐:密码的东西,并弄清楚每个部分将代表什么,这将有助于他在他的尝试,不?

所以我稍微修改了一下我生成并存储在我的数据库中的外部盐,并在通过PBKDF2运行之前在密码中包含salt。然后我做同样的事情来比较给定的密码和我在我的数据库中用于登录,它的工作原理。我唯一担心的是,使用128位盐,生成的密码哈希长度只有50个字符,这对我来说似乎不对。

这是我目前的代码:

define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 10000);
define("PBKDF2_SALT_BYTES", 128);
define("PBKDF2_HASH_BYTES", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

function create_hash($password, $salt)
{
    // format: salthash
    return  
        base64_encode(pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        ));
}

function validate_password($password, $salt, $good_hash)
{
    $pbkdf2 = base64_decode($good_hash);
    return slow_equals(
        $pbkdf2,
        pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        )
    );
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
    $diff = strlen($a) ^ strlen($b);
    for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
    {
        $diff |= ord($a[$i]) ^ ord($b[$i]);
    }
    return $diff === 0; 
}

function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}

我对这种密码保存格式的关注是否有意义?或者它只是相同的事情,因为通过在我的数据库中使用我的盐,一旦它被破解,黑客仍然可以通过暴力破解?

谢谢,抱歉这个长期的问题。

3 个答案:

答案 0 :(得分:4)

  

我唯一担心的是一个带有128位盐的密码   hash只有50个字符,对我来说似乎不对。

生成的哈希的大小与salt的大小,密码和迭代次数完全无关。无论输入如何,现代安全散列算法(如sha256)的输出总是相同的长度。零长度输入与25TB输入具有相同的长度输出。

  

或者无论如何它只是相同的东西,因为拥有我的   在我的数据库中,一旦它被破解,黑客仍然可以   蛮力通过?

通过将盐分成两部分,您需要增加代码复杂性(通常是一件坏事)。根据您储存盐块的方式,在某些情况下您可能会获得一些好处。例如,如果静态salt片段存储在数据库之外,那么数据库的转储将不会向攻击者提供足够的信息来对数据库中的密码哈希值执行脱机攻击。

如果盐片彼此分开存放,则获得的收益是深度的少量防御。它是否超过了复杂性成本是一个判断调用,但我说有时候可以更好地花时间寻找XSS和SQL注入漏洞(攻击者经常获取的方式上面提到的数据库转储),并使用SSL和证书或强密码保护系统的各个组件之间的连接。

答案 1 :(得分:2)

通过@Romain的评论回答我自己的问题。

  

请参阅这些答案,以便更好地了解隐藏无用的方法   salt是here,使用bcrypt(因为在你的链接中没有实现   给出了here,以及关于如何使用哈希来保护的最佳实践   密码here。最后,永远不要低估你的对手!

另外,(因为这有点旧),bcrypt确实是更好的&#34;比pbkdf2,但scrypt更好!

答案 2 :(得分:0)

一些事情,一个,而不是SHA256,你应该使用慢速散列函数,如bcrypt,两个,你可能根本不应该存储密码(而是使用openId或类似的东西)。

所有这些说,你需要为每个用户提供不同的盐,你可以将它们存储在同一行中,甚至(就像你在做的那样)存储在相同的字段中,或者存储在完全不同的数据库中。您愿意花多少精力取决于您和您的性能要求。

除了每个用户/密码的单个salt之外,您还可以考虑每个应用程序salt,它不在任何数据库中。