├── .gitattributes ├── .gitignore ├── .travis ├── ci.ini └── swoole.install.sh ├── phpunit.xml ├── src ├── Contract │ ├── HashInterface.php │ └── DriverInterface.php ├── Driver │ ├── AbstractDriver.php │ ├── Argon2IdDriver.php │ ├── BcryptDriver.php │ └── Argon2IDriver.php ├── ConfigProvider.php ├── Hash.php └── HashManager.php ├── .travis.yml ├── LICENSE ├── composer.json ├── publish └── hashing.php ├── .php-cs-fixer.php └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | /tests export-ignore 2 | /.github export-ignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | *.cache 4 | *.log 5 | .idea/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.travis/ci.ini: -------------------------------------------------------------------------------- 1 | [opcache] 2 | opcache.enable_cli=1 3 | 4 | [swoole] 5 | extension = "swoole.so" 6 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | ./tests/ 14 | 15 | -------------------------------------------------------------------------------- /src/Contract/HashInterface.php: -------------------------------------------------------------------------------- 1 | /etc/php/${PHP_VERSION}/cli/conf.d/swoole.ini" 14 | sudo sh -c "echo swoole.use_shortname='Off' >> /etc/php/${PHP_VERSION}/cli/conf.d/swoole.ini" 15 | php --ri swoole 16 | -------------------------------------------------------------------------------- /src/Driver/AbstractDriver.php: -------------------------------------------------------------------------------- 1 | [ 21 | HashInterface::class => HashManager::class, 22 | ], 23 | 'annotations' => [ 24 | 'scan' => [ 25 | 'paths' => [ 26 | __DIR__, 27 | ], 28 | ], 29 | ], 30 | 'publish' => [ 31 | [ 32 | 'id' => 'config', 33 | 'description' => 'The config for hyperf-ext/hashing.', 34 | 'source' => __DIR__ . '/../publish/hashing.php', 35 | 'destination' => BASE_PATH . '/config/autoload/hashing.php', 36 | ], 37 | ], 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Driver/Argon2IdDriver.php: -------------------------------------------------------------------------------- 1 | verifyAlgorithm && $this->info($hashedValue)['algoName'] !== 'argon2id') { 25 | throw new RuntimeException('This password does not use the Argon2id algorithm.'); 26 | } 27 | 28 | if (strlen($hashedValue) === 0) { 29 | return false; 30 | } 31 | 32 | return password_verify($value, $hashedValue); 33 | } 34 | 35 | /** 36 | * Get the algorithm that should be used for hashing. 37 | * 38 | * @return int 39 | */ 40 | protected function algorithm() 41 | { 42 | return PASSWORD_ARGON2ID; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperf-ext/hashing", 3 | "type": "library", 4 | "license": "MIT", 5 | "keywords": [ 6 | "php", 7 | "hyperf", 8 | "hashing" 9 | ], 10 | "description": "The Hyperf Hashing package.", 11 | "authors": [ 12 | { 13 | "name": "Eric Zhu", 14 | "email": "eric@zhu.email" 15 | }, 16 | { 17 | "name": "Taylor Otwell", 18 | "email": "taylor@laravel.com" 19 | } 20 | ], 21 | "autoload": { 22 | "psr-4": { 23 | "HyperfExt\\Hashing\\": "src/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "HyperfTest\\": "tests" 29 | } 30 | }, 31 | "require": { 32 | "php": ">=8.1", 33 | "hyperf/config": "^3.1", 34 | "hyperf/di": "^3.1", 35 | "hyperf/framework": "^3.1" 36 | }, 37 | "require-dev": { 38 | "friendsofphp/php-cs-fixer": "^3.1", 39 | "hyperf/testing": "^3.1", 40 | "phpstan/phpstan": "^0.12", 41 | "swoole/ide-helper": "dev-master" 42 | }, 43 | "config": { 44 | "sort-packages": true 45 | }, 46 | "scripts": { 47 | "test": "phpunit --prepend tests/bootstrap.php -c phpunit.xml --colors=always", 48 | "analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src", 49 | "cs-fix": "php-cs-fixer fix $1" 50 | }, 51 | "extra": { 52 | "hyperf": { 53 | "config": "HyperfExt\\Hashing\\ConfigProvider" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Hash.php: -------------------------------------------------------------------------------- 1 | get(HashInterface::class)->getDriver($name); 22 | } 23 | 24 | public static function info(string $hashedValue, ?string $driverName = null): array 25 | { 26 | return static::getDriver($driverName)->info($hashedValue); 27 | } 28 | 29 | public static function make(string $value, array $options = [], ?string $driverName = null): string 30 | { 31 | return static::getDriver($driverName)->make($value, $options); 32 | } 33 | 34 | public static function check(string $value, string $hashedValue, array $options = [], ?string $driverName = null): bool 35 | { 36 | return static::getDriver($driverName)->check($value, $hashedValue, $options); 37 | } 38 | 39 | public static function needsRehash(string $hashedValue, array $options = [], ?string $driverName = null): bool 40 | { 41 | return static::getDriver($driverName)->needsRehash($hashedValue, $options); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /publish/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 24 | 25 | 'driver' => [ 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Bcrypt Options 29 | |-------------------------------------------------------------------------- 30 | | 31 | | Here you may specify the configuration options that should be used when 32 | | passwords are hashed using the Bcrypt algorithm. This will allow you 33 | | to control the amount of time it takes to hash the given password. 34 | | 35 | */ 36 | 37 | 'bcrypt' => [ 38 | 'class' => \HyperfExt\Hashing\Driver\BcryptDriver::class, 39 | 'options' => [ 40 | 'rounds' => env('BCRYPT_ROUNDS', 10), 41 | ], 42 | ], 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Argon Options 47 | |-------------------------------------------------------------------------- 48 | | 49 | | Here you may specify the configuration options that should be used when 50 | | passwords are hashed using the Argon algorithm. These will allow you 51 | | to control the amount of time it takes to hash the given password. 52 | | 53 | */ 54 | 55 | 'argon2i' => [ 56 | 'class' => \HyperfExt\Hashing\Driver\Argon2IDriver::class, 57 | 'options' => [ 58 | 'memory' => 1024, 59 | 'threads' => 2, 60 | 'time' => 2, 61 | ], 62 | ], 63 | 64 | 'argon2id' => [ 65 | 'class' => \HyperfExt\Hashing\Driver\Argon2IdDriver::class, 66 | 'options' => [ 67 | 'memory' => 1024, 68 | 'threads' => 2, 69 | 'time' => 2, 70 | ], 71 | ], 72 | ], 73 | ]; 74 | -------------------------------------------------------------------------------- /src/HashManager.php: -------------------------------------------------------------------------------- 1 | config = $config; 38 | } 39 | 40 | /** 41 | * Get information about the given hashed value. 42 | */ 43 | public function info(string $hashedValue): array 44 | { 45 | return $this->getDriver()->info($hashedValue); 46 | } 47 | 48 | /** 49 | * Hash the given value. 50 | */ 51 | public function make(string $value, array $options = []): string 52 | { 53 | return $this->getDriver()->make($value, $options); 54 | } 55 | 56 | /** 57 | * Check the given plain value against a hash. 58 | */ 59 | public function check(string $value, string $hashedValue, array $options = []): bool 60 | { 61 | return $this->getDriver()->check($value, $hashedValue, $options); 62 | } 63 | 64 | /** 65 | * Check if the given hash has been hashed using the given options. 66 | */ 67 | public function needsRehash(string $hashedValue, array $options = []): bool 68 | { 69 | return $this->getDriver()->needsRehash($hashedValue, $options); 70 | } 71 | 72 | /** 73 | * Get a driver instance. 74 | * 75 | * @throws \InvalidArgumentException 76 | */ 77 | public function getDriver(?string $name = null): DriverInterface 78 | { 79 | if (isset($this->drivers[$name]) && $this->drivers[$name] instanceof DriverInterface) { 80 | return $this->drivers[$name]; 81 | } 82 | 83 | $name = $name ?: $this->config->get('hashing.default', 'bcrypt'); 84 | 85 | $config = $this->config->get("hashing.driver.{$name}"); 86 | if (empty($config) or empty($config['class'])) { 87 | throw new InvalidArgumentException(sprintf('The hashing driver config %s is invalid.', $name)); 88 | } 89 | 90 | $driverClass = $config['class'] ?? BcryptDriver::class; 91 | 92 | $driver = make($driverClass, ['options' => $config['options'] ?? []]); 93 | 94 | return $this->drivers[$name] = $driver; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true) 13 | ->setRules([ 14 | '@PSR2' => true, 15 | '@Symfony' => true, 16 | '@DoctrineAnnotation' => true, 17 | '@PhpCsFixer' => true, 18 | 'header_comment' => [ 19 | 'comment_type' => 'PHPDoc', 20 | 'header' => $header, 21 | 'separate' => 'none', 22 | 'location' => 'after_declare_strict', 23 | ], 24 | 'array_syntax' => [ 25 | 'syntax' => 'short' 26 | ], 27 | 'list_syntax' => [ 28 | 'syntax' => 'short' 29 | ], 30 | 'concat_space' => [ 31 | 'spacing' => 'one' 32 | ], 33 | 'blank_line_before_statement' => [ 34 | 'statements' => [ 35 | 'declare', 36 | ], 37 | ], 38 | 'general_phpdoc_annotation_remove' => [ 39 | 'annotations' => [ 40 | 'author' 41 | ], 42 | ], 43 | 'ordered_imports' => [ 44 | 'imports_order' => [ 45 | 'class', 'function', 'const', 46 | ], 47 | 'sort_algorithm' => 'alpha', 48 | ], 49 | 'single_line_comment_style' => [ 50 | 'comment_types' => [ 51 | ], 52 | ], 53 | 'yoda_style' => [ 54 | 'always_move_variable' => false, 55 | 'equal' => false, 56 | 'identical' => false, 57 | ], 58 | 'phpdoc_align' => [ 59 | 'align' => 'left', 60 | ], 61 | 'multiline_whitespace_before_semicolons' => [ 62 | 'strategy' => 'no_multi_line', 63 | ], 64 | 'constant_case' => [ 65 | 'case' => 'lower', 66 | ], 67 | 'class_attributes_separation' => true, 68 | 'combine_consecutive_unsets' => true, 69 | 'declare_strict_types' => true, 70 | 'linebreak_after_opening_tag' => true, 71 | 'lowercase_static_reference' => true, 72 | 'no_useless_else' => true, 73 | 'no_unused_imports' => true, 74 | 'not_operator_with_successor_space' => true, 75 | 'not_operator_with_space' => false, 76 | 'ordered_class_elements' => true, 77 | 'php_unit_strict' => false, 78 | 'phpdoc_separation' => false, 79 | 'single_quote' => true, 80 | 'standardize_not_equals' => true, 81 | 'multiline_comment_opening_closing' => true, 82 | ]) 83 | ->setFinder( 84 | PhpCsFixer\Finder::create() 85 | ->exclude('bin') 86 | ->exclude('public') 87 | ->exclude('runtime') 88 | ->exclude('vendor') 89 | ->in(__DIR__) 90 | ) 91 | ->setUsingCache(false); 92 | -------------------------------------------------------------------------------- /src/Driver/BcryptDriver.php: -------------------------------------------------------------------------------- 1 | rounds = $options['rounds'] ?? $this->rounds; 38 | $this->verifyAlgorithm = $options['verify'] ?? $this->verifyAlgorithm; 39 | } 40 | 41 | /** 42 | * Hash the given value. 43 | * 44 | * @throws \RuntimeException 45 | */ 46 | public function make(string $value, array $options = []): string 47 | { 48 | $hash = password_hash($value, PASSWORD_BCRYPT, [ 49 | 'cost' => $this->cost($options), 50 | ]); 51 | 52 | if ($hash === false) { 53 | throw new RuntimeException('Bcrypt hashing not supported.'); 54 | } 55 | 56 | return $hash; 57 | } 58 | 59 | /** 60 | * Check the given plain value against a hash. 61 | * 62 | * @throws \RuntimeException 63 | */ 64 | public function check(string $value, string $hashedValue, array $options = []): bool 65 | { 66 | if ($this->verifyAlgorithm && $this->info($hashedValue)['algoName'] !== 'bcrypt') { 67 | throw new RuntimeException('This password does not use the Bcrypt algorithm.'); 68 | } 69 | 70 | return parent::check($value, $hashedValue, $options); 71 | } 72 | 73 | /** 74 | * Check if the given hash has been hashed using the given options. 75 | */ 76 | public function needsRehash(string $hashedValue, array $options = []): bool 77 | { 78 | return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, [ 79 | 'cost' => $this->cost($options), 80 | ]); 81 | } 82 | 83 | /** 84 | * Set the default password work factor. 85 | * 86 | * @return $this 87 | */ 88 | public function setRounds(int $rounds): self 89 | { 90 | $this->rounds = $rounds; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Extract the cost value from the options array. 97 | */ 98 | protected function cost(array $options = []): int 99 | { 100 | return $options['rounds'] ?? $this->rounds; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperf 哈希组件 2 | 3 | 该组件为存储用户密码提供了安全的 Bcrypt 和 Argon2 哈希加密方式。 4 | 5 | > 移植自 [illuminate/hashing](https://github.com/illuminate/hashing )。 6 | 7 | ## 安装 8 | 9 | ```shell script 10 | composer require hyperf-ext/hashing 11 | ``` 12 | 13 | ## 发布配置 14 | 15 | ```shell script 16 | php bin/hyperf.php vendor:publish hyperf-ext/hashing 17 | ``` 18 | 19 | > 配置文件位于 `config/autoload/hashing.php`。 20 | 21 | ## 默认配置 22 | 23 | ```php 24 | 'bcrypt', 30 | 'driver' => [ 31 | 'bcrypt' => [ 32 | 'class' => \HyperfExt\Hashing\Driver\BcryptDriver::class, 33 | 'rounds' => env('BCRYPT_ROUNDS', 10), 34 | ], 35 | 'argon' => [ 36 | 'class' => \HyperfExt\Hashing\Driver\Argon2IDriver::class, 37 | 'memory' => 1024, 38 | 'threads' => 2, 39 | 'time' => 2, 40 | ], 41 | 'argon2id' => [ 42 | 'class' => \HyperfExt\Hashing\Driver\Argon2IdDriver::class, 43 | 'memory' => 1024, 44 | 'threads' => 2, 45 | 'time' => 2, 46 | ], 47 | ], 48 | ]; 49 | ``` 50 | 51 | 你可以在 `config/autoload/hashing.php` 配置文件中配置默认哈希驱动程序。目前支持三种驱动程序: Bcrypt 和 Argon2(Argon2i 和 Argon2id variants)。 52 | 53 | > 注意:Argon2i 驱动程序需要 PHP 7.2.0 或更高版本,而 Argon2id 驱动程序则需要 PHP 7.3.0 或更高版本。 54 | 55 | ## 使用 56 | 57 | 你可以通过 `\HyperfExt\Hashing\Hash` 类来加密你的密码: 58 | 59 | ```php 60 | fill([ 76 | 'password' => Hash::make($request->input('new_password')) 77 | ])->save(); 78 | } 79 | } 80 | ``` 81 | 82 | ### 调整 Bcrypt 加密系数 83 | 84 | 如果使用 Bcrypt 算法,你可以在 `make` 方法中使用 `rounds` 选项来配置该算法的加密系数。然而,对大多数应用程序来说,默认值就足够了: 85 | 86 | ```php 87 | $hashed = Hash::make('password', [ 88 | 'rounds' => 12 89 | ]); 90 | ``` 91 | 92 | ### 调整 Argon2 加密系数 93 | 94 | 如果使用 Argon2 算法,你可以在 `make` 方法中使用 `memory`,`time` 和 `threads` 选项来配置该算法的加密系数。然后,对大多数应用程序来说,默认值就足够了: 95 | 96 | ```php 97 | $hashed = Hash::make('password', [ 98 | 'memory' => 1024, 99 | 'time' => 2, 100 | 'threads' => 2, 101 | ]); 102 | ``` 103 | 104 | > 有关这些选项的更多信息,请查阅 [PHP 官方文档](https://secure.php.net/manual/en/function.password-hash.php )。 105 | 106 | ### 密码哈希验证 107 | 108 | `check` 方法能为您验证一段给定的未加密字符串与给定的哈希值是否一致: 109 | 110 | ```php 111 | if (Hash::check('plain-text', $hashedPassword)) { 112 | // 密码匹配… 113 | } 114 | ``` 115 | 116 | ### 检查密码是否需要重新哈希 117 | 118 | `needsRehash` 方法可以为您检查当哈希的加密系数改变时,您的密码是否被新的加密系数重新加密过: 119 | 120 | ```php 121 | if (Hash::needsRehash($hashed)) { 122 | $hashed = Hash::make('plain-text'); 123 | } 124 | ``` 125 | 126 | ### 使用指定驱动 127 | 128 | ```php 129 | $hasher = Hash::getDriver('argon2i'); 130 | $hasher->make('plain-text'); 131 | ``` 132 | 133 | ### 使用自定义哈希类 134 | 135 | 实现 `\HyperfExt\Hashing\Contract\DriverInterface` 接口,并参照配置文件中的其他算法进行配置。 136 | -------------------------------------------------------------------------------- /src/Driver/Argon2IDriver.php: -------------------------------------------------------------------------------- 1 | time = $options['time'] ?? $this->time; 52 | $this->memory = $options['memory'] ?? $this->memory; 53 | $this->threads = $options['threads'] ?? $this->threads; 54 | $this->verifyAlgorithm = $options['verify'] ?? $this->verifyAlgorithm; 55 | } 56 | 57 | /** 58 | * Hash the given value. 59 | * 60 | * @throws \RuntimeException 61 | */ 62 | public function make(string $value, array $options = []): string 63 | { 64 | $hash = password_hash($value, $this->algorithm(), [ 65 | 'memory_cost' => $this->memory($options), 66 | 'time_cost' => $this->time($options), 67 | 'threads' => $this->threads($options), 68 | ]); 69 | 70 | if ($hash === false) { 71 | throw new RuntimeException('Argon2 hashing not supported.'); 72 | } 73 | 74 | return $hash; 75 | } 76 | 77 | /** 78 | * Check the given plain value against a hash. 79 | * 80 | * @throws \RuntimeException 81 | */ 82 | public function check(string $value, string $hashedValue, array $options = []): bool 83 | { 84 | if ($this->verifyAlgorithm && $this->info($hashedValue)['algoName'] !== 'argon2i') { 85 | throw new RuntimeException('This password does not use the Argon2i algorithm.'); 86 | } 87 | 88 | return parent::check($value, $hashedValue, $options); 89 | } 90 | 91 | /** 92 | * Check if the given hash has been hashed using the given options. 93 | */ 94 | public function needsRehash(string $hashedValue, array $options = []): bool 95 | { 96 | return password_needs_rehash($hashedValue, $this->algorithm(), [ 97 | 'memory_cost' => $this->memory($options), 98 | 'time_cost' => $this->time($options), 99 | 'threads' => $this->threads($options), 100 | ]); 101 | } 102 | 103 | /** 104 | * Set the default password memory factor. 105 | * 106 | * @return $this 107 | */ 108 | public function setMemory(int $memory): self 109 | { 110 | $this->memory = $memory; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * Set the default password timing factor. 117 | * 118 | * @return $this 119 | */ 120 | public function setTime(int $time): self 121 | { 122 | $this->time = $time; 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * Set the default password threads factor. 129 | * 130 | * @return $this 131 | */ 132 | public function setThreads(int $threads): self 133 | { 134 | $this->threads = $threads; 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * Get the algorithm that should be used for hashing. 141 | * 142 | * @return int 143 | */ 144 | protected function algorithm() 145 | { 146 | return PASSWORD_ARGON2I; 147 | } 148 | 149 | /** 150 | * Extract the memory cost value from the options array. 151 | */ 152 | protected function memory(array $options): int 153 | { 154 | return $options['memory'] ?? $this->memory; 155 | } 156 | 157 | /** 158 | * Extract the time cost value from the options array. 159 | */ 160 | protected function time(array $options): int 161 | { 162 | return $options['time'] ?? $this->time; 163 | } 164 | 165 | /** 166 | * Extract the threads value from the options array. 167 | */ 168 | protected function threads(array $options): int 169 | { 170 | return $options['threads'] ?? $this->threads; 171 | } 172 | } 173 | --------------------------------------------------------------------------------