jeremykendall/password-validator

Password Validator 验证由 password_hash 生成的密码,根据需要重新散列密码,并将升级旧密码。

3.0.4 2016-01-26 11:36 UTC

README

Password Validator 验证 password_hash 生成的密码,重新散列密码,并将升级旧密码。

阅读介绍性博客文章:PHP 密码散列:一个极其简单的实现

Password Validator 可用于所有 PHP >= 5.3.7 版本。

动机

原因?因为必须始终加密密码以确保最高安全性,而新的 PHP 密码散列 函数提供了这种级别的安全性。

Password Validator 库使得在您的应用程序中使用新的密码散列函数变得更加简单。只需将验证器添加到您的身份验证脚本中即可。

真正重要的是,从您当前的旧散列到新的更安全的 PHP 密码散列的升级非常简单。只需将 PasswordValidator 包装在 UpgradeDecorator 中,提供一个回调来验证您的现有密码散列方案,然后BOOM,您就可以以对应用程序用户完全透明的方式使用新的密码散列。酷吧?

用法

密码验证

如果您已经在应用程序中使用 password_hash 生成的密码,您无需做任何更多的事情,只需在身份验证脚本中添加验证器。验证器使用 password_verify 来测试提供的密码散列的有效性。

use JeremyKendall\Password\PasswordValidator;

$validator = new PasswordValidator();
$result = $validator->isValid($_POST['password'], $hashedPassword);

if ($result->isValid()) {
    // password is valid
}

如果您的应用程序需要除 password_hash 默认选项以外的选项,您可以使用 PasswordValidator::setOptions() 设置 cost 选项。

$options = array(
    'cost' => 11
);
$validator->setOptions($options);

重要PasswordValidator 使用默认的 10 成本。如果您的现有散列实现需要不同的成本,请确保使用 PasswordValidator::setOptions() 指定它。如果不这样做,所有密码都将使用 10 成本重新散列。

重新散列

每个有效的密码都会使用 password_needs_rehash 进行测试。如果需要重新散列,则使用提供的选项使用 password_hash 对有效密码进行散列。将返回 Result::SUCCESS_PASSWORD_REHASHED 代码,新的密码散列可通过 Result::getPassword() 获取。

if ($result->getCode() === Result::SUCCESS_PASSWORD_REHASHED) {
    $rehashedPassword = $result->getPassword();
    // Persist rehashed password
}

重要:如果密码已重新散列,则必须持久化更新的密码散列。否则,这还有什么意义,对吧?

升级旧密码

您可以使用 PasswordValidator,无论您是否目前正在使用由 password_hash 生成的密码。验证器会透明地将您当前的旧哈希升级到新的 password_hash 生成的哈希,每当用户登录时。您需要做的只是为您的密码哈希提供一个验证器回调,然后用 装饰器模式 中的 UpgradeDecorator 装饰验证器。

use JeremyKendall\Password\Decorator\UpgradeDecorator;

// Example callback to validate a sha512 hashed password
$callback = function ($password, $passwordHash, $salt) {
    if (hash('sha512', $password . $salt) === $passwordHash) {
        return true;
    }

    return false;
};

$validator = new UpgradeDecorator(new PasswordValidator(), $callback);
$result = $validator->isValid('password', 'password-hash', 'legacy-salt');

UpgradeDecorator 将使用提供的回调验证用户的当前密码。如果用户的密码有效,它将使用 password_hash 进行哈希,并在上面的 Result 对象中返回,如上所述。

所有密码验证尝试最终都将通过 PasswordValidator。这允许在使用 UpgradeDecorator 时正确验证已升级的密码。

备用升级技术

而不是在用户登录时逐个升级每个用户的密码,可以一次性预先重新哈希持久化的旧哈希。然后可以使用 PasswordValidatorUpgradeDecorator 来验证密码,此时用户的明文密码将使用 password_hash 进行哈希,完成升级过程。

有关此技术的更多信息,请参阅 Daniel Karp 的重新哈希密码哈希博客文章,并查看JeremyKendall\Password\Tests\Decorator\KarptoniteRehashUpgradeDecoratorTest以查看示例实现。

持久化重新哈希的密码

每当验证尝试返回 Result::SUCCESS_PASSWORD_REHASHED 时,都重要地保存更新的密码哈希。

if ($result->getCode() === Result::SUCCESS_PASSWORD_REHASHED) {
    $rehashedPassword = $result->getPassword();
    // Persist rehashed password
}

虽然您始终可以执行测试然后手动更新用户数据库,但如果您选择使用 Storage Decorator,则所有重新哈希的密码都将自动持久化。

