密码散列 SELECT (PHP)

Password Hashing SELECT (PHP)

是否可以仅使用发布的密码从 MySql 数据库中 select 散列和加盐密码?如果是,怎么做?

如果我这样散列密码:

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

$hash 将是 yzzd3lj6oIPlBPnCxsU7nOmtsEFlKw/BdqTXyMgbuojjVpiEe4rVm,它将存储在数据库中。

在登录过程中,如果密码匹配,我如何只检查散列密码列,并且只检查 table 的列,只有 'blabla' 作为数据?

我想你的意思是如何从数据库中 select 散列和加盐的密码,然后用明文密码验证它?如果是这样,这里是使用 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)
);

哈希 Class (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);
    }
}

数据库Class (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);
    }
}

用户 Class (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>

How, during a login, do I check against the hashed password column only, and only table's column, if the passwords match, having only 'blabla' as data?

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

由于密码不需要是唯一的,您可能有一个密码可以匹配多个条目。

我猜这是个坏主意,不是您想要的。

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

1) 首先查询存储的散列

SELECT passwordhash FROM users WHERE email = ?

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

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

是盐使搜索无法进行,必须先从存储的散列中提取盐,然后才能验证密码。这样的查询必须读取每个散列,提取其盐分并进行散列。因为散列函数非常慢(故意),查询将需要太长的时间才能执行。