如何正确存储 PBKDF2 密码哈希

Posted

技术标签:

【中文标题】如何正确存储 PBKDF2 密码哈希【英文标题】:How to properly store a PBKDF2 password hash 【发布时间】:2012-07-28 21:18:52 【问题描述】:

我一直在研究散列/加密密码并将其存储在数据库中的正确方法。我知道 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
    )); 

盐是在 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));

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

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

【问题讨论】:

查看这些答案以更好地了解隐藏盐是多么无用 here,使用 bcrypt(因为在您的链接中没有给出实现)here,以及如何使用哈希的最佳实践保护密码here。最后,永远不要低估你的对手! @Romain 这是我一直在寻找的答案。暂时制作了一个,但请随意回答。 【参考方案1】:

我唯一担心的是,一个 128 位的盐,得到的密码 hash 的长度只有 50 个字符,这对我来说似乎不太合适。

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

或者无论如何它只是一样的东西,因为通过我的 我的数据库中的盐,一旦它被破解,黑客仍然可以 强行闯关?

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

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

【讨论】:

【参考方案2】:

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

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

另外,(因为这有点老了),bcrypt 确实比 pbkdf2“更好”,但是 scrypt 更好!

【讨论】:

【参考方案3】:

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

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

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

【讨论】:

您不应该使用每个应用程序的盐没有密码特定的盐,因为攻击者可以搜索重复的哈希并使用该信息来攻击最易受攻击的密码(并找到两个或多个用户帐户完成相同的工作量)。 @owlstead:是的,我的意思是在 addition 中添加密码。将编辑以明确这一点。谢谢。 这个答案令人困惑。使用 PBKDF2 时使用 SHA256 没有任何问题。 PBKDF2 构造增加了速度(它相当于 bcrypt;“1000”部分表示 SHA256 被 PBKDF2 调用了 1000 次)。 PBKDF2 不等同于 bcrypt——它们有相似之处,但运行 SHA256 1000 次与运行 bcrypt 1000 次不同。 @jmoreno:如果您将 PBKDF2 与 bcrypt 进行比较,那么请考虑在 1 次 PBKDF2 调用期间发生的 1000x SHA256 调用(或您采用的任何哈希算法/迭代计数)。

以上是关于如何正确存储 PBKDF2 密码哈希的主要内容,如果未能解决你的问题,请参考以下文章

是否有使用 PBKDF2 作为密码哈希的标准?

使用Pbkdf2加密加密和验证使用Salt的哈希密码

我应该使用什么哈希算法来存储密码?

如何散列密码

Java 中带有 bouncycastle 的 PBKDF2

ruby 在Ruby中安全实施salted PBKDF2密码哈希(请参阅https://crackstation.net/hashing-security.htm)