Rev 71 | Rev 73 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 71 | Rev 72 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * ViaThinkSoft Modular Crypt Format 1.0 and vts_password_*() functions |
4 | * ViaThinkSoft Modular Crypt Format 1.0 and vts_password_*() functions |
5 | * Copyright 2023 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2023 Daniel Marschall, ViaThinkSoft |
6 | * Revision 2023-03-02 |
6 | * Revision 2023-03-03 |
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 142... | Line 142... | ||
142 | } |
142 | } |
143 | 143 | ||
144 | $dummy = array_shift($ary); |
144 | $dummy = array_shift($ary); |
145 | $bin_hash = crypt_radix64_decode($dummy); |
145 | $bin_hash = crypt_radix64_decode($dummy); |
146 | 146 | ||
- | 147 | return array('id' => $id, |
|
- | 148 | 'salt' => $bin_salt, |
|
- | 149 | 'hash' => $bin_hash, |
|
147 | return array('id' => $id, 'salt' => $bin_salt, 'hash' => $bin_hash, 'params' => $params); |
150 | 'params' => $params); |
148 | } |
151 | } |
149 | 152 | ||
150 | // --- Part 2: ViaThinkSoft Modular Crypt Format 1.0 |
153 | // --- Part 2: ViaThinkSoft Modular Crypt Format 1.0 |
151 | 154 | ||
152 | function vts_crypt_version($hash) { |
155 | function vts_crypt_version($hash) { |
Line 155... | Line 158... | ||
155 | } else { |
158 | } else { |
156 | return '0'; |
159 | return '0'; |
157 | } |
160 | } |
158 | } |
161 | } |
159 | 162 | ||
160 | function _default_iterations($algo, $userland) { |
- | |
161 | if ($userland) { |
- | |
162 | return 100; // because the userland implementation is EXTREMELY slow, we must choose a small value, sorry... |
- | |
163 | } else { |
- | |
164 | // Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
- | |
165 | // Note that hash_pbkdf2() implements PBKDF2-HMAC-* |
- | |
166 | if ($algo == 'sha3-512') return 100000; |
- | |
167 | else if ($algo == 'sha3-384') return 100000; |
- | |
168 | else if ($algo == 'sha3-256') return 100000; |
- | |
169 | else if ($algo == 'sha3-224') return 100000; |
- | |
170 | else if ($algo == 'sha512') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
- | |
171 | else if ($algo == 'sha512/256') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
- | |
172 | else if ($algo == 'sha512/224') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
- | |
173 | else if ($algo == 'sha384') return 600000; |
- | |
174 | else if ($algo == 'sha256') return 600000; // value by owasp.org cheatcheat (28 February 2023) |
- | |
175 | else if ($algo == 'sha224') return 600000; |
- | |
176 | else if ($algo == 'sha1') return 1300000; // value by owasp.org cheatcheat (28 February 2023) |
- | |
177 | else if ($algo == 'md5') return 5000000; |
- | |
178 | else return 5000; |
- | |
179 | } |
- | |
180 | } |
- | |
181 | - | ||
182 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode=PASSWORD_VTS_MCF1_DEFAULT_MODE, $iterations=PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS) { |
163 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode=PASSWORD_VTS_MCF1_DEFAULT_MODE, $iterations=PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS) { |
183 | if ($ver == '1') { |
164 | if ($ver == '1') { |
184 | if ($mode == PASSWORD_VTS_MCF1_MODE_SP) { |
165 | if ($mode == PASSWORD_VTS_MCF1_MODE_SP) { |
185 | $payload = $str_salt.$str_password; |
166 | $payload = $str_salt.$str_password; |
186 | if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
- | |
187 | $bits = explode('-',$algo)[1]; |
- | |
188 | $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true); |
- | |
189 | } else { |
- | |
190 | $bin_hash = hash($algo, $payload, true); |
167 | $bin_hash = hash_ex($algo, $payload, true); |
191 | } |
- | |
192 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) { |
168 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) { |
193 | $payload = $str_password.$str_salt; |
169 | $payload = $str_password.$str_salt; |
194 | if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
- | |
195 | $bits = explode('-',$algo)[1]; |
- | |
196 | $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true); |
- | |
197 | } else { |
- | |
198 | $bin_hash = hash($algo, $payload, true); |
170 | $bin_hash = hash_ex($algo, $payload, true); |
199 | } |
- | |
200 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) { |
171 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) { |
201 | $payload = $str_salt.$str_password.$str_salt; |
172 | $payload = $str_salt.$str_password.$str_salt; |
202 | if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
- | |
203 | $bits = explode('-',$algo)[1]; |
- | |
204 | $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true); |
- | |
205 | } else { |
- | |
206 | $bin_hash = hash($algo, $payload, true); |
173 | $bin_hash = hash_ex($algo, $payload, true); |
207 | } |
- | |
208 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) { |
174 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) { |
209 | if (!hash_hmac_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) { |
- | |
210 | $bits = explode('-',$algo)[1]; |
- | |
211 | $bin_hash = \bb\Sha3\Sha3::hash_hmac($str_password, $str_salt, $bits, true); |
- | |
212 | } else { |
- | |
213 | $bin_hash = hash_hmac($algo, $str_password, $str_salt, true); |
175 | $bin_hash = hash_hmac_ex($algo, $str_password, $str_salt, true); |
214 | } |
- | |
215 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
176 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
216 | if (!hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) { |
- | |
217 | if ($iterations == 0/*default*/) { |
- | |
218 | $iterations = _default_iterations($algo, true); |
- | |
219 | } |
- | |
220 | $bits = explode('-',$algo)[1]; |
- | |
221 | $bin_hash = \bb\Sha3\Sha3::hash_pbkdf2($str_password, $str_salt, $iterations, $bits, 0, true); |
- | |
222 | } else { |
- | |
223 | if ($iterations == 0/*default*/) { |
- | |
224 | $iterations = _default_iterations($algo, false); |
- | |
225 | } |
- | |
226 | $bin_hash = hash_pbkdf2($algo, $str_password, $str_salt, $iterations, 0, true); |
177 | $bin_hash = hash_pbkdf2_ex($algo, $str_password, $str_salt, $iterations, 0, true); |
227 | } |
- | |
228 | } else { |
178 | } else { |
229 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
179 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
230 | } |
180 | } |
231 | $bin_salt = $str_salt; |
181 | $bin_salt = $str_salt; |
232 | $params = array(); |
182 | $params = array(); |
Line 478... | Line 428... | ||
478 | // and it is not a valid option for vts_password_get_info(). |
428 | // and it is not a valid option for vts_password_get_info(). |
479 | unset($options['salt_length']); |
429 | unset($options['salt_length']); |
480 | } |
430 | } |
481 | 431 | ||
482 | // iterations=0 means: Default, depending on the algo |
432 | // iterations=0 means: Default, depending on the algo |
483 | if (($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) && ($options['iterations'] == 0/*default*/)) { |
433 | if (($options['iterations'] == 0/*default*/) && ($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2)) { |
484 | $algo = $options2['algo']; |
434 | $algo = $options2['algo']; |
485 | $userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2'); |
435 | $userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2'); |
486 | $options['iterations'] = _default_iterations($algo, $userland); |
436 | $options['iterations'] = _vts_password_default_iterations($algo, $userland); |
487 | } |
437 | } |
488 | } |
438 | } |
489 | 439 | ||
490 | // Check if options match |
440 | // Check if options match |
491 | if (count($options) !== count($options2)) return true; |
441 | if (count($options) !== count($options2)) return true; |
Line 508... | Line 458... | ||
508 | // Hash created by vts_password_hash(), password_hash(), or crypt() |
458 | // Hash created by vts_password_hash(), password_hash(), or crypt() |
509 | return password_verify($password, $hash); |
459 | return password_verify($password, $hash); |
510 | } |
460 | } |
511 | } |
461 | } |
512 | 462 | ||
- | 463 | // --- Part 4: Functions which include a fallback to a pure-PHP sha3 implementation (requires https://github.com/danielmarschall/php-sha3 ) |
|
- | 464 | ||
- | 465 | function hash_ex($algo, $data, $binary=false, $options=array()) { |
|
- | 466 | if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
|
- | 467 | $bits = explode('-',$algo)[1]; |
|
- | 468 | $hash = \bb\Sha3\Sha3::hash($data, $bits, $binary); |
|
- | 469 | } else { |
|
- | 470 | $hash = hash($algo, $data, $binary); |
|
- | 471 | } |
|
- | 472 | return $hash; |
|
- | 473 | } |
|
- | 474 | ||
- | 475 | function hash_hmac_ex($algo, $data, $key, $binary=false) { |
|
- | 476 | if (!hash_hmac_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) { |
|
- | 477 | $bits = explode('-',$algo)[1]; |
|
- | 478 | $hash = \bb\Sha3\Sha3::hash_hmac($data, $key, $bits, $binary); |
|
- | 479 | } else { |
|
- | 480 | $hash = hash_hmac($algo, $data, $key, $binary); |
|
- | 481 | } |
|
- | 482 | return $hash; |
|
- | 483 | } |
|
- | 484 | ||
- | 485 | function hash_pbkdf2_ex($algo, $password, $salt, &$iterations=0, $length=0, $binary=false) { |
|
- | 486 | if (!hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) { |
|
- | 487 | if ($iterations == 0/*default*/) { |
|
- | 488 | $iterations = _vts_password_default_iterations($algo, true); |
|
- | 489 | } |
|
- | 490 | $bits = explode('-',$algo)[1]; |
|
- | 491 | $hash = \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, $bits, $length, $binary); |
|
- | 492 | } else { |
|
- | 493 | if ($iterations == 0/*default*/) { |
|
- | 494 | $iterations = _vts_password_default_iterations($algo, false); |
|
- | 495 | } |
|
- | 496 | $hash = hash_pbkdf2($algo, $password, $salt, $iterations, $length, $binary); |
|
- | 497 | } |
|
- | 498 | return $hash; |
|
- | 499 | } |
|
- | 500 | ||
513 | // --- Part 4: Useful functions required by the crypt-functions |
501 | // --- Part 5: Useful functions required by the crypt-functions |
514 | 502 | ||
515 | define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'); |
503 | define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'); |
516 | define('BASE64_CRYPT_ALPHABET', './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); |
504 | define('BASE64_CRYPT_ALPHABET', './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); |
517 | 505 | ||
518 | function des_compat_salt($salt_len) { |
506 | function des_compat_salt($salt_len) { |
Line 623... | Line 611... | ||
623 | } |
611 | } |
624 | } |
612 | } |
625 | return $options; |
613 | return $options; |
626 | } |
614 | } |
627 | 615 | ||
- | 616 | function _vts_password_default_iterations($algo, $userland) { |
|
- | 617 | if ($userland) { |
|
- | 618 | return 100; // because the userland implementation is EXTREMELY slow, we must choose a small value, sorry... |
|
- | 619 | } else { |
|
- | 620 | // Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
|
- | 621 | // Note that hash_pbkdf2() implements PBKDF2-HMAC-* |
|
- | 622 | if ($algo == 'sha3-512') return 100000; |
|
- | 623 | else if ($algo == 'sha3-384') return 100000; |
|
- | 624 | else if ($algo == 'sha3-256') return 100000; |
|
- | 625 | else if ($algo == 'sha3-224') return 100000; |
|
- | 626 | else if ($algo == 'sha512') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
|
- | 627 | else if ($algo == 'sha512/256') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
|
- | 628 | else if ($algo == 'sha512/224') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
|
- | 629 | else if ($algo == 'sha384') return 600000; |
|
- | 630 | else if ($algo == 'sha256') return 600000; // value by owasp.org cheatcheat (28 February 2023) |
|
- | 631 | else if ($algo == 'sha224') return 600000; |
|
- | 632 | else if ($algo == 'sha1') return 1300000; // value by owasp.org cheatcheat (28 February 2023) |
|
- | 633 | else if ($algo == 'md5') return 5000000; |
|
- | 634 | else return 5000; |
|
- | 635 | } |
|
- | 636 | } |
|
- | 637 | ||
628 | // --- Part 5: Selftest |
638 | // --- Part 6: Selftest |
629 | 639 | ||
630 | /* |
- | |
631 | for ($i=0; $i<9999; $i++) { |
640 | for ($i=0; $i<9999; $i++) { |
632 | assert($i===base64_int_decode(base64_int_encode($i,4))); |
641 | assert($i===base64_int_decode(base64_int_encode($i,4))); |
633 | } |
642 | } |
634 | 643 | ||
635 | $rnd = random_bytes_ex(50, true, true); |
644 | $rnd = random_bytes_ex(50, true, true); |
Line 680... | Line 689... | ||
680 | 'mode' => 'pbkdf2', |
689 | 'mode' => 'pbkdf2', |
681 | 'iterations' => 0 |
690 | 'iterations' => 0 |
682 | ))); |
691 | ))); |
683 | 692 | ||
684 | echo "OK, password $password\n"; |
693 | echo "OK, password $password\n"; |
685 | */ |
- |