Passed
Push — master ( fa914f...8d5404 )
by Roeland
28:10 queued 13:49
created
lib/private/Security/Hasher.php 1 patch
Indentation   +155 added lines, -155 removed lines patch added patch discarded remove patch
@@ -52,159 +52,159 @@
 block discarded – undo
52 52
  * @package OC\Security
53 53
  */
54 54
 class Hasher implements IHasher {
55
-	/** @var IConfig */
56
-	private $config;
57
-	/** @var array Options passed to password_hash and password_needs_rehash */
58
-	private $options = [];
59
-	/** @var string Salt used for legacy passwords */
60
-	private $legacySalt = null;
61
-
62
-	/**
63
-	 * @param IConfig $config
64
-	 */
65
-	public function __construct(IConfig $config) {
66
-		$this->config = $config;
67
-
68
-		if (\defined('PASSWORD_ARGON2I')) {
69
-			// password_hash fails, when the minimum values are undershot.
70
-			// In this case, apply minimum.
71
-			$this->options['threads'] = max($this->config->getSystemValueInt('hashingThreads', PASSWORD_ARGON2_DEFAULT_THREADS), 1);
72
-			// The minimum memory cost is 8 KiB per thread.
73
-			$this->options['memory_cost'] = max($this->config->getSystemValueInt('hashingMemoryCost', PASSWORD_ARGON2_DEFAULT_MEMORY_COST), $this->options['threads'] * 8);
74
-			$this->options['time_cost'] = max($this->config->getSystemValueInt('hashingTimeCost', PASSWORD_ARGON2_DEFAULT_TIME_COST), 1);
75
-		}
76
-
77
-		$hashingCost = $this->config->getSystemValue('hashingCost', null);
78
-		if (!\is_null($hashingCost)) {
79
-			$this->options['cost'] = $hashingCost;
80
-		}
81
-	}
82
-
83
-	/**
84
-	 * Hashes a message using PHP's `password_hash` functionality.
85
-	 * Please note that the size of the returned string is not guaranteed
86
-	 * and can be up to 255 characters.
87
-	 *
88
-	 * @param string $message Message to generate hash from
89
-	 * @return string Hash of the message with appended version parameter
90
-	 */
91
-	public function hash(string $message): string {
92
-		$alg = $this->getPrefferedAlgorithm();
93
-
94
-		if (\defined('PASSWORD_ARGON2ID') && $alg === PASSWORD_ARGON2ID) {
95
-			return 3 . '|' . password_hash($message, PASSWORD_ARGON2ID, $this->options);
96
-		}
97
-
98
-		if (\defined('PASSWORD_ARGON2I') && $alg === PASSWORD_ARGON2I) {
99
-			return 2 . '|' . password_hash($message, PASSWORD_ARGON2I, $this->options);
100
-		}
101
-
102
-		return 1 . '|' . password_hash($message, PASSWORD_BCRYPT, $this->options);
103
-	}
104
-
105
-	/**
106
-	 * Get the version and hash from a prefixedHash
107
-	 * @param string $prefixedHash
108
-	 * @return null|array Null if the hash is not prefixed, otherwise array('version' => 1, 'hash' => 'foo')
109
-	 */
110
-	protected function splitHash(string $prefixedHash) {
111
-		$explodedString = explode('|', $prefixedHash, 2);
112
-		if (\count($explodedString) === 2) {
113
-			if ((int)$explodedString[0] > 0) {
114
-				return ['version' => (int)$explodedString[0], 'hash' => $explodedString[1]];
115
-			}
116
-		}
117
-
118
-		return null;
119
-	}
120
-
121
-	/**
122
-	 * Verify legacy hashes
123
-	 * @param string $message Message to verify
124
-	 * @param string $hash Assumed hash of the message
125
-	 * @param null|string &$newHash Reference will contain the updated hash
126
-	 * @return bool Whether $hash is a valid hash of $message
127
-	 */
128
-	protected function legacyHashVerify($message, $hash, &$newHash = null): bool {
129
-		if (empty($this->legacySalt)) {
130
-			$this->legacySalt = $this->config->getSystemValue('passwordsalt', '');
131
-		}
132
-
133
-		// Verify whether it matches a legacy PHPass or SHA1 string
134
-		$hashLength = \strlen($hash);
135
-		if (($hashLength === 60 && password_verify($message.$this->legacySalt, $hash)) ||
136
-			($hashLength === 40 && hash_equals($hash, sha1($message)))) {
137
-			$newHash = $this->hash($message);
138
-			return true;
139
-		}
140
-
141
-		return false;
142
-	}
143
-
144
-	/**
145
-	 * Verify V1 (blowfish) hashes
146
-	 * Verify V2 (argon2i) hashes
147
-	 * Verify V3 (argon2id) hashes
148
-	 * @param string $message Message to verify
149
-	 * @param string $hash Assumed hash of the message
150
-	 * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one.
151
-	 * @return bool Whether $hash is a valid hash of $message
152
-	 */
153
-	protected function verifyHash(string $message, string $hash, &$newHash = null): bool {
154
-		if (password_verify($message, $hash)) {
155
-			if ($this->needsRehash($hash)) {
156
-				$newHash = $this->hash($message);
157
-			}
158
-			return true;
159
-		}
160
-
161
-		return false;
162
-	}
163
-
164
-	/**
165
-	 * @param string $message Message to verify
166
-	 * @param string $hash Assumed hash of the message
167
-	 * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one.
168
-	 * @return bool Whether $hash is a valid hash of $message
169
-	 */
170
-	public function verify(string $message, string $hash, &$newHash = null): bool {
171
-		$splittedHash = $this->splitHash($hash);
172
-
173
-		if (isset($splittedHash['version'])) {
174
-			switch ($splittedHash['version']) {
175
-				case 3:
176
-				case 2:
177
-				case 1:
178
-					return $this->verifyHash($message, $splittedHash['hash'], $newHash);
179
-			}
180
-		} else {
181
-			return $this->legacyHashVerify($message, $hash, $newHash);
182
-		}
183
-
184
-		return false;
185
-	}
186
-
187
-	private function needsRehash(string $hash): bool {
188
-		$algorithm = $this->getPrefferedAlgorithm();
189
-
190
-		return password_needs_rehash($hash, $algorithm, $this->options);
191
-	}
192
-
193
-	private function getPrefferedAlgorithm() {
194
-		$default = PASSWORD_BCRYPT;
195
-		if (\defined('PASSWORD_ARGON2I')) {
196
-			$default = PASSWORD_ARGON2I;
197
-		}
198
-
199
-		if (\defined('PASSWORD_ARGON2ID')) {
200
-			$default = PASSWORD_ARGON2ID;
201
-		}
202
-
203
-		// Check if we should use PASSWORD_DEFAULT
204
-		if ($this->config->getSystemValue('hashing_default_password', false) === true) {
205
-			$default = PASSWORD_DEFAULT;
206
-		}
207
-
208
-		return $default;
209
-	}
55
+    /** @var IConfig */
56
+    private $config;
57
+    /** @var array Options passed to password_hash and password_needs_rehash */
58
+    private $options = [];
59
+    /** @var string Salt used for legacy passwords */
60
+    private $legacySalt = null;
61
+
62
+    /**
63
+     * @param IConfig $config
64
+     */
65
+    public function __construct(IConfig $config) {
66
+        $this->config = $config;
67
+
68
+        if (\defined('PASSWORD_ARGON2I')) {
69
+            // password_hash fails, when the minimum values are undershot.
70
+            // In this case, apply minimum.
71
+            $this->options['threads'] = max($this->config->getSystemValueInt('hashingThreads', PASSWORD_ARGON2_DEFAULT_THREADS), 1);
72
+            // The minimum memory cost is 8 KiB per thread.
73
+            $this->options['memory_cost'] = max($this->config->getSystemValueInt('hashingMemoryCost', PASSWORD_ARGON2_DEFAULT_MEMORY_COST), $this->options['threads'] * 8);
74
+            $this->options['time_cost'] = max($this->config->getSystemValueInt('hashingTimeCost', PASSWORD_ARGON2_DEFAULT_TIME_COST), 1);
75
+        }
76
+
77
+        $hashingCost = $this->config->getSystemValue('hashingCost', null);
78
+        if (!\is_null($hashingCost)) {
79
+            $this->options['cost'] = $hashingCost;
80
+        }
81
+    }
82
+
83
+    /**
84
+     * Hashes a message using PHP's `password_hash` functionality.
85
+     * Please note that the size of the returned string is not guaranteed
86
+     * and can be up to 255 characters.
87
+     *
88
+     * @param string $message Message to generate hash from
89
+     * @return string Hash of the message with appended version parameter
90
+     */
91
+    public function hash(string $message): string {
92
+        $alg = $this->getPrefferedAlgorithm();
93
+
94
+        if (\defined('PASSWORD_ARGON2ID') && $alg === PASSWORD_ARGON2ID) {
95
+            return 3 . '|' . password_hash($message, PASSWORD_ARGON2ID, $this->options);
96
+        }
97
+
98
+        if (\defined('PASSWORD_ARGON2I') && $alg === PASSWORD_ARGON2I) {
99
+            return 2 . '|' . password_hash($message, PASSWORD_ARGON2I, $this->options);
100
+        }
101
+
102
+        return 1 . '|' . password_hash($message, PASSWORD_BCRYPT, $this->options);
103
+    }
104
+
105
+    /**
106
+     * Get the version and hash from a prefixedHash
107
+     * @param string $prefixedHash
108
+     * @return null|array Null if the hash is not prefixed, otherwise array('version' => 1, 'hash' => 'foo')
109
+     */
110
+    protected function splitHash(string $prefixedHash) {
111
+        $explodedString = explode('|', $prefixedHash, 2);
112
+        if (\count($explodedString) === 2) {
113
+            if ((int)$explodedString[0] > 0) {
114
+                return ['version' => (int)$explodedString[0], 'hash' => $explodedString[1]];
115
+            }
116
+        }
117
+
118
+        return null;
119
+    }
120
+
121
+    /**
122
+     * Verify legacy hashes
123
+     * @param string $message Message to verify
124
+     * @param string $hash Assumed hash of the message
125
+     * @param null|string &$newHash Reference will contain the updated hash
126
+     * @return bool Whether $hash is a valid hash of $message
127
+     */
128
+    protected function legacyHashVerify($message, $hash, &$newHash = null): bool {
129
+        if (empty($this->legacySalt)) {
130
+            $this->legacySalt = $this->config->getSystemValue('passwordsalt', '');
131
+        }
132
+
133
+        // Verify whether it matches a legacy PHPass or SHA1 string
134
+        $hashLength = \strlen($hash);
135
+        if (($hashLength === 60 && password_verify($message.$this->legacySalt, $hash)) ||
136
+            ($hashLength === 40 && hash_equals($hash, sha1($message)))) {
137
+            $newHash = $this->hash($message);
138
+            return true;
139
+        }
140
+
141
+        return false;
142
+    }
143
+
144
+    /**
145
+     * Verify V1 (blowfish) hashes
146
+     * Verify V2 (argon2i) hashes
147
+     * Verify V3 (argon2id) hashes
148
+     * @param string $message Message to verify
149
+     * @param string $hash Assumed hash of the message
150
+     * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one.
151
+     * @return bool Whether $hash is a valid hash of $message
152
+     */
153
+    protected function verifyHash(string $message, string $hash, &$newHash = null): bool {
154
+        if (password_verify($message, $hash)) {
155
+            if ($this->needsRehash($hash)) {
156
+                $newHash = $this->hash($message);
157
+            }
158
+            return true;
159
+        }
160
+
161
+        return false;
162
+    }
163
+
164
+    /**
165
+     * @param string $message Message to verify
166
+     * @param string $hash Assumed hash of the message
167
+     * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one.
168
+     * @return bool Whether $hash is a valid hash of $message
169
+     */
170
+    public function verify(string $message, string $hash, &$newHash = null): bool {
171
+        $splittedHash = $this->splitHash($hash);
172
+
173
+        if (isset($splittedHash['version'])) {
174
+            switch ($splittedHash['version']) {
175
+                case 3:
176
+                case 2:
177
+                case 1:
178
+                    return $this->verifyHash($message, $splittedHash['hash'], $newHash);
179
+            }
180
+        } else {
181
+            return $this->legacyHashVerify($message, $hash, $newHash);
182
+        }
183
+
184
+        return false;
185
+    }
186
+
187
+    private function needsRehash(string $hash): bool {
188
+        $algorithm = $this->getPrefferedAlgorithm();
189
+
190
+        return password_needs_rehash($hash, $algorithm, $this->options);
191
+    }
192
+
193
+    private function getPrefferedAlgorithm() {
194
+        $default = PASSWORD_BCRYPT;
195
+        if (\defined('PASSWORD_ARGON2I')) {
196
+            $default = PASSWORD_ARGON2I;
197
+        }
198
+
199
+        if (\defined('PASSWORD_ARGON2ID')) {
200
+            $default = PASSWORD_ARGON2ID;
201
+        }
202
+
203
+        // Check if we should use PASSWORD_DEFAULT
204
+        if ($this->config->getSystemValue('hashing_default_password', false) === true) {
205
+            $default = PASSWORD_DEFAULT;
206
+        }
207
+
208
+        return $default;
209
+    }
210 210
 }
Please login to merge, or discard this patch.