Storage Decorator 接收两个构造函数参数:一个 PasswordValidatorInterface 的实例和一个 JeremyKendall\Password\Storage\StorageInterface 的实例。

StorageInterface

StorageInterface 包含一个单一的方法,updatePassword()。遵循接口的类可能看起来像这样

<?php

namespace Example;

use JeremyKendall\Password\Storage\StorageInterface;

class UserDao implements StorageInterface
{
    public function __construct(\PDO $db)
    {
        $this->db = $db;
    }

    public function updatePassword($identity, $password)
    {
        $sql = 'UPDATE users SET password = :password WHERE username = :identity';
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array('password' => $password, 'identity' => $identity));
    }
}

Storage Decorator

有了您的 UserDao,您就可以装饰一个 PasswordValidatorInterface

use Example\UserDao;
use JeremyKendall\Password\Decorator\StorageDecorator;

$storage = new UserDao($db);
$validator = new StorageDecorator(new PasswordValidator(), $storage);

// If validation results in a rehash, the new password hash will be persisted
$result = $validator->isValid('password', 'passwordHash', null, 'username');

重要:在调用 StorageDecorator::isValid() 时,您必须传递给 isValid() 的可选第四个参数($identity)。如果不这样做,StorageDecorator 将抛出 IdentityMissingException

将存储装饰器与升级装饰器结合使用

由于 装饰器模式,可以串联装饰器。利用此功能的一个好方法是将 StorageDecoratorUpgradeDecorator 结合起来,首先更新旧哈希,然后保存。这样做很简单 - 您只需将 StorageDecorator 的实例作为构造函数参数传递给 UpgradeDecorator

use Example\UserDao;
use JeremyKendall\Password\Decorator\StorageDecorator;
use JeremyKendall\Password\Decorator\UpgradeDecorator;

// Example callback to validate a sha512 hashed password
$callback = function ($password, $passwordHash, $salt) {
    if (hash('sha512', $password . $salt) === $passwordHash) {
        return true;
    }

    return false;
};

$storage = new UserDao($db);
$storageDecorator = new StorageDecorator(new PasswordValidator(), $storage);
$validator = new UpgradeDecorator($storageDecorator, $callback);

// If validation results in a rehash, the new password hash will be persisted
$result = $validator->isValid('password', 'passwordHash', null, 'username');

验证结果

每次验证尝试都会返回一个 JeremyKendall\Password\Result 对象。该对象提供了对验证过程状态的某些洞察。

  • Result::isValid() 将返回 true 如果尝试成功
  • Result::getCode() 将返回三种可能的 int 代码之一
    • Result::SUCCESS 如果验证尝试成功
    • Result::SUCCESS_PASSWORD_REHASHED 如果尝试成功并且密码已重新哈希
    • Result::FAILURE_PASSWORD_INVALID 如果尝试失败
  • Result::getPassword() 将返回重新哈希的密码,但仅当密码被重新哈希时

数据库模式更改

如上所述,因为这个库使用了PASSWORD_DEFAULT算法,所以确保密码字段是VARCHAR(255)对于未来的默认密码哈希算法更新来说非常重要。

辅助脚本

运行composer install后,有两个辅助脚本可用,都与密码哈希函数有关。

version-check

如果你还没有运行PHP 5.5+,你应该运行version-check以确保你的PHP版本能够使用password-compat,这是PHP密码哈希函数的用户空间实现。从项目的根目录运行./vendor/bin/version-check。脚本的输出结果是“通过/失败”。

cost-check

password_hash默认使用的cost是10。这可能或可能不适合你的生产硬件,并且你很可能可以使用比默认值更高的cost。cost-check基于PHP文档中的找到一个好的cost示例。只需在命令行中运行./vendor/bin/cost-check,即可返回一个合适的cost。

注意:默认时间目标是0.2秒。你可以通过传递一个浮点参数到cost-check来选择一个更高或更低的靶心,如下所示

$ ./vendor/bin/cost-check 0.4
Appropriate 'PASSWORD_DEFAULT' Cost Found:  13

安装

官方支持的唯一安装方法是使用Composer

运行以下命令将库的最新版本添加到项目中

$ composer require jeremykendall/password-validator

你可以使用此命令更新到最新版本

$ composer update jeremykendall/password-validator

如果你在项目中还没有使用Composer,请将自动加载器添加到项目中

<?php

require_once '../vendor/autoload.php'

你现在可以开始使用密码验证器了。

贡献

欢迎提交拉取请求。请在提交拉取请求之前审查CONTRIBUTING.md文档。