如何使用password_hash

Jos*_*ter 73 php salt password-hash php-password-hash

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

根据我的理解(基于本页的阅读:http://php.net/manual/en/faq.passwords.php),当您使用password_hash时,已经在行中生成了salt.这是真的?

我的另一个问题是,含有2种盐是不是很聪明?一个直接在文件中,一个在DB中?这样,如果有人在数据库中泄露了你的盐,你仍然直接在文件中?我在这里读到,存储盐绝不是一个明智的想法,但它总是让我困惑的是人们的意思.

Aka*_*kar 132

使用password_hash是建议密码存储的方法.不要将它们与数据库和文件分开.

假设我们有以下输入:

$password = $_POST['password'];
Run Code Online (Sandbox Code Playgroud)

我不是为了理解这个概念而验证输入.

您首先通过执行以下操作来散列密码:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);
Run Code Online (Sandbox Code Playgroud)

然后看输出:

var_dump($hashed_password);
Run Code Online (Sandbox Code Playgroud)

正如你所看到的那样.(我假设你做了那些步骤).

现在,您将此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.
Run Code Online (Sandbox Code Playgroud)

官方参考

  • 手册中非常清楚地推荐使用Varchar 255. (6认同)
  • @FunkFortyNiner,b/c Josh问了这个问题,我发现它,2年后,它帮助了我.这就是SO的重点.该手册与泥浆一样清晰. (6认同)
  • 这已经在手册http://php.net/manual/en/function.password-hash.php --- http://php.net/manual/en/function.password-verify.php那个OP可能没有阅读或理解.这个问题经常被问到. (4认同)
  • @toddmo:为了赞同你的评论,我刚刚在 2020 年 6 月提出这个问题,这次讨论让我免去了几个小时的挫败感。我也发现 PHP 手册大多数时候都像泥巴一样清晰。 (4认同)
  • 好的,我只是尝试了一下,它奏效了。我对此功能表示怀疑,因为它似乎太简单了。您建议我使用varchar长度多长时间?225? (2认同)
  • 至于长度,在关于password_hash的PHP手册中,有一个示例中的注释——“注意DEFAULT可能会随着时间的推移而改变,所以你需要通过允许你的存储扩展到超过60个字符(255个字符会很好)来做好准备” (2认同)

mar*_*kli 19

是的,你理解正确,函数password_hash()将自己生成一个salt,并将其包含在生成的哈希值中.将盐存储在数据库中是绝对正确的,即使已知,它也可以正常工作.

// 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);
Run Code Online (Sandbox Code Playgroud)

你提到的第二种盐(存储在文件中的盐)实际上是胡椒或服务器端密钥.如果你在散列之前添加它(比如盐),那么你加一个胡椒.有一种更好的方法,你可以先计算哈希值,然后用服务器端密钥加密(双向)哈希.这使您可以在必要时更改密钥.

与盐相反,这个钥匙应该保密.人们经常将它混合起来并试图隐藏盐,但最好让盐完成它的工作并用钥匙添加秘密.


小智 8

永远不要使用 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);
Run Code Online (Sandbox Code Playgroud)

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

<?php 

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

如果您想使用自己的盐,请使用自定义生成的函数,只需按照下面的操作,但我不推荐这样做,因为它在最新版本的 PHP 中已被弃用。

在使用以下代码之前阅读password_hash()

<?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);
Run Code Online (Sandbox Code Playgroud)

  • salt 选项被弃用是有充分理由的,因为该函数尽最大努力生成加密安全的 salt,而且几乎不可能做得更好。 (5认同)

Sam*_*tch 6

关于内置于 PHP 密码函数的向后和向前兼容性的讨论明显缺乏。尤其:

  1. 向后兼容性:密码函数本质上是围绕 的精心编写的包装器crypt(),并且本质上与crypt()-format 散列向后兼容,即使它们使用过时和/或不安全的散列算法。
  2. 向前兼容性:password_needs_rehash()您的身份验证工作流程中插入一些逻辑可以使您的哈希值与当前和未来的算法保持同步,并且未来对工作流程的更改可能为零。注意:任何与指定算法不匹配的字符串都将被标记为需要重新散列,包括非加密兼容的散列。

例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

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)
Run Code Online (Sandbox Code Playgroud)

最后要注意的是,鉴于您只能在登录时重新散列用户的密码,您应该考虑“取消”不安全的遗留散列以保护您的用户。我的意思是,在一定的宽限期之后,您将删除所有不安全的 [例如:裸 MD5/SHA/其他弱] 哈希值,并使您的用户依赖于您的应用程序的密码重置机制。


Joe*_*inz 5

对,是真的.你为什么怀疑这个函数的php faq?:)

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

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

正如您所看到的,哈希是其中的一部分.

当然,你可以增加一层额外的盐来增加一层安全性,但老实说我认为这在普通的php应用程序中有点过分.默认的bcrypt算法很好,可选的河豚可以说更好.

  • BCrypt 是一个 _hashing_ 函数,而 Blowfish 是一个 _encryption_ 算法。BCrypt 起源于 Blowfish 算法。 (2认同)