6

我一直在研究散列/加密密码并将其存储在数据库中的正确方法。我知道 Salt 和 Hashing,所以我环顾四周,PBKDF2 似乎是一个不错的选择。所以我找到了这个网站,它提供了一个很好的教程以及 PBKDF2 的 PHP 改编版(这是我在我的网站上使用的)。

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

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。这是我必须存储在数据库中的内容,并且由于盐包含在生成的哈希中,因此我不需要将其添加到我的数据库中。然而,在用这个生成了一些测试用户之后,我想知道在我的数据库中的哈希密码中包含 PBKDF2 设置是否真的是一件好事。他们让我的新手看到它是一个黑客,在破解我的数据库后,会看到这些 sha256:1000:salt:password 东西,并弄清楚每个部分代表什么,这对他的尝试有很大帮助,不?

因此,我对其进行了一些修改,使其具有一个我生成并存储在我的数据库中的外部盐,并在通过 PBKDF2 运行它之前将盐包含在密码中。然后我做同样的事情来比较给定的密码和我在我的数据库中的登录密码,它可以工作。我唯一担心的是,使用 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));
}

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

谢谢,很抱歉这个长问题。

4

3 回答 3

4

我唯一担心的是,使用 128 位盐,生成的密码哈希只有 50 个字符长,这对我来说似乎不正确。

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

或者它是否只是同样的事情,因为通过将我的盐放在我的数据库中,一旦它被破解,黑客仍然可以暴力破解?

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

如果将盐碎片彼此分开存储,则可以获得少量的深度防御。它是否超过复杂性成本是一个判断要求,但我想说,花时间寻找 XSS 和 SQL 注入漏洞(攻击者经常获取上述数据库转储的方式)和保护使用 SSL 和证书或强密码在系统的各个组件之间建立连接。

于 2012-08-01T05:00:41.777 回答
2

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

请参阅这些答案,以便很好地了解隐藏盐是多么无用,在这里使用 bcrypt(因为在您的链接中没有给出实现),以及如何在此处使用哈希保护密码的最佳实践。最后,永远不要低估你的对手!

此外,(因为这有点旧),bcrypt 确实比 pbkdf2“更好”,但 scrypt 甚至更好!

于 2014-11-06T19:14:44.393 回答
0

有几件事,一个,而不是 SHA256,您应该使用慢速散列函数,例如 bcrypt,第二,您可能根本不应该存储密码(而是使用 openId 或类似的东西)。

综上所述,您需要为每个用户使用不同的盐,您可以将它们存储在同一行,甚至(正如您所做的那样)相同的字段,或完全不同的数据库中。您愿意付出多少努力取决于您和您的性能要求。

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

于 2012-08-01T05:11:52.660 回答