Skip to content

Commit

Permalink
Added Argon2 hash support
Browse files Browse the repository at this point in the history
  • Loading branch information
BenExile committed Oct 4, 2017
1 parent d610151 commit 6d96332
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ sudo: false
cache:
directories:
- $HOME/.composer/cache
- $HOME/libsodium

services:
- memcached
Expand All @@ -31,6 +32,11 @@ before_install:
- phpenv config-rm xdebug.ini || true
- echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- printf "\n" | pecl install -f redis
- sudo apt-get install -y software-properties-common
- sudo LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php
- sudo apt-get update
- sudo apt-get install -y libsodium-dev
- pecl install -f libsodium
- travis_retry composer self-update

install:
Expand Down
85 changes: 85 additions & 0 deletions src/Illuminate/Hashing/Argon2Hasher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Illuminate\Hashing;

use RuntimeException;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;

class Argon2Hasher implements HasherContract
{
/**
* Hash the given value.
*
* @param string $value
* @param array $options
* @return string
*
* @throws \RuntimeException
*/
public function make($value, array $options = []): string
{
if (extension_loaded('sodium')) {
return sodium_crypto_pwhash_str(
$value,
$options['time_cost'] ?? SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
$options['memory_cost'] ?? SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
}

throw new RuntimeException('Argon2i hashing not supported.');
}

/**
* Check a plain text value against a hashed value.
*
* @param string $value
* @param string $hashedValue
* @param array $options
* @return bool
*
* @throws \RuntimeException
*/
public function check($value, $hashedValue, array $options = []): bool
{
if (extension_loaded('sodium')) {
$valid = sodium_crypto_pwhash_str_verify($hashedValue, $value);
sodium_memzero($value);
return $valid;
}

throw new RuntimeException('Argon2i hashing not supported.');
}

/**
* Check if the given hash has been hashed using the given options.
*
* @param string $hashedValue
* @param array $options
* @return bool
*
* @throws \RuntimeException
*/
public function needsRehash($hashedValue, array $options = []): bool
{
// Extract options from the hashed value
list($memoryCost, $timeCost) = sscanf($hashedValue, '$%*[argon2id]$v=%*ld$m=%d,t=%d');
$hashOptions = ['memory_cost' => $memoryCost, 'time_cost' => $timeCost];

// Filter unknown options from the options array
$options = array_filter($options, function ($key) use ($hashOptions) {
return isset($hashOptions[$key]);
}, ARRAY_FILTER_USE_KEY);

return ! empty(array_diff_assoc($options, $hashOptions));
}

/**
* Determine if the system supports Argon2i hashing.
*
* @return bool
*/
public function isSupported(): bool
{
return extension_loaded('sodium');
}
}
9 changes: 8 additions & 1 deletion src/Illuminate/Hashing/HashServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ class HashServiceProvider extends ServiceProvider
public function register()
{
$this->app->singleton('hash', function () {
return new BcryptHasher;
switch (config('hash.algorithm')) {
case 'argon2':
case 'argon2i':
return new Argon2Hasher;
case 'bcrypt':
default:
return new BcryptHasher;
}
});
}

Expand Down
46 changes: 46 additions & 0 deletions tests/Hashing/Argon2HasherTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Illuminate\Tests\Hashing;

use PHPUnit\Framework\TestCase;
use Illuminate\Hashing\Argon2Hasher;

class Argon2HasherTest extends TestCase
{
const PLAINTEXT_PASSWORD = 'password';

public function setUp()
{
if (! (new Argon2Hasher)->isSupported()) {
$this->markTestSkipped('Argon2i hashing not supported.');
}
}

public function testHashPassword()
{
$hasher = new Argon2Hasher;
$hashedPassword = $hasher->make(self::PLAINTEXT_PASSWORD);

$this->assertNotSame(self::PLAINTEXT_PASSWORD, $hashedPassword);
$this->assertStringStartsWith(SODIUM_CRYPTO_PWHASH_STRPREFIX, $hashedPassword);
}

public function testVerifyPassword()
{
$hasher = new Argon2Hasher;
$hashedPassword = $hasher->make(self::PLAINTEXT_PASSWORD);

$this->assertTrue($hasher->check(self::PLAINTEXT_PASSWORD, $hashedPassword));
$this->assertFalse($hasher->check(strrev(self::PLAINTEXT_PASSWORD), $hashedPassword));
}

public function testNeedsRehash()
{
$hasher = new Argon2Hasher;
$hashedPassword = $hasher->make(self::PLAINTEXT_PASSWORD);

$this->assertFalse($hasher->needsRehash($hashedPassword));
$this->assertTrue($hasher->needsRehash($hashedPassword, ['time_cost' => 1]));
$this->assertTrue($hasher->needsRehash($hashedPassword, ['memory_cost' => 1]));
}
}

0 comments on commit 6d96332

Please sign in to comment.