【问题标题】:Password Hashing SELECT (PHP)密码散列选择 (PHP)
【发布时间】:2015-10-27 09:32:35
【问题描述】:

是否可以仅使用发布的密码从 MySql DB 中选择散列和加盐密码?如果有,怎么做?

如果我这样散列密码:

    $password = "blabla";
    $hash = password_hash($password, PASSWORD_DEFAULT);

$hash 将是,例如,$2y$10$8zzd3lj6oIPlBPnCxsU7nOmtsEFlKw/BdqTXyMgbuojjVpiEe4rVm,它将存储在数据库中。

如何在登录期间仅检查哈希密码列,并且仅检查表的列,如果密码匹配,则只有“blabla”作为数据?

【问题讨论】:

  • 当然,只需使用存储在数据库中的所有盐对密码进行哈希处理,并将其与存储的哈希值进行比较;这将在 O(#users) 中运行——如果每个用户都有自己的盐——这可能没问题。但是你为什么要做这样的事情呢?
  • 欢迎。你是 PH​​P 编程还是别的什么?
  • 如果您可以访问散列方法和盐,并且您可以选择访问数据库。有很多先决条件,你没有在你的问题中提供很多细节。 PHP 是关键组件吗?是 MySQL 吗?您只有客户端访问权限还是同时拥有服务器访问权限?
  • 您的意思是作为服务器登录过程的正常部分?为什么你需要知道$password 是什么?

标签: hash mysql salt


【解决方案1】:

我想你的意思是如何从数据库中选择一个散列和加盐的密码,然后用明文密码验证它?如果是这样,这就是 bcrypt 的方法。

请记住,这需要 PHP 5 >= 5.5.0。

另外,我推荐scrypt 而不是 bcrypt,但你必须手动安装 scrypt。

SQL 的东西

CREATE DATABASE `example`;
USE `example`;
CREATE TABLE `users` (
    `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `username` VARCHAR(16),
    `password` VARCHAR(255)
);

哈希类(classes/Hash.class.php)

<?php
class Hash
{
    public static function make($string)
    {
        $options = array('cost' => 11);
        return password_hash($string, PASSWORD_BCRYPT, $options)
    }

    public static function check($password, $hash)
    {
        return password_verify($password, $hash);
    }
}

数据库类(classes/DB.class.php)

<?php
class DB
{
    private $dbhost = '127.0.0.1';
    private $dbname = 'example';
    private $dbuser = 'root';
    private $dbpass = 'pass';

    public function Connect()
    {
        return new PDO('mysql:host=' . $this->dbhost . ';dbname=' . $this->dbname, $this->dbuser, $this->pass);
    }
}

用户类(classes/User.class.php)

<?php
require_once('DB.class.php');
require_once('Hash.class.php');

class User
{
    private $db;

    public function __construct()
    {
        $this->db = new DB();
        $this->db = $this->db->Connect();
    }

    public function find($username)
    {
        $st = $this->db->prepare('SELECT * FROM `users` WHERE `username` = :username LIMIT 1');
        $st->bindParam(':username', $username, PDO::PARAM_STR);
        $st->execute();

        if($st->rowCount())
        {
            return $st->fetch(PDO::FETCH_ASSOC);
        }

        return false;
    }

    public function create($username, $password)
    {
        $password = Hash::make($password);

        $st = $this->db->prepare('INSERT INTO `users` (`username`, `password`) VALUES (:username, :password)');
        $st->bindParam(':username', $username, PDO::PARAM_STR);
        $st->bindParam(':password', $password, PDO::PARAM_STR);
        $st->execute();
    }

    public function verify($username, $password)
    {
        $user = $this->find($username);

        if($user)
        {
            if(Hash::check($password, $user['password']))
            {
                $_SESSION['isLoggedIn'] = true;
                return true;
            }
        }

        return false;
    }

    public function isLoggedIn()
    {
        if(isset($_SESSION['isLoggedIn']))
        {
            return true;
        }

        return false;
    }
}

注册(register.php)

<?php
require_once('classes/User.class.php');

$user = new User();

if($user->isLoggedIn())
{
    header('Location: index.php');
    die();
}

if($_SERVER['REQUEST_METHOD'] == 'POST')
{
    $username = $_POST['username'];
    $password = $_POST['password'];

    // Check if username and password exist
    if(!isset($username) || !isset($password))
    {
        die('Username and password required');
    }

    // Check if values are not empty
    if(empty($username) || empty($password))
    {
        die('Blank fields not allowed');
    }


    // Check if username length is in between 4 and 16
    if(strlen($username) < 4 && strlen($username) > 16)
    {
        die('Username must be in between 4 and 16 characters');
    }

    // Check if username is alphanumeric
    if(!ctype_alnum($username))
    {
        die('Username must be alphanumeric');
    }

    // Check password length
    if(strlen($password) < 8)
    {
        die('Passwords should be at least 8 characters long');
    }

    // Check if username exists
    $exists = $user->find($username);

    if($exists)
    {
        die('Username already in use');
    }

    // Create account
    $user->create($username, $password);
    header('Location: login.php');
    die();

}
?>

// HTML goes here

登录(login.php)

<?php
require_once('classes/User.class.php');

$user = new User();

if($user->isLoggedIn())
{
    header('Location: index.php');
    die();
}

if($_SERVER['REQUEST_METHOD'] == 'POST')
{
    $username = $_POST['username'];
    $password = $_POST['password'];

    // Check if username and password exist
    if(!isset($username) || !isset($password))
    {
        die('Username and password required');
    }

    // Check if values are not empty
    if(empty($username) || empty($password))
    {
        die('Blank fields not allowed');
    }


    // Check if username length is in between 4 and 16
    if(strlen($username) < 4 && strlen($username) > 16)
    {
        die('Username must be in between 4 and 16 characters');
    }

    // Check if username is alphanumeric
    if(!ctype_alnum($username))
    {
        die('Username must be alphanumeric');
    }

    // Check password length
    if(strlen($password) < 8)
    {
        die('Passwords should be at least 8 characters long');
    }

    // Try to login
    $verified = $user->verify($username, $password);

    if($verified)
    {
        header('Location: index.php');
        die();
    } else {
        die('Invalid username/password');
    }
}
?>

// HTML goes here

注销 (logout.php)

<?php
require_once('classes/User.class.php');

$user = new User();

if($user->isLoggedIn())
{
    unset($_SESSION['isLoggedIn']);
}

header('Location: login.php');
die();

索引(index.php)

<?php
require_once('classes/User.class.php');

if(!$user->isLoggedIn())
{
    header('Location: login.php');
    die();
}
?>

<!DOCTYPE html>

<html lang="en">

<head>

    <title>Welcome</title>

</head>

<body>

    <h1>Menu</h1>
    <ul>
        <li><a href="logout.php">Logout?</a></li>
    </ul>

</body>

</html>

【讨论】:

  • @Simon 我没有说 bcrypt 有什么问题,我说“我推荐 scrypt 而不是 bcrypt,但你必须手动安装 scrypt。”
  • 好吧,我改一下。为什么你会推荐 scrypt 而不是 bcrypt?
  • @Simon 虽然 bcrypt 比 scrypt 更老,但 scrypt 允许您设置它将使用的内存空间量,而 bcrypt 没有。另见this
  • 小题大做,Hash 类中的check 函数可以简化为return password_verify($password, $hash);
【解决方案2】:

如何在登录期间仅检查散列密码列,并且仅检查表的列,如果密码匹配,只有“blabla”作为数据?

你不能。密码存储旨在使许多操作变得不可能。如果您想在不使用用户名或其他密钥的情况下查找密码匹配项,则需要在每个密码上调用password_verify,直到找到匹配项。按照设计,这将非常缓慢。

因为密码不需要是唯一的,您可能有一个与许多条目匹配的密码。

我的猜测是这是一个坏主意,而不是你想要的。

【讨论】:

    【解决方案3】:

    无法使用数据库查询搜索正确加盐和散列的密码。您必须通过用户名/电子邮件/...搜索哈希值,然后您可以使用找到的哈希值验证输入的密码。

    1) 首先查询存储的哈希

    SELECT passwordhash FROM users WHERE email = ?
    

    2) 使用找到的哈希验证输入的密码

    $isPasswordCorrect = password_verify($password, $existingHashFromDb);
    

    盐使搜索变得不可能,它必须从存储的哈希中提取出来你可以验证密码。这样的查询必须读取每个散列,提取其盐并进行散列。因为哈希函数非常慢(故意),所以查询需要很长时间才能执行。

    【讨论】:

      猜你喜欢
      • 2015-08-13
      • 2012-05-30
      • 2017-05-15
      • 2011-04-06
      • 2018-07-29
      • 1970-01-01
      • 2017-06-26
      • 2014-09-09
      • 1970-01-01
      相关资源
      最近更新 更多