当前位置: 首页 > 知识库问答 >
问题:

如何正确存储PBKDF2密码哈希

哈骞仕
2023-03-14

我一直在研究散列/加密密码并将其存储在数据库中的正确方法。我知道盐和散列,所以我环顾四周,PBKDF2似乎是一个不错的选择。所以我找到了这个网站,它提供了一个很好的教程,以及一个适用于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_散列函数中生成,并存储在生成的散列中,该散列最终看起来像sha256:1000:salt:hashed_password。这就是我必须存储在数据库中的内容,由于salt包含在结果哈希中,所以我不需要将其添加到数据库中。然而,在生成了一些测试用户之后,我想知道在数据库中的哈希密码中包含PBKDF2设置是否真的是一件好事。他们就像我的新手自己看到的那样,黑客在破解我的数据库后,会看到这些sha256:1000:salt:password的东西,然后找出每个部分代表什么,这对他的尝试会有很大帮助,不是吗?

因此,我对其进行了一些修改,以生成一个外部salt并将其存储在数据库中,并在通过PBKDF2运行它之前将salt包含在密码中。然后,我做同样的事情,将给定的密码与我在数据库中的登录密码进行比较,这样就可以了。我唯一担心的是,如果使用128位salt,则生成的密码散列长度仅为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个答案

王长卿
2023-03-14

有几件事情,第一,你应该使用慢哈希函数,比如bcrypt,而不是SHA256,第二,你可能根本不应该存储密码(而是使用openId或类似的东西)。

所有这些都说明,您需要为每个用户提供不同的salt,您可以将它们存储在同一行中,甚至(正如您所做的)同一字段中,或者存储在完全不同的db中。你愿意付出多少努力取决于你和你的绩效要求。

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

况弘新
2023-03-14

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

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

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

申屠锦
2023-03-14

我唯一担心的是,如果使用128位salt,则生成的密码散列长度仅为50个字符,这在我看来是不对的。

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

还是说,不管怎样,这只是一回事,因为通过在我的数据库中存储我的盐,一旦它被破解,黑客仍然可以强行通过?

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

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

 类似资料:
  • 在这里找到了一些示例代码,但这只适用于SHA1。代码的关键位是: 我需要把这个从SHA1改成SHA256。 从Java文档和这篇文章来看,下面的内容似乎是可能的,但是C#库中的构造函数没有重载。

  • 我正在写一个Django应用程序,需要与现有的Java播放框架应用程序一起工作。Play应用程序使用PasswordHash.java来存储密码。它以冒号分隔的格式存储密码。每个哈希都存储为::。 例如,下面是密码“测试”的条目: 在这里,我们可以通过拆分字符串并找到: 迭代次数: 盐: PBKDF2哈希:。 我修改了Django的check_密码机制以与此格式兼容,但发现它认为密码不正确。我用了

  • 尽管我有点困惑,我是否应该将salt输入到我的PBKDF2函数中,并将salt存储在一列中,而将PBKDF2的密码存储在另一列中。 我也在使用CodeIgniter,并找到了一个用于PBKDF2的库(https://github.com/hashemqolami/codeigniter-pbkdf2-library),它声称我不需要单独存储salt。 使用作为推荐的用户密码注册用户;无需单独存储用

  • 我使用Laravel 5.7,并在不同的服务器上运行两个应用程序。我想通过加密进行通信。所以两端都需要有一定的键。默认的 laravel加密设置为 AES-256-CBC。 所以我想,我需要一个32字节的密钥,就像这样: $ key = bin 2 hex(OpenSSL _ random _ pseudo _ bytes(32,$ c strong)); 我得到一个64长的十六进制字符串,我想保

  • 存储密码 用户名和密码组合必须存储在某处。以下列表提到了一些受欢迎的地方: 文字:你现在应该熟悉这种方法。 SQL数据库:FreeRADIUS包含与SQL数据库交互的模块。 MySQL非常受欢迎,并且广泛用于FreeRADIUS。 目录:Microsof的Actve目录或Novell的电子目录是典型的企业级目录。OpenLDAP是一种流行的开源替代方案。 FreeRADIUS可以使用的用户文件和S

  • 过去一段时间以来, 许多的网站遭遇用户密码数据泄露事件, 这其中包括顶级的互联网企业–Linkedin, 国内诸如CSDN,该事件横扫整个国内互联网,随后又爆出多玩游戏800万用户资料被泄露,另有传言人人网、开心网、天涯社区、世纪佳缘、百合网等社区都有可能成为黑客下一个目标。层出不穷的类似事件给用户的网上生活造成巨大的影响,人人自危,因为人们往往习惯在不同网站使用相同的密码,所以一家“暴库”,全部