Rev 64 | Rev 66 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 64 | Rev 65 | ||
---|---|---|---|
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 && ($algo === 'sha3-512') && function_exists('sha3_512')) { |
146 | $bin_hash = sha3_512($payload, true); |
148 | $bin_hash = sha3_512($payload, true); |
147 | } else { |
149 | } else { |
148 | $bin_hash = hash($algo, $payload, true); |
150 | $bin_hash = hash($algo, $payload, true); |
149 | } |
151 | } |
150 | } else if ($mode == 'ps') { |
152 | } else if ($mode == 'ps') { |
151 | $payload = $str_password.$str_salt; |
153 | $payload = $str_password.$str_salt; |
- | 154 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
152 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) { |
155 | if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512')) { |
153 | $bin_hash = sha3_512($payload, true); |
156 | $bin_hash = sha3_512($payload, true); |
154 | } else { |
157 | } else { |
155 | $bin_hash = hash($algo, $payload, true); |
158 | $bin_hash = hash($algo, $payload, true); |
156 | } |
159 | } |
157 | } else if ($mode == 'sps') { |
160 | } else if ($mode == 'sps') { |
158 | $payload = $str_salt.$str_password.$str_salt; |
161 | $payload = $str_salt.$str_password.$str_salt; |
- | 162 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
159 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) { |
163 | if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512')) { |
160 | $bin_hash = sha3_512($payload, true); |
164 | $bin_hash = sha3_512($payload, true); |
161 | } else { |
165 | } else { |
162 | $bin_hash = hash($algo, $payload, true); |
166 | $bin_hash = hash($algo, $payload, true); |
163 | } |
167 | } |
164 | } else if ($mode == 'hmac') { |
168 | } else if ($mode == 'hmac') { |
- | 169 | 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 |
170 | $algo_supported_natively = in_array($algo, hash_hmac_algos()); |
- | 171 | } else { |
|
- | 172 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
- | 173 | } |
|
166 | if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512_hmac')) { |
174 | if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512_hmac')) { |
167 | $bin_hash = sha3_512_hmac($str_password, $str_salt, true); |
175 | $bin_hash = sha3_512_hmac($str_password, $str_salt, true); |
168 | } else { |
176 | } else { |
169 | $bin_hash = hash_hmac($algo, $str_password, $str_salt, true); |
177 | $bin_hash = hash_hmac($algo, $str_password, $str_salt, true); |
170 | } |
178 | } |
- | 179 | } else if ($mode == 'pbkdf2') { |
|
- | 180 | if ($iterations == 0) { |
|
- | 181 | // TODO: Find good value for iterations, see https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
|
- | 182 | $iterations = 500000; |
|
- | 183 | } |
|
- | 184 | $algo_supported_natively = in_array($algo, hash_algos()); |
|
- | 185 | if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512_pbkdf2')) { |
|
- | 186 | $bin_hash = sha3_512_pbkdf2($str_password, $str_salt, $iterations, 0, true); |
|
- | 187 | } else { |
|
- | 188 | $bin_hash = hash_pbkdf2($algo, $str_password, $str_salt, $iterations, 0, true); |
|
- | 189 | } |
|
171 | } else { |
190 | } else { |
172 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac."); |
191 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
173 | } |
192 | } |
174 | $bin_salt = $str_salt; |
193 | $bin_salt = $str_salt; |
- | 194 | $params = array(); |
|
- | 195 | $params['a'] = $algo; |
|
- | 196 | $params['m'] = $mode; |
|
- | 197 | 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)); |
198 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params); |
176 | } else { |
199 | } else { |
177 | throw new Exception("Invalid VTS crypt version, expect 1."); |
200 | throw new Exception("Invalid VTS crypt version, expect 1."); |
178 | } |
201 | } |
179 | } |
202 | } |
180 | 203 | ||
Line 186... | Line 209... | ||
186 | if ($data === false) throw new Exception('Invalid auth key'); |
209 | if ($data === false) throw new Exception('Invalid auth key'); |
187 | $id = $data['id']; |
210 | $id = $data['id']; |
188 | $bin_salt = $data['salt']; |
211 | $bin_salt = $data['salt']; |
189 | $bin_hash = $data['hash']; |
212 | $bin_hash = $data['hash']; |
190 | $params = $data['params']; |
213 | $params = $data['params']; |
- | 214 | ||
- | 215 | if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing'); |
|
191 | $algo = $params['a']; |
216 | $algo = $params['a']; |
- | 217 | ||
- | 218 | if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing'); |
|
192 | $mode = $params['m']; |
219 | $mode = $params['m']; |
193 | 220 | ||
- | 221 | if (($mode == 'pbkdf2') && !isset($params['i'])) throw new Exception('Param "i" (iterations) missing'); |
|
- | 222 | $iterations = $params['i']; |
|
- | 223 | ||
194 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
224 | // 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); |
225 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations); |
196 | 226 | ||
197 | // We rewrite the MCF to make sure that they match (if params have the wrong order) |
227 | // 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); |
228 | $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params); |
199 | 229 | ||
200 | return hash_equals($calc_authkey_1, $calc_authkey_2); |
230 | return hash_equals($calc_authkey_1, $calc_authkey_2); |
Line 270... | Line 300... | ||
270 | } else if ($algo === PASSWORD_VTS_MCF1) { |
300 | } else if ($algo === PASSWORD_VTS_MCF1) { |
271 | // Algorithms: PASSWORD_VTS_MCF1 |
301 | // Algorithms: PASSWORD_VTS_MCF1 |
272 | $ver = '1'; |
302 | $ver = '1'; |
273 | $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512'; |
303 | $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512'; |
274 | $mode = isset($options['mode']) ? $options['mode'] : 'ps'; |
304 | $mode = isset($options['mode']) ? $options['mode'] : 'ps'; |
- | 305 | $iterations = isset($options['iterations']) ? $options['iterations'] : 0/*default*/; |
|
275 | $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50; |
306 | $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50; |
276 | $salt = random_bytes_ex($salt_len, true, true); |
307 | $salt = random_bytes_ex($salt_len, true, true); |
277 | return vts_crypt_hash($algo, $password, $salt, $ver, $mode); |
308 | return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations); |
278 | } else { |
309 | } else { |
279 | // Algorithms: PASSWORD_DEFAULT |
310 | // Algorithms: PASSWORD_DEFAULT |
280 | // PASSWORD_BCRYPT |
311 | // PASSWORD_BCRYPT |
281 | // PASSWORD_ARGON2I |
312 | // PASSWORD_ARGON2I |
282 | // PASSWORD_ARGON2ID |
313 | // PASSWORD_ARGON2ID |
Line 341... | Line 372... | ||
341 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES))); |
372 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES))); |
342 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5))); |
373 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5))); |
343 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH))); |
374 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH))); |
344 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256))); |
375 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256))); |
345 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512))); |
376 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512))); |
346 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_VTS_MCF1))); |
377 | assert(vts_password_verify($password,$debug = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
- | 378 | 'algo' => 'sha3-512', |
|
- | 379 | 'mode' => 'pbkdf2', |
|
- | 380 | 'iterations' => 5000 |
|
- | 381 | )))); |
|
- | 382 | echo "$debug\n"; |
|
347 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT))); |
383 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT))); |
348 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT))); |
384 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT))); |
349 | if (defined('PASSWORD_ARGON2I')) |
385 | if (defined('PASSWORD_ARGON2I')) |
350 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I))); |
386 | assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I))); |
351 | if (defined('PASSWORD_ARGON2ID')) |
387 | if (defined('PASSWORD_ARGON2ID')) |