使用 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 建议的那样,如果您对安全性感兴趣,您可能需要查看 mysqli
(ext/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 在 Amazon EC2 上安装 pdo_mysql