Skip to content
Snippets Groups Projects
Unverified Commit 0d651f10 authored by Roeland Jago Douma's avatar Roeland Jago Douma
Browse files

Allow selecting the hashing algorithm

parent 4503cff5
No related branches found
No related tags found
No related merge requests found
......@@ -1435,6 +1435,16 @@ $CONFIG = array(
/**
* 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,
/**
*
* Nextcloud uses the Argon2 algorithm (with PHP >= 7.2) to create hashes by its
* own and exposes its configuration options as following. More information can
......
......@@ -92,11 +92,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);
}
/**
......@@ -147,12 +149,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;
......@@ -170,7 +167,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;
......@@ -199,8 +196,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;
}
}
......@@ -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']);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment