如何使用PHP的password_hash哈希和验证密码

问题描述 投票:74回答:7

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

根据我的理解(基于this page的读数),当您使用password_hash时,行中已经生成了盐。这是真的?

我的另一个问题是,吃2种盐不是很聪明吗?一个直接在文件中,另一个在数据库中?这样,如果有人破坏了数据库中的盐,您仍然直接在文件中保留了盐吗?我在这里读到,存储盐从来都不是一个聪明的主意,但是它总是让我感到困惑。

php salt password-hash php-password-hash
7个回答
134
投票

建议使用password_hash存储密码。不要将它们分隔为数据库和文件。

假设我们输入以下内容:

$password = $_POST['password'];

为了理解概念,我不验证输入。

您首先通过执行以下操作对密码进行哈希处理:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

然后查看输出:

var_dump($hashed_password);

如您所见,它是散列的。 (我假设您已执行这些步骤)。

现在,您将此hashed_pa​​ssword存储在数据库中,然后让我们说出当用户要求登录时。通过执行以下操作,可以在数据库中检查带有此哈希值的密码输入:

// 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.

Official Reference


19
投票

是的,您正确理解了,函数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);

您提到的第二种盐(存储在文件中的第二种盐,实际上是胡椒粉或服务器端密钥。如果在散列之前添加它(例如盐),则添加胡椒。不过,还有一种更好的方法,您可以先计算哈希,然后再使用服务器端密钥对哈希进行加密(双向)。这使您可以在必要时更改密钥。

与盐相反,此密钥应保密。人们经常将其混合并尝试隐藏盐,但是最好让盐完成其工作并用密钥添加秘密。


5
投票

是的,是的。为什么您对函数的php常见疑问表示怀疑? :)

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

  1. 使用的算法
  2. 参数
  3. 实际密码哈希

因此,您可以看到,哈希是其中的一部分。

当然,您可以为增加安全性添加额外的盐,但是老实说,在常规的php应用程序中,这太过分了。默认的bcrypt算法是好的,可以说可选的河豚甚至更好。


4
投票

不要使用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/en/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);

?>

希望这些都对您有帮助!


4
投票

PHP的密码功能中内置了关于向后和向前兼容性的讨论,这显然很缺乏。值得注意的是:

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

例如:

password_needs_rehash()

输出:

password_needs_rehash()

最后,鉴于您只能在登录时重新散列用户密码,因此应考虑将不安全的旧式哈希值“破坏”以保护用户。我的意思是,在一定的宽限期之后,您将删除所有不安全的[例如:裸MD5 / SHA /否则较弱的]哈希,并让用户依赖应用程序的密码重置机制。


1
投票

类密码完整代码:

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;
}

-1
投票

我已经建立了一个一直用于密码验证和创建密码的功能,例如将它们存储在MySQL数据库中。它使用随机生成的盐,比使用静态盐更安全。

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)
© www.soinside.com 2019 - 2024. All rights reserved.