Rev 73 | Rev 75 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 73 | Rev 74 | ||
---|---|---|---|
Line 27... | Line 27... | ||
27 | The function vts_password_verify() replaces password_verify(). |
27 | The function vts_password_verify() replaces password_verify(). |
28 | 28 | ||
29 | ViaThinkSoft Modular Crypt Format 1.0 performs a simple hash or HMAC operation. |
29 | ViaThinkSoft Modular Crypt Format 1.0 performs a simple hash or HMAC operation. |
30 | No key derivation function or iterations are performed. |
30 | No key derivation function or iterations are performed. |
31 | Format: |
31 | Format: |
32 | $1.3.6.1.4.1.37476.3.0.1.1$a=<algo>,m=<mode>$<salt>$<hash> |
32 | $1.3.6.1.4.1.37476.3.0.1.1$a=<algo>,m=<mode>[,i=<iterations>]$<salt>$<hash> |
33 | where <algo> is any valid hash algorithm (name scheme of PHP hash_algos() preferred), e.g. |
33 | where <algo> is any valid hash algorithm (name scheme of PHP hash_algos() preferred), e.g. |
34 | sha3-512 |
34 | sha3-512 |
35 | sha3-384 |
35 | sha3-384 |
36 | sha3-256 |
36 | sha3-256 |
37 | sha3-224 |
37 | sha3-224 |
Line 51... | Line 51... | ||
51 | sp = salt + password |
51 | sp = salt + password |
52 | ps = password + salt |
52 | ps = password + salt |
53 | sps = salt + password + salt |
53 | sps = salt + password + salt |
54 | hmac = HMAC (salt is the key) |
54 | hmac = HMAC (salt is the key) |
55 | pbkdf2 = PBKDF2-HMAC (Additional param i= contains the number of iterations) |
55 | pbkdf2 = PBKDF2-HMAC (Additional param i= contains the number of iterations) |
- | 56 | <iterations> can be omitted if 0. It is required for mode=pbkdf2. For sp/ps/sps/hmac, it is optional. |
|
56 | Like most Crypt-hashes, <salt> and <hash> are Radix64 coded |
57 | Like most Crypt-hashes, <salt> and <hash> are Radix64 coded |
57 | with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding. |
58 | with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding. |
58 | Link to the online specification: |
59 | Link to the online specification: |
59 | https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1 |
60 | https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1 |
60 | Reference implementation in PHP: |
61 | Reference implementation in PHP: |
Line 90... | Line 91... | ||
90 | define('PASSWORD_BLOWFISH_DEFAULT_COST', 10); |
91 | define('PASSWORD_BLOWFISH_DEFAULT_COST', 10); |
91 | define('PASSWORD_SHA256_DEFAULT_ROUNDS', 5000); |
92 | define('PASSWORD_SHA256_DEFAULT_ROUNDS', 5000); |
92 | define('PASSWORD_SHA512_DEFAULT_ROUNDS', 5000); |
93 | define('PASSWORD_SHA512_DEFAULT_ROUNDS', 5000); |
93 | define('PASSWORD_VTS_MCF1_DEFAULT_ALGO', 'sha3-512'); // any value in hash_algos(), NOT vts_hash_algos() |
94 | define('PASSWORD_VTS_MCF1_DEFAULT_ALGO', 'sha3-512'); // any value in hash_algos(), NOT vts_hash_algos() |
94 | define('PASSWORD_VTS_MCF1_DEFAULT_MODE', PASSWORD_VTS_MCF1_MODE_PS); |
95 | define('PASSWORD_VTS_MCF1_DEFAULT_MODE', PASSWORD_VTS_MCF1_MODE_PS); |
95 | define('PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS', 0); // only for mode=pbkdf2. 0=Default, depending on algo |
96 | define('PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS', 0); // For PBKDF2, iterations=0 means: Default, depending on the algo |
96 | 97 | ||
97 | // --- Part 1: Modular Crypt Format encode/decode |
98 | // --- Part 1: Modular Crypt Format encode/decode |
98 | 99 | ||
99 | function crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params=null) { |
100 | function crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params=null) { |
100 | // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
101 | // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
Line 161... | Line 162... | ||
161 | } |
162 | } |
162 | 163 | ||
163 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode=PASSWORD_VTS_MCF1_DEFAULT_MODE, $iterations=PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS) { |
164 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode=PASSWORD_VTS_MCF1_DEFAULT_MODE, $iterations=PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS) { |
164 | if ($ver == '1') { |
165 | if ($ver == '1') { |
165 | if ($mode == PASSWORD_VTS_MCF1_MODE_SP) { |
166 | if ($mode == PASSWORD_VTS_MCF1_MODE_SP) { |
166 | $payload = $str_salt.$str_password; |
167 | $bin_hash = hash_ex($algo, $str_salt.$str_password, true); |
- | 168 | for ($i=0; $i<$iterations; $i++) { |
|
167 | $bin_hash = hash_ex($algo, $payload, true); |
169 | $bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i, true); |
- | 170 | } |
|
168 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) { |
171 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) { |
169 | $payload = $str_password.$str_salt; |
172 | $bin_hash = hash_ex($algo, $str_password.$str_salt, true); |
- | 173 | for ($i=0; $i<$iterations; $i++) { |
|
170 | $bin_hash = hash_ex($algo, $payload, true); |
174 | $bin_hash = hash_ex($algo, $bin_hash.$i.$str_salt, true); |
- | 175 | } |
|
171 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) { |
176 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) { |
172 | $payload = $str_salt.$str_password.$str_salt; |
177 | $bin_hash = hash_ex($algo, $str_salt.$str_password.$str_salt, true); |
- | 178 | for ($i=0; $i<$iterations; $i++) { |
|
173 | $bin_hash = hash_ex($algo, $payload, true); |
179 | $bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i.$str_salt, true); |
- | 180 | } |
|
174 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) { |
181 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) { |
175 | $bin_hash = hash_hmac_ex($algo, $str_password, $str_salt, true); |
182 | $bin_hash = hash_hmac_ex($algo, $str_password, $str_salt, true); |
- | 183 | for ($i=0; $i<$iterations; $i++) { |
|
- | 184 | // https://security.stackexchange.com/questions/149299/rounds-in-a-hashing-function |
|
- | 185 | $bin_hash = hash_hmac_ex($algo, $str_password, $bin_hash.$i, true); |
|
- | 186 | } |
|
176 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
187 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
177 | $bin_hash = hash_pbkdf2_ex($algo, $str_password, $str_salt, $iterations, 0, true); |
188 | $bin_hash = hash_pbkdf2_ex($algo, $str_password, $str_salt, $iterations, 0, true); |
178 | } else { |
189 | } else { |
179 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
190 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
180 | } |
191 | } |
181 | $bin_salt = $str_salt; |
192 | $bin_salt = $str_salt; |
182 | $params = array(); |
193 | $params = array(); |
183 | $params['a'] = $algo; |
194 | $params['a'] = $algo; |
184 | $params['m'] = $mode; |
195 | $params['m'] = $mode; |
185 | if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) $params['i'] = $iterations; |
196 | if ($iterations != 0) $params['i'] = $iterations; // i can be omitted if it is 0. |
186 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params); |
197 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params); |
187 | } else { |
198 | } else { |
188 | throw new Exception("Invalid VTS crypt version, expect 1."); |
199 | throw new Exception("Invalid VTS crypt version, expect 1."); |
189 | } |
200 | } |
190 | } |
201 | } |
Line 208... | Line 219... | ||
208 | 219 | ||
209 | if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
220 | if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
210 | if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing'); |
221 | if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing'); |
211 | $iterations = $params['i']; |
222 | $iterations = $params['i']; |
212 | } else { |
223 | } else { |
213 | $iterations = 0; |
224 | $iterations = isset($params['i']) ? $params['i'] : 0; |
214 | } |
225 | } |
215 | 226 | ||
216 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
227 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
217 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations); |
228 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations); |
218 | 229 | ||
Line 264... | Line 275... | ||
264 | $options['mode'] = $mcf['params']['m']; |
275 | $options['mode'] = $mcf['params']['m']; |
265 | 276 | ||
266 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
277 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
267 | if (!isset($mcf['params']['i'])) throw new Exception('Param "i" (iterations) missing'); |
278 | if (!isset($mcf['params']['i'])) throw new Exception('Param "i" (iterations) missing'); |
268 | $options['iterations'] = (int)$mcf['params']['i']; |
279 | $options['iterations'] = (int)$mcf['params']['i']; |
- | 280 | } else { |
|
- | 281 | $options['iterations'] = isset($mcf['params']['i']) ? (int)$mcf['params']['i'] : 0; |
|
269 | } |
282 | } |
270 | 283 | ||
271 | return array( |
284 | return array( |
272 | "algo" => PASSWORD_VTS_MCF1, |
285 | "algo" => PASSWORD_VTS_MCF1, |
273 | "algoName" => "vts-mcf-v1", |
286 | "algoName" => "vts-mcf-v1", |
Line 427... | Line 440... | ||
427 | // but it is not a valid option inside the MCF options |
440 | // but it is not a valid option inside the MCF options |
428 | // and it is not a valid option for vts_password_get_info(). |
441 | // and it is not a valid option for vts_password_get_info(). |
429 | unset($options['salt_length']); |
442 | unset($options['salt_length']); |
430 | } |
443 | } |
431 | 444 | ||
432 | // iterations=0 means: Default, depending on the algo |
445 | // For PBKDF2, iterations=0 means: Default, depending on the algo |
433 | if (($options['iterations'] == 0/*default*/) && ($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2)) { |
446 | if (($options['iterations'] == 0/*default*/) && ($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2)) { |
434 | $algo = $options2['algo']; |
447 | $algo = $options2['algo']; |
435 | $userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2'); |
448 | $userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2'); |
436 | $options['iterations'] = _vts_password_default_iterations($algo, $userland); |
449 | $options['iterations'] = _vts_password_default_iterations($algo, $userland); |
437 | } |
450 | } |
Line 605... | Line 618... | ||
605 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
618 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
606 | if (!isset($options['iterations'])) { |
619 | if (!isset($options['iterations'])) { |
607 | $options['iterations'] = PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS; |
620 | $options['iterations'] = PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS; |
608 | } |
621 | } |
609 | } else { |
622 | } else { |
610 | unset($options['iterations']); |
623 | $options['iterations'] = isset($options['iterations']) ? $options['iterations'] : 0; |
611 | } |
624 | } |
612 | } |
625 | } |
613 | return $options; |
626 | return $options; |
614 | } |
627 | } |
615 | 628 | ||
Line 689... | Line 702... | ||
689 | 'algo' => 'sha3-256', |
702 | 'algo' => 'sha3-256', |
690 | 'mode' => 'pbkdf2', |
703 | 'mode' => 'pbkdf2', |
691 | 'iterations' => 0 |
704 | 'iterations' => 0 |
692 | ))); |
705 | ))); |
693 | 706 | ||
- | 707 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
|
- | 708 | 'algo' => 'sha3-512', |
|
- | 709 | 'mode' => 'sps', |
|
- | 710 | 'iterations' => 2 |
|
- | 711 | )))); |
|
- | 712 | //echo "'$dummy' ".strlen($dummy)."\n"; |
|
- | 713 | //var_dump(vts_password_get_info($dummy)); |
|
- | 714 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
|
- | 715 | 'salt_length' => 51, |
|
- | 716 | 'algo' => 'sha3-512', |
|
- | 717 | 'mode' => 'sps', |
|
- | 718 | 'iterations' => 2 |
|
- | 719 | ))); |
|
- | 720 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
|
- | 721 | 'salt_length' => 50, |
|
- | 722 | 'algo' => 'sha3-256', |
|
- | 723 | 'mode' => 'sps', |
|
- | 724 | 'iterations' => 2 |
|
- | 725 | ))); |
|
- | 726 | ||
- | 727 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
|
- | 728 | 'algo' => 'sha3-512', |
|
- | 729 | 'mode' => 'hmac', |
|
- | 730 | 'iterations' => 2 |
|
- | 731 | )))); |
|
- | 732 | //echo "'$dummy' ".strlen($dummy)."\n"; |
|
- | 733 | //var_dump(vts_password_get_info($dummy)); |
|
- | 734 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
|
- | 735 | 'salt_length' => 51, |
|
- | 736 | 'algo' => 'sha3-512', |
|
- | 737 | 'mode' => 'hmac', |
|
- | 738 | 'iterations' => 2 |
|
- | 739 | ))); |
|
- | 740 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
|
- | 741 | 'salt_length' => 50, |
|
- | 742 | 'algo' => 'sha3-256', |
|
- | 743 | 'mode' => 'hmac', |
|
- | 744 | 'iterations' => 2 |
|
- | 745 | ))); |
|
694 | */ |
746 | */ |