【问题标题】:Password does not match after being encrypted using crypt() and password_hash() function使用 crypt() 和 password_hash() 函数加密后密码不匹配
【发布时间】:2013-11-08 21:02:00
【问题描述】:

我修改了我的旧帖子。我尝试了 crypt() 函数,现在尝试使用 password_hash() 和 password_verify() 来验证来自数据库的加密密码,但是在每次调用时,password_hash() 函数会重新调整不同的加密字符串,而 password_verify() 无法匹配它。

我就是这样做的。

 //please ignore the syntax error if any

$data = '11';
$dbpass = password_hash($data, PASSWORD_BCRYPT);
echo $dbpass;  // displays the random strings on each page refresh.

一旦密码被保存到数据库中,在登录过程中不匹配。下面是我的实际功能。

   private function process_data($password){
    $password = __STR.$password.__STR;
    return  password_hash($password, PASSWORD_BCRYPT);

  }
  private function processed($login_password, $dbpassword){
    $login_password = __STR.$login_password.__STR;
    return password_verify($login_password, $dbpassword);
  }

在每次为密码创建哈希字符串的函数调用中,该函数下一次返回不同的字符串。

【问题讨论】:

  • 请阅读为什么不应该使用 MD5 来散列密码:php.net/manual/en/faq.passwords.php
  • 我相信(并且在很多地方都读过)最好给每个密码自己的盐。
  • 同意你的看法。我已经看到 MD5 加密的反向在互联网上很容易获得。但是我尝试在其中添加盐,以使黑客很难弄清楚这个密码是什么。这不是个好主意吗?我总是寻求专家意见。
  • @HunzaAli - 根据您的新问题更新了我的答案。
  • 另见 Openwall 的 PHP password hashing framework (PHPass)。它的便携性和强化了对用户密码的一些常见攻击。

标签: php security passwords php-password-hash


【解决方案1】:

好的,让我们一个一个来看看。

首先,它是散列,而不是加密。加密是两种方式,散列是一种方式。我们想要散列。我们从不想加密。是的,术语很重要。请使用正确的术语。

接下来,对password_hash 的每次调用假定返回不同的哈希值。那是因为它会生成强随机盐。这就是它的设计方式,以及您真正应该如何使用它。

此外,请勿在密码前后添加__STR。除了可能会削弱用户密码之外,您什么也没做(这不好)。如果您想了解更多关于为什么这是一个坏主意的信息:Read This Answer

继续,我强烈建议您不要直接使用crypt。实际上,搞砸并生成极弱的哈希值非常容易。这就是设计password_* api 的原因。 crypt 是一个低级库,您想在代码中使用高级库。有关如何搞砸 bcrypt 的更多信息,请查看我的博客:Seven Ways To Screw Up Bcrypt

Password API 旨在成为一个简单的一站式商店。如果它不适合您,请检查以下事项:

  1. 您使用的是 PHP >= 5.5.0 吗?还是您使用 PHP >= 5.3.7 和 password_compat

    1. 您的数据库列是否足够宽?

      它需要至少 60 个字符。

    2. 您是否在检查函数的结果是字符串,而不是bool(false)

      如果有内部错误,它会从password_hash返回一个非字符串。

    3. 你有什么错误吗?

      您是否将error_reporting 设置为最大设置(我建议-1 捕获所有内容)并检查代码没有抛出任何错误?

    4. 你确定你使用正确吗?

      function saveUser($username, $password) {
          $hash = password_hash($password, PASSWORD_BCRYPT);
          // save $username and $hash to db
      }
      function login($username, $password) {
          // fetch $hash from db
          return password_verify($password, $hash);
      }
      

      请注意,每一个都只能调用一次。

  2. 您是否使用 PHP password_compat?如果是这样,这是你的问题。您在不受支持的 PHP 版本上使用兼容性库。你可以让它工作(某些 RedHat 发行版已经向后移植了必要的修复),但你使用的是不受支持的版本。请升级到合理的版本。

如果一切都失败了,请尝试运行此代码并报告输出:

$hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
$test = crypt("password", $hash);
$pass = $test == $hash;

echo "Test for functionality of compat library: " . ($pass ? "Pass" : "Fail");
echo "\n";

如果返回Fail,您正在运行不受支持的 PHP 版本,应该升级。如果它返回通过,那么错误就在您的逻辑中(库运行正常)。

【讨论】:

  • 非常感谢@ircmaxell。我按原样尝试了您的 saveUser 和登录功能,并且有效。现在我要在我的网站上尝试这些,这将是最后的测试。我不知道为什么它以前不起作用。我尝试了 password_hash 和 password_verify 函数,但在使用它们之前我无法完成工作。希望这些不会在我的网站上工作。让我测试一下,我会回复你。谢谢
  • 它成功了。我尝试了您在第 4 点中解释的功能。上帝保佑您。干杯!!
  • @HunzaAli:很高兴听到这个消息!
  • 您好,感谢您的这些步骤,我在使用 PHP 5.5、5.6 和 5.6.18 的 IIS 服务器上遇到了同样的问题(它总是在我的许多 Apache 网站上运行),我不知道如何解决这个问题。我按照您的步骤进行操作,您的“$pass”测试似乎返回给我“失败”。你能帮帮我吗?
【解决方案2】:

存储密码的最佳方式是使用 PHP 的函数password_hash()。它会自动为每个密码生成一个加密安全的盐,并将其包含在生成的 60 个字符的字符串中。您完全不用担心盐分!

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

// 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($password, $existingHashFromDb);

您自己的方案非常弱,首先您使用 MD5,这对于生成密码哈希来说太快了,然后您使用静态盐,这违背了盐的目的。也许你想看看我关于safely storing passwords的教程。

编辑以回答更新后的问题:

没有必要将__STR 添加到密码中(如果您想添加辣椒,有更好的方法),但您的示例函数应该可以正常工作。由于随机盐,password_hash() 的返回值每次都会不同。这是正确的,函数password_verify() 能够提取此盐进行验证。在您的情况下,数据库字段可能是问题所在。确保它可以容纳 60 个字符的字符串。

【讨论】:

  • 感谢您的帮助@martinstoeckli。在你编辑帖子之前我想通了。实际上,我每次都对获取随机哈希字符串时间感到困惑。而且我不知道将如何验证。但我发现,password_verify() 函数可以解决这个问题。谢谢你的帮助。干杯!!