shoppingfeed / password
此包尚未发布任何版本,可用的信息很少。
README
此库提供了一种安全的方式来验证和存储密码。
特性
散列和验证器
接口 ShoppingFeed\Password\PasswordHashStrategyInterface
描述了散列方法的合约。有两种实现
ShoppingFeed\Password\Md5PasswordHash
:生成给定密码的 md5 摘要,没有盐(用于与旧散列兼容)ShoppingFeed\Password\StandardPasswordHash
:使用内置函数password_hash
生成密码的摘要。在 PHP <= 7.1 中,默认散列是 BCrypt。
接口 ShoppingFeed\Password\PasswordValidatorInterface
描述了验证散列的合约。有两种实现
ShoppingFeed\Password\EqualPasswordValidator
:此验证器使用散列策略获取给定密码的摘要,并与有效散列进行严格比较。ShoppingFeed\Password\StandardPasswordHash
:此类还实现了验证器。它使用内置函数password_verify
检查给定的密码是否与有效散列匹配。我们需要使用此函数,因为password_hash
每次调用都会生成一个随机的 IV,因此输出永远不会相同。
密码散列迁移
当使用的散列方法变得脆弱或损坏时(例如:md5、sha1),始终更新散列是一个好习惯。为此,每个验证器都必须实现方法 needsRehash
。例如,由于 Md5 散列脆弱,此方法始终返回 true。《StandardPasswordHash》使用内置函数 password_needs_rehash
来确定散列是否需要更新,这意味着将更新散列到新方法的责任委托给了 PHP 安全团队。
类 ShoppingFeed\Password\RehashPasswordValidator
可以装饰任何验证器,以便在验证过程中直接更新散列。如果使用旧散列方法匹配散列和密码,则调用适配器来在数据库中进行更新,适配器必须实现接口 ShoppingFeed\Password\RehashPasswordAdapterInterface
。
更新过程的示例
<?php use ShoppingFeed\Application\Password\LegacyRehashPasswordAdapter; use ShoppingFeed\Password\Md5PasswordHash; use ShoppingFeed\Password\EqualPasswordValidator; use ShoppingFeed\Password\RehashPasswordValidator; use ShoppingFeed\Password\StandardPasswordHash; $oldValidator = new EqualPasswordValidator(new Md5PasswordHash); $newMethod = new StandardPasswordHash; $adapter = new LegacyRehashPasswordAdapter; // this class belongs to the application, it can't be generic // The decorated validator uses the oldValidator to verify that the password is matching // Then is uses the new method to hash the password the given one is valid. // Finally, the new hash is passed to the adapter to update the hash in the database. $decoratedValidator = new RehashPasswordValidator($oldValidator, $newMethod, $adapter); // return false, the adapter is not called because the password is not matching the hash $decoratedValidator->validate('foo', md5('bar')); // return true, the adapter is called with a new BCrypt hash of the password "foo" // // it may be hard to update the password with nothing else than a new hash, // that's why you can pass a context array in 3rd parameter: the context is passed to the adapter. // Here, the adapter can update the password of the user "42". $decoratedValidator->validate('foo', md5('foo'), ['user' => 42]);
同时处理多种散列类型
更新过程可以帮助您将旧散列迁移到新方法,但在过程中,您可能需要同时处理多种散列类型。如果您可以识别散列类型,处理多种类型不是问题。
类 ShoppingFeed\Password\DelegatePasswordValidator
可以同时装饰多个验证器,使用比较函数来找到要使用的正确验证器。
示例
<?php // Let's imagine you use md5, sha1, sh256 and bcrypt hashes to store your passwords. use ShoppingFeed\Password\DelegatePasswordValidator; use ShoppingFeed\Password\Md5PasswordHash; use ShoppingFeed\Password\EqualPasswordValidator; use ShoppingFeed\Password\StandardPasswordHash; // We build all the validator that we need $md5Validator = new EqualPasswordValidator(new Md5PasswordHash()); $sha1Validator = new EqualPasswordValidator(new Sha1PasswordHash()); $sha256Validator = new EqualPasswordValidator(new Sha256PasswordHash()); $bcryptValidator = new StandardPasswordHash(); // Then we can build the delegate, it take one argument: // The default validator to use. It will always be used if no other one can be used. $delegateValidator = new DelegatePasswordValidator($md5Validator); $delegateValidator // The first argument is the comparison function. // Here, we want to use the bcrypt validator if the hash starts with "$", // because it is the only kind of hash with this specificity we use. ->addValidator(function (string $hash): bool { return $hash[0] === '$'; }, $bcryptValidator) // If we store a sha256 hash directly in binary, its length is 32 bytes. // Moreover, sha256 is the only method to produce a 32 bytes long hash in our hashes method. ->addValidator(function (string $hash): bool { return strlen($hash) === 32; }, $sha256Validator) // Same for sha1, it produces a 20 bytes long hash. ->addValidator(function (string $hash): bool { return strlen($hash) === 20; }, $sha1Validator) ; // In this case, the delegate will use $bcryptValidator because the given hash starts with '$' $delegateValidator->validate('foo', '$abcd'); // Here, it will use $sha256Validator because the hash is 32 bytes long $delegateValidator->validate('foo', "\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04"); // Here, it will use $sha1Validator because the hash is 20 bytes long $delegateValidator->validate('foo', "\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04"); // Here, it will use $md5Validator because no other validator matching this kind of hash (even if the given hash is not a valid md5). $delegateValidator->validate('foo', "\x01\x02\x03\x04\x01\x02\x03\x04");
结合迁移和委派
所有这些组件的目标是提供一种管理多种散列类型的方法,同时进行迁移。
您可以使用迁移功能将所有散列更新为 bcrypt,同时支持旧 md5 散列和 bcrypt,对于已更新的散列同时支持。
示例
<?php use ShoppingFeed\Application\Password\LegacyRehashPasswordAdapter; use ShoppingFeed\Password\DelegatePasswordValidator; use ShoppingFeed\Password\Md5PasswordHash; use ShoppingFeed\Password\EqualPasswordValidator; use ShoppingFeed\Password\StandardPasswordHash; use ShoppingFeed\Password\RehashPasswordValidator; // Create old and new validator $oldValidator = new EqualPasswordValidator(new Md5PasswordHash()); $newHashAndValidator = new StandardPasswordHash(); // Create the delegate to validate both md5 and bcrypt hashes $delegateValidator = new DelegatePasswordValidator($newHashAndValidator); $delegateValidator->addValidator(function ($hash) { return $hash[0] !== '$'; }, $oldValidator); // Setup the rehash validator decorator $validator = new RehashPasswordValidator($delegateValidator, $newHashAndValidator, new LegacyRehashPasswordAdapter()); // And now we have a validator that: // - verify the given password using an md5 or bcrypt hash // - always update md5 hashes to bcrypt // - may update bcrypt to another new method if the php security team set a new default method. if ($validator->validate($_GET['password'], $user['password'], ['user' => $user])) { // the password is valid and may have been updated, but we don't care in this userland code :-) } else { // the password is not valid and has not been updated. }