Rev 1102 | Rev 1104 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 1102 | Rev 1103 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * ViaThinkSoft Modular Crypt Format 1.0 / vts_password_hash() / vts_password_verify() |
4 | * ViaThinkSoft Modular Crypt Format 1.0 / vts_password_hash() / vts_password_verify() |
5 | * Copyright 2023 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2023 Daniel Marschall, ViaThinkSoft |
6 | * Revision 2023-02-27 |
6 | * Revision 2023-02-28 |
7 | * |
7 | * |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
9 | * you may not use this file except in compliance with the License. |
9 | * you may not use this file except in compliance with the License. |
10 | * You may obtain a copy of the License at |
10 | * You may obtain a copy of the License at |
11 | * |
11 | * |
Line 46... | Line 46... | ||
46 | Valid <mode> : |
46 | Valid <mode> : |
47 | sp = salt + password |
47 | sp = salt + password |
48 | ps = password + salt |
48 | ps = password + salt |
49 | sps = salt + password + salt |
49 | sps = salt + password + salt |
50 | hmac = HMAC (salt is the key) |
50 | hmac = HMAC (salt is the key) |
- | 51 | pbkdf2 = PBKDF2 (Additional param i= contains the number of iterations) |
|
51 | Like most Crypt-hashes, <salt> and <hash> are Radix64 coded |
52 | Like most Crypt-hashes, <salt> and <hash> are Radix64 coded |
52 | with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding. |
53 | with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding. |
53 | Link to the online specification: |
54 | Link to the online specification: |
54 | https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1 |
55 | https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1 |
55 | Reference implementation in PHP: |
56 | Reference implementation in PHP: |
Line 136... | Line 137... | ||
136 | } else { |
137 | } else { |
137 | return '0'; |
138 | return '0'; |
138 | } |
139 | } |
139 | } |
140 | } |
140 | 141 | ||
141 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode='ps') { |
142 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode='ps', $iterations=0/*default*/) { |
142 | if ($ver == '1') { |
143 | if ($ver == '1') { |
143 | if ($mode == 'sp') { |
144 | if ($mode == 'sp') { |
144 | $payload = $str_salt.$str_password; |
145 | $payload = $str_salt.$str_password; |
- | 146 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
145 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) { |
147 | if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
- | 148 | $bits = explode('-',$algo)[1]; |
|
146 | $bin_hash = sha3_512($payload, true); |
149 | $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true); |
147 | } else { |
150 | } else { |
148 | $bin_hash = hash($algo, $payload, true); |
151 | $bin_hash = hash($algo, $payload, true); |
149 | } |
152 | } |
150 | } else if ($mode == 'ps') { |
153 | } else if ($mode == 'ps') { |
151 | $payload = $str_password.$str_salt; |
154 | $payload = $str_password.$str_salt; |
- | 155 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
152 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) { |
156 | if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
- | 157 | $bits = explode('-',$algo)[1]; |
|
153 | $bin_hash = sha3_512($payload, true); |
158 | $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true); |
154 | } else { |
159 | } else { |
155 | $bin_hash = hash($algo, $payload, true); |
160 | $bin_hash = hash($algo, $payload, true); |
156 | } |
161 | } |
157 | } else if ($mode == 'sps') { |
162 | } else if ($mode == 'sps') { |
158 | $payload = $str_salt.$str_password.$str_salt; |
163 | $payload = $str_salt.$str_password.$str_salt; |
- | 164 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
159 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) { |
165 | if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
- | 166 | $bits = explode('-',$algo)[1]; |
|
160 | $bin_hash = sha3_512($payload, true); |
167 | $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true); |
161 | } else { |
168 | } else { |
162 | $bin_hash = hash($algo, $payload, true); |
169 | $bin_hash = hash($algo, $payload, true); |
163 | } |
170 | } |
164 | } else if ($mode == 'hmac') { |
171 | } else if ($mode == 'hmac') { |
- | 172 | if (version_compare(PHP_VERSION, '7.2.0') >= 0) { |
|
165 | // Note: Actually, we should use hash_hmac_algos(), but this requires PHP 7.2, and we would like to stay compatible with PHP 7.0 for now |
173 | $algo_supported_natively = in_array($algo, hash_hmac_algos()); |
- | 174 | } else { |
|
- | 175 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
- | 176 | } |
|
166 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512_hmac')) { |
177 | if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) { |
- | 178 | $bits = explode('-',$algo)[1]; |
|
167 | $bin_hash = sha3_512_hmac($str_password, $str_salt, true); |
179 | $bin_hash = \bb\Sha3\Sha3::hash_hmac($str_password, $str_salt, $bits, true); |
168 | } else { |
180 | } else { |
169 | $bin_hash = hash_hmac($algo, $str_password, $str_salt, true); |
181 | $bin_hash = hash_hmac($algo, $str_password, $str_salt, true); |
170 | } |
182 | } |
- | 183 | } else if ($mode == 'pbkdf2') { |
|
- | 184 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
- | 185 | if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) { |
|
- | 186 | if ($iterations == 0) { |
|
- | 187 | $iterations = 2000; // because userland implementations are much slower, we must choose a small value... |
|
- | 188 | } |
|
- | 189 | $bits = explode('-',$algo)[1]; |
|
- | 190 | $bin_hash = \bb\Sha3\Sha3::hash_pbkdf2($str_password, $str_salt, $iterations, $bits, 0, true); |
|
- | 191 | } else { |
|
- | 192 | if ($iterations == 0) { |
|
- | 193 | // Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
|
- | 194 | // I am not sure if these recommendations are correct. They write PBKDF2-HMAC-SHA1... |
|
- | 195 | // Does this count for us, or does hash_pbkdf2() implement PBKDF2-SHA1 rather than PBKDF2-HMAC-SHA1? |
|
- | 196 | if ($algo == 'sha3-512') $iterations = 100000; |
|
- | 197 | else if ($algo == 'sha3-384') $iterations = 100000; |
|
- | 198 | else if ($algo == 'sha3-256') $iterations = 100000; |
|
- | 199 | else if ($algo == 'sha3-224') $iterations = 100000; |
|
- | 200 | else if ($algo == 'sha512') $iterations = 210000; // value by owasp.org cheatcheat (28.02.2023) |
|
- | 201 | else if ($algo == 'sha512/256') $iterations = 210000; // value by owasp.org cheatcheat (28.02.2023) |
|
- | 202 | else if ($algo == 'sha512/224') $iterations = 210000; // value by owasp.org cheatcheat (28.02.2023) |
|
- | 203 | else if ($algo == 'sha384') $iterations = 600000; |
|
- | 204 | else if ($algo == 'sha256') $iterations = 600000; // value by owasp.org cheatcheat (28.02.2023) |
|
- | 205 | else if ($algo == 'sha224') $iterations = 600000; |
|
- | 206 | else if ($algo == 'sha1') $iterations = 1300000; // value by owasp.org cheatcheat (28.02.2023) |
|
- | 207 | else if ($algo == 'md5') $iterations = 5000000; |
|
- | 208 | else $iterations = 5000; |
|
- | 209 | } |
|
- | 210 | $bin_hash = hash_pbkdf2($algo, $str_password, $str_salt, $iterations, 0, true); |
|
- | 211 | } |
|
171 | } else { |
212 | } else { |
172 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac."); |
213 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
173 | } |
214 | } |
174 | $bin_salt = $str_salt; |
215 | $bin_salt = $str_salt; |
- | 216 | $params = array(); |
|
- | 217 | $params['a'] = $algo; |
|
- | 218 | $params['m'] = $mode; |
|
- | 219 | if ($mode == 'pbkdf2') $params['i'] = $iterations; |
|
175 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode)); |
220 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params); |
176 | } else { |
221 | } else { |
177 | throw new Exception("Invalid VTS crypt version, expect 1."); |
222 | throw new Exception("Invalid VTS crypt version, expect 1."); |
178 | } |
223 | } |
179 | } |
224 | } |
180 | 225 | ||
Line 186... | Line 231... | ||
186 | if ($data === false) throw new Exception('Invalid auth key'); |
231 | if ($data === false) throw new Exception('Invalid auth key'); |
187 | $id = $data['id']; |
232 | $id = $data['id']; |
188 | $bin_salt = $data['salt']; |
233 | $bin_salt = $data['salt']; |
189 | $bin_hash = $data['hash']; |
234 | $bin_hash = $data['hash']; |
190 | $params = $data['params']; |
235 | $params = $data['params']; |
- | 236 | ||
- | 237 | if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing'); |
|
191 | $algo = $params['a']; |
238 | $algo = $params['a']; |
- | 239 | ||
- | 240 | if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing'); |
|
192 | $mode = $params['m']; |
241 | $mode = $params['m']; |
193 | 242 | ||
- | 243 | if ($mode == 'pbkdf2') { |
|
- | 244 | if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing'); |
|
- | 245 | $iterations = $params['i']; |
|
- | 246 | } else { |
|
- | 247 | $iterations = 0; |
|
- | 248 | } |
|
- | 249 | ||
194 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
250 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
195 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode); |
251 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations); |
196 | 252 | ||
197 | // We rewrite the MCF to make sure that they match (if params have the wrong order) |
253 | // We rewrite the MCF to make sure that they match (if params have the wrong order) |
198 | $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params); |
254 | $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params); |
199 | 255 | ||
200 | return hash_equals($calc_authkey_1, $calc_authkey_2); |
256 | return hash_equals($calc_authkey_1, $calc_authkey_2); |
Line 270... | Line 326... | ||
270 | } else if ($algo === PASSWORD_VTS_MCF1) { |
326 | } else if ($algo === PASSWORD_VTS_MCF1) { |
271 | // Algorithms: PASSWORD_VTS_MCF1 |
327 | // Algorithms: PASSWORD_VTS_MCF1 |
272 | $ver = '1'; |
328 | $ver = '1'; |
273 | $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512'; |
329 | $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512'; |
274 | $mode = isset($options['mode']) ? $options['mode'] : 'ps'; |
330 | $mode = isset($options['mode']) ? $options['mode'] : 'ps'; |
- | 331 | $iterations = isset($options['iterations']) ? $options['iterations'] : 0/*default*/; |
|
275 | $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50; |
332 | $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50; |
276 | $salt = random_bytes_ex($salt_len, true, true); |
333 | $salt = random_bytes_ex($salt_len, true, true); |
277 | return vts_crypt_hash($algo, $password, $salt, $ver, $mode); |
334 | return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations); |
278 | } else { |
335 | } else { |
279 | // Algorithms: PASSWORD_DEFAULT |
336 | // Algorithms: PASSWORD_DEFAULT |
280 | // PASSWORD_BCRYPT |
337 | // PASSWORD_BCRYPT |
281 | // PASSWORD_ARGON2I |
338 | // PASSWORD_ARGON2I |
282 | // PASSWORD_ARGON2ID |
339 | // PASSWORD_ARGON2ID |
Line 341... | Line 398... | ||
341 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES))); |
398 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES))); |
342 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5))); |
399 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5))); |
343 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH))); |
400 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH))); |
344 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256))); |
401 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256))); |
345 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512))); |
402 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512))); |
346 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_VTS_MCF1))); |
403 | assert(vts_password_verify($password,$debug = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
- | 404 | 'algo' => 'sha3-512', |
|
- | 405 | 'mode' => 'pbkdf2', |
|
- | 406 | 'iterations' => 5000 |
|
- | 407 | )))); |
|
- | 408 | echo "$debug\n"; |
|
347 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT))); |
409 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT))); |
348 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT))); |
410 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT))); |
349 | if (defined('PASSWORD_ARGON2I')) |
411 | if (defined('PASSWORD_ARGON2I')) |
350 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I))); |
412 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I))); |
351 | if (defined('PASSWORD_ARGON2ID')) |
413 | if (defined('PASSWORD_ARGON2ID')) |