diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 558df255e230..14fc68965f03 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -1111,7 +1111,7 @@ public function registerCoreContainerAliases() 'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class], 'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class], 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], - 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], + 'hash' => [\Illuminate\Hashing\HashManager::class], 'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class], 'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index f3cbd85364df..b5e6b3a5bc45 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -185,7 +185,7 @@ function base_path($path = '') if (! function_exists('bcrypt')) { /** - * Hash the given value. + * Hash the given value against the bcrypt algorithm. * * @param string $value * @param array $options @@ -193,7 +193,9 @@ function base_path($path = '') */ function bcrypt($value, $options = []) { - return app('hash')->make($value, $options); + return app('hash') + ->driver('bcrypt') + ->make($value, $options); } } diff --git a/src/Illuminate/Hashing/ArgonHasher.php b/src/Illuminate/Hashing/ArgonHasher.php new file mode 100644 index 000000000000..9e7e1ba9bd15 --- /dev/null +++ b/src/Illuminate/Hashing/ArgonHasher.php @@ -0,0 +1,160 @@ + $this->memory($options), + 'time_cost' => $this->time($options), + 'threads' => $this->processors($options) + ]); + + if ($hash === false) { + throw new RuntimeException('Argon2 hashing not supported.'); + } + + return $hash; + } + + /** + * Check the given plain value against a hash. + * + * @param string $value + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function check($value, $hashedValue, array $options = []) + { + if (strlen($hashedValue) === 0) { + return false; + } + + return password_verify($value, $hashedValue); + } + + /** + * Check if the given hash has been hashed using the given options. + * + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function needsRehash($hashedValue, array $options = []) + { + return password_needs_rehash($hashedValue, PASSWORD_ARGON2I, [ + 'memory_cost' => $this->memory($options), + 'time_cost' => $this->time($options), + 'threads' => $this->processors($options) + ]); + } + + /** + * Set the default password threads factor. + * + * @param int $threads + * + * @return $this; + */ + public function setProcessors(int $threads) + { + $this->threads = $threads; + + return $this; + } + + /** + * Set the default password memory factor. + * + * @param int $memory + * + * @return $this + */ + public function setMemory(int $memory) + { + $this->memory = $memory; + + return $this; + } + + /** + * Set the default password timing factor. + * + * @param int $time + * + * @return $this + */ + public function setTime(int $time) + { + $this->time = $time; + + return $this; + } + + /** + * Extract the memory cost value from the options array. + * + * @param $options + * @return int + */ + protected function memory($options) + { + return $options['memory'] ?? $this->memory; + } + + /** + * Extract the time cost value from the options array. + * + * @param $options + * @return int + */ + protected function time($options) + { + return $options['time'] ?? $this->time; + } + + /** + * Extract the threads value from the options array. + * + * @param $options + * @return int + */ + protected function processors($options) + { + return $options['processors'] ?? $this->processors; + } +} \ No newline at end of file diff --git a/src/Illuminate/Hashing/HashManager.php b/src/Illuminate/Hashing/HashManager.php new file mode 100644 index 000000000000..f0b56c6e8d80 --- /dev/null +++ b/src/Illuminate/Hashing/HashManager.php @@ -0,0 +1,38 @@ +app['config']['hashing.driver']; + } + + /** + * Create an instance of the Brycrypt hash Driver. + * + * @return BcryptHasher + */ + public function createBcryptDriver() + { + return new BcryptHasher; + } + + /** + * Create an instance of the Argon2 hash Driver. + * + * @return ArgonHasher + */ + public function createArgonDriver() + { + return new ArgonHasher; + } +} \ No newline at end of file diff --git a/src/Illuminate/Hashing/HashServiceProvider.php b/src/Illuminate/Hashing/HashServiceProvider.php index 85581f40cf65..5b8d525463d9 100755 --- a/src/Illuminate/Hashing/HashServiceProvider.php +++ b/src/Illuminate/Hashing/HashServiceProvider.php @@ -20,8 +20,8 @@ class HashServiceProvider extends ServiceProvider */ public function register() { - $this->app->singleton('hash', function () { - return new BcryptHasher; + $this->app->singleton('hash', function ($app) { + return new HashManager($app); }); } diff --git a/tests/Hashing/BcryptHasherTest.php b/tests/Hashing/BcryptHasherTest.php deleted file mode 100755 index e223b71dc4a7..000000000000 --- a/tests/Hashing/BcryptHasherTest.php +++ /dev/null @@ -1,18 +0,0 @@ -make('password'); - $this->assertNotSame('password', $value); - $this->assertTrue($hasher->check('password', $value)); - $this->assertFalse($hasher->needsRehash($value)); - $this->assertTrue($hasher->needsRehash($value, ['rounds' => 1])); - } -} diff --git a/tests/Hashing/HasherTest.php b/tests/Hashing/HasherTest.php new file mode 100755 index 000000000000..1ac9d34089d6 --- /dev/null +++ b/tests/Hashing/HasherTest.php @@ -0,0 +1,32 @@ +make('password'); + $this->assertNotSame('password', $value); + $this->assertTrue($hasher->check('password', $value)); + $this->assertFalse($hasher->needsRehash($value)); + $this->assertTrue($hasher->needsRehash($value, ['rounds' => 1])); + } + + public function testBasicArgonHashing() + { + if (! defined('PASSWORD_ARGON2I')) { + $this->markTestSkipped('PHP not compiled with argon2 hashing support support.'); + } + + $hasher = new \Illuminate\Hashing\ArgonHasher; + $value = $hasher->make('password'); + $this->assertNotSame('password', $value); + $this->assertTrue($hasher->check('password', $value)); + $this->assertFalse($hasher->needsRehash($value)); + $this->assertTrue($hasher->needsRehash($value, ['processors' => 1])); + } +} diff --git a/tests/Integration/Auth/AuthenticationTest.php b/tests/Integration/Auth/AuthenticationTest.php index 7faea9f38a84..475a8c5bb84e 100644 --- a/tests/Integration/Auth/AuthenticationTest.php +++ b/tests/Integration/Auth/AuthenticationTest.php @@ -24,6 +24,8 @@ protected function getEnvironmentSetUp($app) 'database' => ':memory:', 'prefix' => '', ]); + + $app['config']->set('hashing', ['driver' => 'bcrypt']); } public function setUp() diff --git a/tests/Integration/Http/ThrottleRequestsTest.php b/tests/Integration/Http/ThrottleRequestsTest.php index 824427842e34..45c5b7e9c420 100644 --- a/tests/Integration/Http/ThrottleRequestsTest.php +++ b/tests/Integration/Http/ThrottleRequestsTest.php @@ -19,6 +19,11 @@ public function tearDown() Carbon::setTestNow(null); } + public function getEnvironmentSetUp($app) + { + $app['config']->set('hashing', ['driver' => 'bcrypt']); + } + public function test_lock_opens_immediately_after_decay() { Carbon::setTestNow(null); diff --git a/tests/Integration/Http/ThrottleRequestsWithRedisTest.php b/tests/Integration/Http/ThrottleRequestsWithRedisTest.php index e4e11277110d..123e0f06a010 100644 --- a/tests/Integration/Http/ThrottleRequestsWithRedisTest.php +++ b/tests/Integration/Http/ThrottleRequestsWithRedisTest.php @@ -22,6 +22,11 @@ public function tearDown() Carbon::setTestNow(null); } + public function getEnvironmentSetUp($app) + { + $app['config']->set('hashing', ['driver' => 'bcrypt']); + } + public function test_lock_opens_immediately_after_decay() { $this->ifRedisAvailable(function () {