aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoeland Jago Douma <roeland@famdouma.nl>2020-01-29 21:39:58 +0100
committerBackportbot <backportbot-noreply@rullzer.com>2020-02-04 10:32:42 +0000
commit9687febed7fba22ef749e9530d531101bb64e07f (patch)
tree3724176c2a85f7b52203756bc1609f479d449502
parent9bd432255800fa8566b04f3e7351c254d757ec9c (diff)
downloadnextcloud-server-9687febed7fba22ef749e9530d531101bb64e07f.tar.gz
nextcloud-server-9687febed7fba22ef749e9530d531101bb64e07f.zip
Allow selecting the hashing algorithm
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
-rw-r--r--config/config.sample.php10
-rw-r--r--lib/private/Security/Hasher.php38
-rw-r--r--tests/lib/Security/HasherTest.php65
3 files changed, 100 insertions, 13 deletions
diff --git a/config/config.sample.php b/config/config.sample.php
index e2d0b2b54a6..4e83563b09e 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -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
diff --git a/lib/private/Security/Hasher.php b/lib/private/Security/Hasher.php
index 5f8529c7828..882f80ea2bf 100644
--- a/lib/private/Security/Hasher.php
+++ b/lib/private/Security/Hasher.php
@@ -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;
+ }
+
}
diff --git a/tests/lib/Security/HasherTest.php b/tests/lib/Security/HasherTest.php
index 3222b5d0984..e680efb19b6 100644
--- a/tests/lib/Security/HasherTest.php
+++ b/tests/lib/Security/HasherTest.php
@@ -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']);
+
+ }
}