Allow selecting the hashing algorithm

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
Roeland Jago Douma 2020-01-29 21:39:58 +01:00 committed by Julius Härtl
parent 615a903870
commit 1b7239226d
No known key found for this signature in database
GPG Key ID: 4C614C6ED2CDE6DF
3 changed files with 101 additions and 13 deletions

View File

@ -1421,6 +1421,17 @@ $CONFIG = array(
*/
'tempdirectory' => '/tmp/nextcloudtemp',
/**
* Hashing
*/
/**
* By default Nextcloud will use the Argon2 password hashing if available.
* However if for whatever reason you want to stick with the PASSWORD_DEFAULT
* of your php version. Then set the setting to true.
*/
'hashing_default_password' => false,
/**
* The hashing cost used by hashes generated by Nextcloud
* Using a higher value requires more time and CPU power to calculate the hashes

View File

@ -76,11 +76,13 @@ class Hasher implements IHasher {
* @return string Hash of the message with appended version parameter
*/
public function hash(string $message): string {
if (\defined('PASSWORD_ARGON2I')) {
$alg = $this->getPrefferedAlgorithm();
if (\defined('PASSWORD_ARGON2I') && $alg === PASSWORD_ARGON2I) {
return 2 . '|' . password_hash($message, PASSWORD_ARGON2I, $this->options);
} else {
return 1 . '|' . password_hash($message, PASSWORD_BCRYPT, $this->options);
}
return 1 . '|' . password_hash($message, PASSWORD_BCRYPT, $this->options);
}
/**
@ -131,12 +133,7 @@ class Hasher implements IHasher {
*/
protected function verifyHashV1(string $message, string $hash, &$newHash = null): bool {
if(password_verify($message, $hash)) {
$algo = PASSWORD_BCRYPT;
if (\defined('PASSWORD_ARGON2I')) {
$algo = PASSWORD_ARGON2I;
}
if(password_needs_rehash($hash, $algo, $this->options)) {
if ($this->needsRehash($hash)) {
$newHash = $this->hash($message);
}
return true;
@ -154,7 +151,7 @@ class Hasher implements IHasher {
*/
protected function verifyHashV2(string $message, string $hash, &$newHash = null) : bool {
if(password_verify($message, $hash)) {
if(password_needs_rehash($hash, PASSWORD_ARGON2I, $this->options)) {
if($this->needsRehash($hash)) {
$newHash = $this->hash($message);
}
return true;
@ -183,8 +180,27 @@ class Hasher implements IHasher {
return $this->legacyHashVerify($message, $hash, $newHash);
}
return false;
}
private function needsRehash(string $hash): bool {
$algorithm = $this->getPrefferedAlgorithm();
return password_needs_rehash($hash, $algorithm, $this->options);
}
private function getPrefferedAlgorithm() {
$default = PASSWORD_BCRYPT;
if (\defined('PASSWORD_ARGON2I')) {
$default = PASSWORD_ARGON2I;
}
// Check if we should use PASSWORD_DEFAULT
if ($this->config->getSystemValue('hashing_default_password', false) === true) {
$default = PASSWORD_DEFAULT;
}
return $default;
}
}

View File

@ -126,8 +126,12 @@ class HasherTest extends \Test\TestCase {
$this->config
->expects($this->any())
->method('getSystemValue')
->with('passwordsalt', null)
->will($this->returnValue('6Wow67q1wZQZpUUeI6G2LsWUu4XKx'));
->willReturnCallback(function ($key, $default) {
if($key === 'passwordsalt') {
return '6Wow67q1wZQZpUUeI6G2LsWUu4XKx';
}
return $default;
});
$result = $this->hasher->verify($password, $hash);
$this->assertSame($expected, $result);
@ -162,4 +166,61 @@ class HasherTest extends \Test\TestCase {
$this->assertFalse(password_needs_rehash($relativePath['hash'], PASSWORD_ARGON2I, []));
}
public function testUsePasswordDefaultArgon2iVerify() {
if (!\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Need ARGON2 support to test ARGON2 hashes');
}
$this->config->method('getSystemValue')
->with('hashing_default_password')
->willReturn(true);
$message = 'mysecret';
$argon2i = 2 . '|' . password_hash($message, PASSWORD_ARGON2I, []);
$newHash = null;
$this->assertTrue($this->hasher->verify($message, $argon2i, $newHash));
$this->assertNotNull($newHash);
}
public function testDoNotUserPasswordDefaultArgon2iVerify() {
if (!\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Need ARGON2 support to test ARGON2 hashes');
}
$this->config->method('getSystemValue')
->with('hashing_default_password')
->willReturn(false);
$message = 'mysecret';
$argon2i = 2 . '|' . password_hash($message, PASSWORD_ARGON2I, []);
$newHash = null;
$this->assertTrue($this->hasher->verify($message, $argon2i, $newHash));
$this->assertNull($newHash);
}
public function testHashUsePasswordDefault() {
if (!\defined('PASSWORD_ARGON2I')) {
$this->markTestSkipped('Need ARGON2 support to test ARGON2 hashes');
}
$this->config->method('getSystemValue')
->with('hashing_default_password')
->willReturn(true);
$message = 'mysecret';
$hash = $this->hasher->hash($message);
$relativePath = self::invokePrivate($this->hasher, 'splitHash', [$hash]);
$this->assertSame(1, $relativePath['version']);
$info = password_get_info($relativePath['hash']);
$this->assertEquals(PASSWORD_BCRYPT, $info['algo']);
}
}