【问题标题】:When do I need to use password_needs_rehash()?什么时候需要使用 password_needs_rehash()?
【发布时间】:2017-06-17 13:42:10
【问题描述】:

我有一个用于登录应用程序的处理文件。我要么不明白password_needs_rehash() 的目的,要么它不起作用。登录正在验证并将我传递到正确的页面。但我什至无法让代码回显新的哈希值。

我这样做对吗?

if 不会因为不需要重新散列而引发新散列吗?如果是这样,如果密码被正确散列并存储在数据库中,什么时候需要重新散列?

我的处理文件如下:

$email = $_POST["li_email"];

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    // set the PDO error mode to exception
    $stmt = $conn->query("SELECT * FROM users WHERE email='$email'");
    $stmt->execute();
    while($row=$stmt->fetch()){ //for each result, do the following
        $hash = $row['hash'];
        $userPassword = $_POST["li_password"];
        if (password_verify($userPassword, $hash)) {
            if ( password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 12]) ) {
                $newhash = password_hash($userPassword, PASSWORD_DEFAULT, ['cost' => 12]);
                echo $newhash;
            }
        } else {
            header('Location: http://' . $_SERVER['HTTP_HOST'] . '?error=loginfailed');
            exit();
        }
    }
} catch(PDOException $e) {
    echo $sql . "<br>" . $e->getMessage();
}

$conn = null;

感谢您的帮助!

【问题讨论】:

  • 如果您更改cost,则需要重新哈希。
  • 我明白了。 NOOB 问题:我为什么要更改成本?如果我这样做,它不会在user 第一次登录后重新散列,因为新散列的成本会改变?还是我想随机化成本?
  • cost 越多,将原始密码转换为可用于验证密码的哈希所需的 CPU 时间就越多。成本越高,“破解”它就越困难。那你为什么要改变它?好吧,也许当 CPU 速度提高时,或者您转移到多个 CPU 执行相同操作并且负载平衡的云中。无论哪种方式,10 都足够了。
  • 好的,所以它是一个面向未来的代码?就像它从现在开始的 2 年一样,CPU 变得更快了,这意味着我设置的成本不太安全,现在我提高了与时俱进的成本?
  • 差不多,是的。不需要更改代码或任何东西,只需在登录时验证它是否需要重新哈希并自行决定更改成本。

标签: php login php-password-hash


【解决方案1】:

函数password_needs_rehash()只有在你改变$options时才需要使用,$options通常是指cost

成本越高,哈希密码所需的 CPU 时间越多,但破解密码就越困难。如果您更改主机或迁移到基于云的系统,多台计算机可以为您计算哈希,您可以自行决定增加它。

您只需要在用户登录时检查密码是否需要重新哈希,因为如果您更改了$optionspassword_verify() 仍然可以验证密码。如果此时 password_needs_rehash() 返回 true,请使用 password_hash() 和新选项并替换旧哈希。

try {
  $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
  $stmt = $conn->prepare($sql = "SELECT * FROM users WHERE email=?"); // otherwise $sql is not defined in catch()

  if($stmt->execute([$_POST["li_email"]])){
    // preventing sql injection

    if(($data = $stmt->fetch()) != false){
      // only expecting 1 row from db since an email should be unique.

      if(password_verify($_POST["li_email"], $data['hash'])){
        // valid login

        if(password_needs_rehash($data['hash'], PASSWORD_DEFAULT, $options = ['cost' => 12])){
          $newhash = password_hash($_POST["li_email"], PASSWORD_DEFAULT, options);
          // store new hash in db.
        }
      } else {
        header('Location: http://' . $_SERVER['HTTP_HOST'] . '?error=loginfailed');
        exit();
      }
    } else {
      // no results
    }
  } else {
    // query failed
  }
} catch(PDOException $e) {
    echo $sql . "<br>" . $e->getMessage();
}

【讨论】:

  • 你可能想检查我的答案,只是出于好奇
  • 感谢 @YourCommonSense 的编辑,我似乎一直忘记 execute() 只接受值数组而不是来自我的 API 的 ...$packed 参数列表。是的,您的代码确实看起来更干净,没有明显的错误检查。只是添加它来添加一些“意义”。
【解决方案2】:

仅作记录,上述代码经过合理重构的版本,删除了一些漏洞和不一致之处。

$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

$stmt = $conn->prepare("SELECT * FROM users WHERE email=?");
$stmt->execute([$_POST["li_email"]]);
$data = $stmt->fetch();

if($data && password_verify($_POST["li_password"], $data['hash'])) {
    if(password_needs_rehash($data['hash'], PASSWORD_DEFAULT, $options = ['cost' => 12])){
        $newhash = password_hash($_POST["li_password"], PASSWORD_DEFAULT, options);
        // store new hash in db.
    }
} else {
    header('Location: http://' . $_SERVER['HTTP_HOST'] . '?error=loginfailed');
    exit();
}

一些关键点:

  • 无需使用用户名和密码的不同错误消息提示黑客。
  • 应严格避免数据库结构泄漏
  • 看到带有错误消息的 SQL 没有意义,因为它已经在代码中了
  • 如果执行失败,则为系统错误,应予以相应处理

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-16
    • 1970-01-01
    • 2015-12-02
    • 2012-10-29
    • 2013-03-17
    • 2012-08-03
    • 1970-01-01
    相关资源
    最近更新 更多