如何使用PHP的password_hash来散列和验证密码?

php salt password-hash php-password-hash


最近,我一直在尝试在互联网上偶然发现的登录脚本上实现自己的安全性。在尝试学习如何制作自己的脚本以为每个用户生成盐的努力之后,我偶然发现了 password_hash

据我了解(基于本页面的阅读内容),当您使用 password_hash 时,salt已在行中生成。这是真的?

我的另一个问题是,如果有2个盐,不是很聪明吗?一个直接在文件中,一个在数据库中?这样的话,如果有人破坏了你在DB中的盐,你还会有直接在文件中的盐吗?我在这里读到,存储盐分从来都不是一个聪明的主意,但我一直不明白人们的意思。




Answer 1 Akar


建议使用 password_hash 来存储密码。不要将它们分开到数据库和文件中。

假设我们有以下输入:

$password = $_POST['password'];

我不会为了理解概念而验证输入,只是为了理解概念而验证。

你首先要通过这样做来进行密码的加密。

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

然后看输出。

var_dump($hashed_password);

正如你所看到的那样,它已经被洗过了。我假设你做了这些步骤)。

现在你把这个hash_password存储在数据库中,确保你的密码列足够大,可以容纳散列值(至少60个字符或更长)。当用户要求登录时,你在数据库中用这个散列值来检查密码输入,通过这样做,你就可以在数据库中检查密码输入。

// Query the database for username and password
// ...

if(password_verify($password, $hashed_password)) {
    // If the password inputs matched the hashed password in the database
    // Do something, you know... log them in.
} 

// Else, Redirect them back to the login page.

官方参考资料




Answer 2 martinstoeckli


是的,你没看错,函数password_hash()会自行生成一个盐,并将其包含在生成的哈希值中。将盐存储在数据库中是绝对正确的,即使是已知的,它也能完成它的工作。

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT);

// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);

你提到的第二个盐(存储在文件中的那个),其实就是胡椒粉或者是服务器端键。如果你在散列前加了它(像盐一样),那么你就加了一个胡椒粉。不过还有一个更好的方法,你可以先计算出散列,然后用服务器端密钥对散列进行加密(双向)。这样你就可以在必要时改变密钥。

与食盐相比,这把钥匙要保密。人们常常把它混为一谈,想方设法地把盐藏起来,但最好是让盐发挥它的作用,用钥匙把这个秘密加进去。




Answer 3 Joel Hinz


是的,这是真的。你为什么要怀疑phpfaq上的函数?)

运行 password_hash() 的结果包含四个部分:

  1. the algorithm used
  2. parameters
  3. salt
  4. 切换密码

所以大家可以看到,哈希是其中的一部分。

当然,你可以加一个额外的盐来增加安全性,但我认为这在普通的php应用中是矫枉过正的。默认的 bcrypt 算法很好,而可选的 blowfish 算法可以说是更好的。




Answer 4 Mahesh Yadav


千万不要用md5()来保护密码,即使是用盐,也是很危险的!

使用以下最新的散列算法使您的密码安全。

<?php

// Your original Password
$password = '121@121';

//PASSWORD_BCRYPT or PASSWORD_DEFAULT use any in the 2nd parameter
/*
PASSWORD_BCRYPT always results 60 characters long string.
PASSWORD_DEFAULT capacity is beyond 60 characters
*/
$password_encrypted = password_hash($password, PASSWORD_BCRYPT);

?>

要与数据库的加密密码和用户输入的密码进行匹配,请使用以下功能。

<?php 

if (password_verify($password_inputted_by_user, $password_encrypted)) {
    // Success!
    echo 'Password Matches';
}else {
    // Invalid credentials
    echo 'Password Mismatch';
}

?>

如果你想使用你自己的盐,请使用你的自定义生成函数,只需按照下面的方法,但我不建议这样做,因为在最新版本的PHP中发现它已被淘汰。

在使用以下代码之前,请先阅读此http://php.net/manual/zh/function.password-hash.php

<?php

$options = [
    'salt' => your_custom_function_for_salt(), 
    //write your own code to generate a suitable & secured salt
    'cost' => 12 // the default cost is 10
];

$hash = password_hash($your_password, PASSWORD_DEFAULT, $options);

?>

希望这些都能帮助到你!




Answer 5 Sammitch


在PHP的密码功能中,明显缺乏对前后向兼容性的讨论,这也是PHP内置的密码功能。值得注意的是。

  1. 向后兼容性:密码函数实质上是 crypt() 周围写得很好的包装,并且即使使用过时和/或不安全的哈希算法,也固有地与 crypt() 格式的哈希向后兼容。
  2. 转发兼容性:在认证工作流程中插入 password_needs_rehash() 和一些逻辑,可以使您的哈希算法保持最新​​,并与当前和将来的算法保持一致,并且对工作流程的未来更改可能为零。注意:任何与指定算法不匹配的字符串都将被标记为需要重新哈希,包括不兼容crypt的哈希。

Eg:

class FakeDB {
    public function __call($name, $args) {
        printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args));
        return $this;
    }
}

class MyAuth {
    protected $dbh;
    protected $fakeUsers = [
        // old crypt-md5 format
        1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'],
        // old salted md5 format
        2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'],
        // current bcrypt format
        3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO']
    ];

    public function __construct($dbh) {
        $this->dbh = $dbh;
    }

    protected function getuser($id) {
        // just pretend these are coming from the DB
        return $this->fakeUsers[$id];
    }

    public function authUser($id, $password) {
        $userInfo = $this->getUser($id);

        // Do you have old, turbo-legacy, non-crypt hashes?
        if( strpos( $userInfo['password'], '$' ) !== 0 ) {
            printf("%s::legacy_hash\n", __METHOD__);
            $res = $userInfo['password'] === md5($password . $userInfo['salt']);
        } else {
            printf("%s::password_verify\n", __METHOD__);
            $res = password_verify($password, $userInfo['password']);
        }

        // once we've passed validation we can check if the hash needs updating.
        if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) {
            printf("%s::rehash\n", __METHOD__);
            $stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?');
            $stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]);
        }

        return $res;
    }
}

$auth = new MyAuth(new FakeDB());

for( $i=1; $i<=3; $i++) {
    var_dump($auth->authuser($i, 'foo'));
    echo PHP_EOL;
}

Output:

MyAuth::authUser::password_verify
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]])
bool(true)

MyAuth::authUser::legacy_hash
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]])
bool(true)

MyAuth::authUser::password_verify
bool(true)

最后一点,考虑到你只能在登录时重新设置用户的密码,你应该考虑 "日落 "不安全的传统哈希值来保护你的用户。我的意思是,在一定的宽限期后,你应该删除所有不安全的[例如:裸MD5/SHA/其他弱的]哈希值,并让你的用户依赖你的应用程序的密码重置机制。