bcrypt和随机生成的盐

5

我正在尝试使用bcrypt进行实验。我有一个类(如下所示),我从http://www.firedartstudios.com/articles/read/php-security-how-to-safely-store-your-passwords网站上获取的。该类包含3个函数。第一个函数用于生成随机Salt,第二个函数用于使用第一个生成的Salt生成哈希值,最后一个函数用于通过将提供的密码与哈希密码进行比较来验证密码。

<?php
/* Bcrypt Example */
class bcrypt {
    private $rounds;
    public function __construct($rounds = 12) {
        if(CRYPT_BLOWFISH != 1) {
            throw new Exception("Bcrypt is not supported on this server, please see the following to learn more: http://php.net/crypt");
        }
        $this->rounds = $rounds;
    }

    /* Gen Salt */
    public function genSalt() {
        /* openssl_random_pseudo_bytes(16) Fallback */
        $seed = '';
        for($i = 0; $i < 16; $i++) {
            $seed .= chr(mt_rand(0, 255));
        }
        /* GenSalt */
        $salt = substr(strtr(base64_encode($seed), '+', '.'), 0, 22);
        /* Return */
        return $salt;
    }

    /* Gen Hash */
    public function genHash($password) {
        /* Explain '$2y$' . $this->rounds . '$' */
            /* 2a selects bcrypt algorithm */
            /* $this->rounds is the workload factor */
        /* GenHash */
        $hash = crypt($password, '$2y$' . $this->rounds . '$' . $this->genSalt());
        /* Return */
        return $hash;
    }

    /* Verify Password */
    public function verify($password, $existingHash) {
        /* Hash new password with old hash */
        $hash = crypt($password, $existingHash);

        /* Do Hashs match? */
        if($hash === $existingHash) {
            return true;
        } else {
            return false;
        }
    }
}
/* Next the Usage */
/* Start Instance */
$bcrypt = new bcrypt(12);

/* Two create a Hash you do */
echo 'Bcrypt Password: ' . $bcrypt->genHash('password');

/* Two verify a hash you do */
$HashFromDB = $bcrypt->genHash('password'); /* This is an example you would draw the hash from your db */
echo 'Verify Password: ' . $bcrypt->verify('password', $HashFromDB);
?>

现在,如果我例如使用'password'生成哈希值,那么我将得到一个哈希密码,它使用了随机生成的盐。接下来,如果我再次输入'password'并使用验证函数,我会得到true,这意味着密码匹配。如果我输入错误的密码,则会得到false。我的问题是这怎么可能?随机生成的盐又怎样都没有影响吗?

2
PHP v5.5(即将推出)将拥有一组password_xxx()函数,可以消除任何此类代码的需求。还有一个向后兼容的库可供下载,该库将这些PHP 5.5密码函数实现到5.3或5.4中。您可能想要尝试它;它会为您节省大量工作。请参见https://github.com/ircmaxell/password_compat - Spudley
1个回答

16

好好观察你要处理的值。随机生成的盐将是:

abcdefg...

输入到crypt的内容看起来像这样:

crypt($password, '$2y$10$abcdefg...')
                   |  |    |
                   |  |    +- the salt
                   |  +- the cost parameter
                   +- the algorithm type

结果看起来像:

$2y$10$abcdefg...123456789...
 |  |    |        |
 |  |    |        +- the password hash
 |  |    +- the salt
 |  +- the cost parameter
 +- the algorithm type

换句话说,结果哈希的第一部分与输入到crypt函数中的原始输入相同;它包含算法类型和参数、随机盐和hash结果。

Input:  $password + $2y$10$abcdefg...
Output:             $2y$10$abcdefg...123456789...
                    ^^^^^^^^^^^^^^^^^
                   first part identical

当您确认密码时,需要使用相同的原始salt。只有使用相同的salt才能使相同的密码哈希为相同的散列值。并且在哈希中仍然存在,以一种可以直接传递给crypt函数的格式,以便重复生成哈希时执行与生成哈希时相同的操作。这就是为什么您需要将密码和哈希值一起传入验证函数的原因:

crypt($passwordToCheck, '$2y$10$abcdefg...123456789...')

crypt函数会取前若干个字符,包括abcdefg...在内,并丢弃其余字符(这就是为什么盐值需要固定长度的原因)。因此,它与之前的操作相同:

crypt函数会取前定义好的一定数量的字符,包括abcdefg...在内,并将其余的部分舍去(这也是为什么盐值必须是一个固定长度的原因)。因此,它和以前的操作相同:

crypt($passwordToCheck, '$2y$10$abcdefg...')

只有在$passwordToCheck相同时,才会生成相同的哈希值(if and only if)


1
是的,因为盐是生成的哈希中的一部分,以明文形式存在。您不需要单独存储它,它是哈希的一部分。哈希的第一部分与 crypt 所需的参数完全相同,这就是为什么您只需将其反馈到 crypt 中的原因。 - deceze
那么你的意思是bcrypt在验证时只检查从原始密码字符串创建的哈希的部分,并忽略Salt吗? - Sameer Zahid
1
不是,相反的。请看我的更新。它读取算法、成本和盐部分,就像以前一样,忽略附加的哈希值。 - deceze
所以底线是,我只需要将用户输入的明文密码传递给验证函数,无需为其添加盐,也不需要在哈希原始密码时在表中存储生成的盐,比如当注册用户时。 - Sameer Zahid
1
已经有很多关于这个问题的答案了:http://security.stackexchange.com/questions/17421/how-to-store-salt,http://security.stackexchange.com/questions/33505/why-using-random-salts - deceze
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接