使用 PHP 5.5 的 password_hash 和 password_verify 函数

Posted

技术标签:

【中文标题】使用 PHP 5.5 的 password_hash 和 password_verify 函数【英文标题】:Using PHP 5.5's password_hash and password_verify function 【发布时间】:2013-02-06 04:38:00 【问题描述】:

假设我想为用户存储密码,这是否是使用 php 5.5 的 password_hash() 函数(或 PHP 5.3.7+ 的此版本:https://github.com/ircmaxell/password_compat)的正确方法?

$options = array("cost" => 10, "salt" => uniqid());
$hash = password_hash($password, PASSWORD_BCRYPT, $options);

那么我会这样做:

mysql_query("INSERT INTO users(username,password, salt) VALUES($username, $hash, " . $options['salt']);

插入数据库。

然后去验证:

$row = mysql_fetch_assoc(mysql_query("SELECT salt FROM users WHERE id=$userid"));
$salt = $row["salt"];
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10, "salt" => $salt));

if (password_verify($password, $hash) 
    // Verified

【问题讨论】:

嗯,这里有点奇怪,你有array("cost"=>10,后面什么都没有? 已修复,对此感到抱歉。 我从来不理解盐机制的使用。如果黑客试图暴力访问用户帐户,他们很可能可以访问您的数据库或网络服务器,从中他们可以轻松获取帐户的盐值。 Imo,使用盐机制是安全的误报。 那么你应该多读一些,盐的内容比你看起来理解的要多得多。 不,但如果你使用拉伸算法(如password_hash)并增加计算时间,你可以使蛮力/字典攻击变得不那么实用。 【参考方案1】:

暂时忽略你的数据库语句的问题,我将回答有关password_hash的问题。

简而言之,不,这不是你的做法。您不想单独存储盐,您应该同时存储哈希和盐,然后使用两者来验证密码。 password_hash 返回一个包含两者的字符串。

password_hash 函数返回一个包含哈希值和盐值的字符串。所以:

$hashAndSalt = password_hash($password, PASSWORD_BCRYPT);
// Insert $hashAndSalt into database against user

然后去验证:

// Fetch hash+salt from database, place in $hashAndSalt variable
// and then to verify $password:
if (password_verify($password, $hashAndSalt)) 
   // Verified

此外,正如 cmets 建议的那样,如果您对安全性感兴趣,您可能需要查看 mysqliext/mysql 在 PHP5.5 中已弃用),以及这篇关于 SQL 注入的文章:http://php.net/manual/en/security.database.sql-injection.php

【讨论】:

你是否建议他使用盐机制,但不存储盐?如果是这样,您将如何检索用于创建原始哈希的原始盐,以检查用户何时尝试登录? salt 和 hash 由 password_hash 在一个字符串中返回。 我想你会发现阅读这个:wiki.php.net/rfc/password_hash 和这个:php.net/password-hash 更有效率。 来自 php.net/password-hash:“如果省略,将创建随机盐并使用默认成本。”我认为这些选项适用于那些喜欢更多控制的人。不过,默认值应该没问题。 @DougSmith - 您应该创建自己的 salt,password_hash() 将从操作系统的随机源创建一个加密安全的 salt。它实施了后备措施以使盐尽可能好。【参考方案2】:

你不应该输入自己的盐,把盐留空,函数会生成好的随机盐。

将函数返回的整个字符串插入数据库(或文件或任何你使用的东西)。它包含了: 算法 ID、成本、盐(22 个字符)和哈希密码。

使用password_verify()需要整个字符串。盐是随机的,落入坏人之手(使用散列密码)不会造成伤害。这会阻止(或非常难以)使用现成集生成的密码和哈希列表 - 彩虹表。

您应该考虑添加成本参数。默认值(如果省略)为 10 - 如果更高,则函数计算哈希更长。将成本增加 1,生成哈希所需的时间增加一倍(从而延长破解密码所需的时间)

$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10));

您应该根据服务器上的速度检查来设置此参数。建议该函数执行 100 毫秒以上(有些人更喜欢将其设为 250 毫秒)。通常成本 = 10 或 11 是一个不错的选择(2015 年)。

为了提高安全性,您可能希望在密码中添加一个长的(50-60 个字符是不错的选择)秘密字符串。在使用 password_hash() 或 password_verify() 之前。

$secret_string = 'asCaahC72D2bywdu@#$@#$234';
$password  = trim($_POST['user_password']) . $secret_string;
// here use password_* function

注意 将 PASSWORD_BCRYPT 用于 algo 参数,将导致密码参数被截断为最大长度为 72 个字符。

如果 $password 将超过 72 个字符并且您更改或添加 73 或 90 个字符,则哈希不会更改。可选,粘贴 $secret_string 应该在最后(在用户密码之后而不是之前)。

【讨论】:

【参考方案3】:

从 php.net 注意到这一点

警告

从 PHP 7.0.0 开始不推荐使用 salt 选项。就是现在 更喜欢简单地使用默认生成的盐。

结论?忘记盐的选择。

这就够了password_hash('password', PASSWORD_DEFAULT) *(或_BCRYPT)

【讨论】:

BCRYPT 是 PHP 7.0.0 的默认值【参考方案4】:

不建议使用您自己的盐,从 PHP 7 开始,its use is deprecated。为了理解原因,password_hash 的作者分享了这些想法(链接已失效)

我已经非常清楚一件事:盐选项是 危险的。我还没有看到使用 salt 选项的单一用法 甚至体面。每次使用范围从坏(通过 mt_rand() 输出)到危险(静态字符串)到疯狂(传递密码) 作为自己的盐)。

我得出的结论是,我认为我们不应该允许用户 指定盐。

他甚至 made this comment in SO chat 指出自己撒盐是多么糟糕

【讨论】:

以上是关于使用 PHP 5.5 的 password_hash 和 password_verify 函数的主要内容,如果未能解决你的问题,请参考以下文章

使用 PHP 5.5 安装 xdebug

php 5.5使用 array_column的方法

谁能给我个PHP 5.5版本的下载地址啊。

使用 PHP 5.5 在 Amazon EC2 上安装 pdo_mysql

在两个 foreach 循环中使用“继续 2”时 php 5.5 内存泄漏?

在 CentOS 上将 PHP 5.3 更新到 PHP 5.5 后,我无法访问 phpmyadmin