jeremykendall / password-validator
Password Validator 验证由 password_hash 生成的密码,根据需要重新散列密码,并将升级旧密码。
Requires
- php: >=5.3.7
- ircmaxell/password-compat: 1.*
- wp-cli/php-cli-tools: 0.10.*
Requires (Dev)
This package is auto-updated.
Last update: 2024-08-25 07:00:00 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
时正确验证已升级的密码。
备用升级技术
而不是在用户登录时逐个升级每个用户的密码,可以一次性预先重新哈希持久化的旧哈希。然后可以使用 PasswordValidator
和 UpgradeDecorator
来验证密码,此时用户的明文密码将使用 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
。
将存储装饰器与升级装饰器结合使用
由于 装饰器模式,可以串联装饰器。利用此功能的一个好方法是将 StorageDecorator
和 UpgradeDecorator
结合起来,首先更新旧哈希,然后保存。这样做很简单 - 您只需将 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文档。