]> source.dussan.org Git - nextcloud-server.git/commitdiff
Allow selecting the hashing algorithm 19292/head
authorRoeland Jago Douma <roeland@famdouma.nl>
Wed, 29 Jan 2020 20:39:58 +0000 (21:39 +0100)
committerBackportbot <backportbot-noreply@rullzer.com>
Tue, 4 Feb 2020 10:32:42 +0000 (10:32 +0000)
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
config/config.sample.php
lib/private/Security/Hasher.php
tests/lib/Security/HasherTest.php

index e2d0b2b54a62df247a2c157d03e95a3c46a5929d..4e83563b09e80e0f089dbf8a8bb7f9bd9976722c 100644 (file)
@@ -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
index 5f8529c7828aa1d6c94b913c7ff486c2774f0c1e..882f80ea2bfd9ae05ad4153137c369502ce190a7 100644 (file)
@@ -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;
+       }
+
 }
index 3222b5d098470a9e2295c36c6b93bca2364597fc..e680efb19b6908cbee319e73b3bc4effe657eddf 100644 (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']);
+
+       }
 }