Php 如何正确存储PBKDF2密码哈希

Php 如何正确存储PBKDF2密码哈希,php,encryption,hash,passwords,pbkdf2,Php,Encryption,Hash,Passwords,Pbkdf2,我一直在研究散列/加密密码并将其存储在数据库中的正确方法。我知道盐和散列,所以我环顾四周,PBKDF2似乎是一个不错的选择。因此,我发现它提供了一个很好的教程,以及对PHP的PBKDF2的改编(这是我在我的网站上使用的) 因此,我设置了我的网站,以使用这些功能生成/创建密码,但正如您在以下代码中看到的: salt在create_散列函数中生成,并存储在生成的散列中,该散列最终看起来像sha256:1000:salt:hashed_password。这就是我必须存储在数据库中的内容,由于salt包

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

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

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));
}
define(“PBKDF2散列算法”、“sha256”);
定义(“PBKDF2_迭代”,10000);
定义(“PBKDF2字节”,128);
定义(“PBKDF2散列字节”,24);
定义(“散列部分”,4);
定义(“哈希算法索引”,0);
定义(“哈希迭代索引”,1);
定义(“HASH_SALT_INDEX”,2);
定义(“HASH_PBKDF2_索引”,3);
函数create_hash($password,$salt)
{
//格式:salthash
返回
base64_编码(pbkdf2(
PBKDF2_散列算法,
$password,
$salt,
PBKDF2_迭代,
PBKDF2_哈希_字节,
真的
));
}
函数validate\u password($password、$salt、$good\u hash)
{
$pbkdf2=base64_解码($good_hash);
返回慢_等于(
$pbkdf2,
pbkdf2(
PBKDF2_散列算法,
$password,
$salt,
PBKDF2_迭代,
PBKDF2_哈希_字节,
真的
)
);
}
//比较长度为常量时间的两个字符串$a和$b。
函数慢_等于($a,$b)
{
$diff=strlen($a)^strlen($b);
对于($i=0;$i
结果散列的大小与salt的大小、密码和迭代次数完全无关。现代安全散列算法(如sha256)的输出总是相同的长度,而不管输入是什么。零长度输入与25TB输入具有相同的长度输出

或者,这也会是同样的事情,因为 我数据库里的盐一旦被破解黑客仍然可以 用蛮力穿过

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


如果将salt片段彼此分开存储,那么好处是深度防御的一小部分。它是否超过复杂性成本是一个判断问题,但我要说的是,花时间寻找XSS和SQL注入漏洞会更好(攻击者通常获得上述数据库转储的方式),并使用SSL和证书或强密码保护系统各个组件之间的连接。

有几件事,第一件事,您应该使用诸如bcrypt之类的慢速哈希函数,第二件事,您可能根本不应该存储密码(而应该使用openId或类似的东西)

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


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

< p> >用@ RoMeun.

的注释回答我自己的问题。
请参阅这些答案,以更好地了解隐藏 salt是,使用bcrypt(因为在您的链接中没有实现) ,以及关于如何使用哈希来保护 密码。最后,永远不要低估你的对手


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

请参阅这些答案,以便更好地了解使用bcrypt隐藏盐的无用性(因为在您的链接中没有给出实现),以及如何使用散列来保护密码的最佳实践。最后,永远不要低估你的对手!@Romain这是我一直在寻找的答案。现在就做一个,但是费用
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));
}