Rev 1439 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 1439 | Rev 1469 | ||
---|---|---|---|
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-03 |
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 | * |
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
13 | * |
13 | * |
14 | * Unless required by applicable law or agreed to in writing, software |
14 | * Unless required by applicable law or agreed to in writing, software |
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
17 | * See the License for the specific language governing permissions and |
17 | * See the License for the specific language governing permissions and |
18 | * limitations under the License. |
18 | * limitations under the License. |
19 | */ |
19 | */ |
20 | 20 | ||
21 | /* |
21 | /* |
22 | 22 | ||
23 | The function vts_password_hash() replaces password_hash() |
23 | The function vts_password_hash() replaces password_hash() |
24 | and adds the ViaThinkSoft Modular Crypt Format 1.0 hash as well as |
24 | and adds the ViaThinkSoft Modular Crypt Format 1.0 hash as well as |
25 | all hashes from password_hash() and crypt(). |
25 | all hashes from password_hash() and crypt(). |
26 | 26 | ||
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>[,i=<iterations>]$<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 |
38 | sha512 |
38 | sha512 |
39 | sha512/256 |
39 | sha512/256 |
40 | sha512/224 |
40 | sha512/224 |
41 | sha384 |
41 | sha384 |
42 | sha256 |
42 | sha256 |
43 | sha224 |
43 | sha224 |
44 | sha1 |
44 | sha1 |
45 | md5 |
45 | md5 |
46 | Not supported are these hashes (because they have a special salt-handling and output their own crypt format): |
46 | Not supported are these hashes (because they have a special salt-handling and output their own crypt format): |
47 | bcrypt [Standardized crypt identifier 2, 2a, 2x, 2y] |
47 | bcrypt [Standardized crypt identifier 2, 2a, 2x, 2y] |
48 | argon2i [Crypt identifier argon2i, not standardized] |
48 | argon2i [Crypt identifier argon2i, not standardized] |
49 | argon2id [Crypt identifier argon2i, not standardized] |
49 | argon2id [Crypt identifier argon2i, not standardized] |
50 | Valid <mode> : |
50 | Valid <mode> : |
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 | <iterations> can be omitted if 0. It is required for mode=pbkdf2. For sp/ps/sps/hmac, it is optional. |
57 | Like most Crypt-hashes, <salt> and <hash> are Radix64 coded |
57 | Like most Crypt-hashes, <salt> and <hash> are Radix64 coded |
58 | with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding. |
58 | with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding. |
59 | Link to the online specification: |
59 | Link to the online specification: |
60 | https://hosted.oidplus.com/viathinksoft/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1 |
60 | https://hosted.oidplus.com/viathinksoft/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1 |
61 | Reference implementation in PHP: |
61 | Reference implementation in PHP: |
62 | https://github.com/danielmarschall/php_utils/blob/master/vts_crypt.inc.php |
62 | https://github.com/danielmarschall/php_utils/blob/master/vts_crypt.inc.php |
63 | 63 | ||
64 | */ |
64 | */ |
65 | 65 | ||
66 | require_once __DIR__ . '/misc_functions.inc.php'; |
66 | require_once __DIR__ . '/misc_functions.inc.php'; |
67 | 67 | ||
68 | define('OID_MCF_VTS_V1', '1.3.6.1.4.1.37476.3.0.1.1'); // { iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 specifications(3) misc(0) modular-crypt-format(1) vts-crypt-v1(1) } |
68 | define('OID_MCF_VTS_V1', '1.3.6.1.4.1.37476.3.0.1.1'); // { iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 specifications(3) misc(0) modular-crypt-format(1) vts-crypt-v1(1) } |
69 | 69 | ||
70 | // Valid algorithms for vts_password_hash(): |
70 | // Valid algorithms for vts_password_hash(): |
71 | define('PASSWORD_STD_DES', 'std-des'); // Algorithm from crypt() |
71 | define('PASSWORD_STD_DES', 'std-des'); // Algorithm from crypt() |
72 | define('PASSWORD_EXT_DES', 'ext-des'); // Algorithm from crypt() |
72 | define('PASSWORD_EXT_DES', 'ext-des'); // Algorithm from crypt() |
73 | define('PASSWORD_MD5', 'md5'); // Algorithm from crypt() |
73 | define('PASSWORD_MD5', 'md5'); // Algorithm from crypt() |
74 | define('PASSWORD_BLOWFISH', 'blowfish'); // Algorithm from crypt() |
74 | define('PASSWORD_BLOWFISH', 'blowfish'); // Algorithm from crypt() |
75 | define('PASSWORD_SHA256', 'sha256'); // Algorithm from crypt() |
75 | define('PASSWORD_SHA256', 'sha256'); // Algorithm from crypt() |
76 | define('PASSWORD_SHA512', 'sha512'); // Algorithm from crypt() |
76 | define('PASSWORD_SHA512', 'sha512'); // Algorithm from crypt() |
77 | define('PASSWORD_VTS_MCF1', OID_MCF_VTS_V1); // Algorithm by ViaThinkSoft |
77 | define('PASSWORD_VTS_MCF1', OID_MCF_VTS_V1); // Algorithm by ViaThinkSoft |
78 | // Other valid values (already defined in PHP): |
78 | // Other valid values (already defined in PHP): |
79 | // - PASSWORD_DEFAULT |
79 | // - PASSWORD_DEFAULT |
80 | // - PASSWORD_BCRYPT |
80 | // - PASSWORD_BCRYPT |
81 | // - PASSWORD_ARGON2I |
81 | // - PASSWORD_ARGON2I |
82 | // - PASSWORD_ARGON2ID |
82 | // - PASSWORD_ARGON2ID |
83 | 83 | ||
84 | define('PASSWORD_VTS_MCF1_MODE_SP', 'sp'); // Salt+Password |
84 | define('PASSWORD_VTS_MCF1_MODE_SP', 'sp'); // Salt+Password |
85 | define('PASSWORD_VTS_MCF1_MODE_PS', 'ps'); // Password+Salt |
85 | define('PASSWORD_VTS_MCF1_MODE_PS', 'ps'); // Password+Salt |
86 | define('PASSWORD_VTS_MCF1_MODE_SPS', 'sps'); // Salt+Password+Salt |
86 | define('PASSWORD_VTS_MCF1_MODE_SPS', 'sps'); // Salt+Password+Salt |
87 | define('PASSWORD_VTS_MCF1_MODE_HMAC', 'hmac'); // HMAC |
87 | define('PASSWORD_VTS_MCF1_MODE_HMAC', 'hmac'); // HMAC |
88 | define('PASSWORD_VTS_MCF1_MODE_PBKDF2', 'pbkdf2'); // PBKDF2-HMAC |
88 | define('PASSWORD_VTS_MCF1_MODE_PBKDF2', 'pbkdf2'); // PBKDF2-HMAC |
89 | 89 | ||
90 | define('PASSWORD_EXT_DES_DEFAULT_ITERATIONS', 725); |
90 | define('PASSWORD_EXT_DES_DEFAULT_ITERATIONS', 725); |
91 | define('PASSWORD_BLOWFISH_DEFAULT_COST', 10); |
91 | define('PASSWORD_BLOWFISH_DEFAULT_COST', 10); |
92 | define('PASSWORD_SHA256_DEFAULT_ROUNDS', 5000); |
92 | define('PASSWORD_SHA256_DEFAULT_ROUNDS', 5000); |
93 | define('PASSWORD_SHA512_DEFAULT_ROUNDS', 5000); |
93 | define('PASSWORD_SHA512_DEFAULT_ROUNDS', 5000); |
94 | 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() |
95 | define('PASSWORD_VTS_MCF1_DEFAULT_MODE', PASSWORD_VTS_MCF1_MODE_PS); |
95 | define('PASSWORD_VTS_MCF1_DEFAULT_MODE', PASSWORD_VTS_MCF1_MODE_PS); |
96 | define('PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS', 0); // For PBKDF2, iterations=0 means: Default, depending on the algo |
96 | define('PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS', 0); // For PBKDF2, iterations=0 means: Default, depending on the algo |
97 | 97 | ||
98 | // --- Part 1: Modular Crypt Format encode/decode |
98 | // --- Part 1: Modular Crypt Format encode/decode |
99 | 99 | ||
100 | 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) { |
101 | // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
101 | // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] |
102 | $out = '$'.$id; |
102 | $out = '$'.$id; |
103 | if (!is_null($params)) { |
103 | if (!is_null($params)) { |
104 | $ary_params = array(); |
104 | $ary_params = array(); |
105 | foreach ($params as $name => $value) { |
105 | foreach ($params as $name => $value) { |
106 | $ary_params[] = "$name=$value"; |
106 | $ary_params[] = "$name=$value"; |
107 | } |
107 | } |
108 | $out .= '$'.implode(',',$ary_params); |
108 | $out .= '$'.implode(',',$ary_params); |
109 | } |
109 | } |
110 | $out .= '$'.crypt_radix64_encode($bin_salt); |
110 | $out .= '$'.crypt_radix64_encode($bin_salt); |
111 | $out .= '$'.crypt_radix64_encode($bin_hash); |
111 | $out .= '$'.crypt_radix64_encode($bin_hash); |
112 | return $out; |
112 | return $out; |
113 | } |
113 | } |
114 | 114 | ||
115 | function crypt_modular_format_decode($mcf) { |
115 | function crypt_modular_format_decode($mcf) { |
116 | $ary = explode('$', $mcf); |
116 | $ary = explode('$', $mcf); |
117 | 117 | ||
118 | $dummy = array_shift($ary); |
118 | $dummy = array_shift($ary); |
119 | if ($dummy !== '') return false; |
119 | if ($dummy !== '') return false; |
120 | 120 | ||
121 | $dummy = array_shift($ary); |
121 | $dummy = array_shift($ary); |
122 | $id = $dummy; |
122 | $id = $dummy; |
123 | 123 | ||
124 | $params = array(); |
124 | $params = array(); |
125 | $dummy = array_shift($ary); |
125 | $dummy = array_shift($ary); |
126 | if (strpos($dummy, '=') !== false) { |
126 | if (strpos($dummy, '=') !== false) { |
127 | $params_ary = explode(',',$dummy); |
127 | $params_ary = explode(',',$dummy); |
128 | foreach ($params_ary as $param) { |
128 | foreach ($params_ary as $param) { |
129 | $bry = explode('=', $param, 2); |
129 | $bry = explode('=', $param, 2); |
130 | if (count($bry) > 1) { |
130 | if (count($bry) > 1) { |
131 | $params[$bry[0]] = $bry[1]; |
131 | $params[$bry[0]] = $bry[1]; |
132 | } |
132 | } |
133 | } |
133 | } |
134 | } else { |
134 | } else { |
135 | array_unshift($ary, $dummy); |
135 | array_unshift($ary, $dummy); |
136 | } |
136 | } |
137 | 137 | ||
138 | if (count($ary) > 1) { |
138 | if (count($ary) > 1) { |
139 | $dummy = array_shift($ary); |
139 | $dummy = array_shift($ary); |
140 | $bin_salt = crypt_radix64_decode($dummy); |
140 | $bin_salt = crypt_radix64_decode($dummy); |
141 | } else { |
141 | } else { |
142 | $bin_salt = ''; |
142 | $bin_salt = ''; |
143 | } |
143 | } |
144 | 144 | ||
145 | $dummy = array_shift($ary); |
145 | $dummy = array_shift($ary); |
146 | $bin_hash = crypt_radix64_decode($dummy); |
146 | $bin_hash = crypt_radix64_decode($dummy); |
147 | 147 | ||
148 | return array('id' => $id, |
148 | return array('id' => $id, |
149 | 'salt' => $bin_salt, |
149 | 'salt' => $bin_salt, |
150 | 'hash' => $bin_hash, |
150 | 'hash' => $bin_hash, |
151 | 'params' => $params); |
151 | 'params' => $params); |
152 | } |
152 | } |
153 | 153 | ||
154 | // --- Part 2: ViaThinkSoft Modular Crypt Format 1.0 |
154 | // --- Part 2: ViaThinkSoft Modular Crypt Format 1.0 |
155 | 155 | ||
156 | function vts_crypt_version($hash) { |
156 | function vts_crypt_version($hash) { |
157 | if (str_starts_with($hash, '$'.OID_MCF_VTS_V1.'$')) { |
157 | if (str_starts_with($hash, '$'.OID_MCF_VTS_V1.'$')) { |
158 | return '1'; |
158 | return '1'; |
159 | } else { |
159 | } else { |
160 | return '0'; |
160 | return '0'; |
161 | } |
161 | } |
162 | } |
162 | } |
163 | 163 | ||
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 | function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode=PASSWORD_VTS_MCF1_DEFAULT_MODE, $iterations=PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS) { |
165 | if ($ver == '1') { |
165 | if ($ver == '1') { |
166 | if ($mode == PASSWORD_VTS_MCF1_MODE_SP) { |
166 | if ($mode == PASSWORD_VTS_MCF1_MODE_SP) { |
167 | $bin_hash = hash_ex($algo, $str_salt.$str_password, true); |
167 | $bin_hash = hash_ex($algo, $str_salt.$str_password, true); |
168 | for ($i=0; $i<$iterations; $i++) { |
168 | for ($i=0; $i<$iterations; $i++) { |
169 | $bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i, true); |
169 | $bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i, true); |
170 | } |
170 | } |
171 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) { |
171 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PS) { |
172 | $bin_hash = hash_ex($algo, $str_password.$str_salt, true); |
172 | $bin_hash = hash_ex($algo, $str_password.$str_salt, true); |
173 | for ($i=0; $i<$iterations; $i++) { |
173 | for ($i=0; $i<$iterations; $i++) { |
174 | $bin_hash = hash_ex($algo, $bin_hash.$i.$str_salt, true); |
174 | $bin_hash = hash_ex($algo, $bin_hash.$i.$str_salt, true); |
175 | } |
175 | } |
176 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) { |
176 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_SPS) { |
177 | $bin_hash = hash_ex($algo, $str_salt.$str_password.$str_salt, true); |
177 | $bin_hash = hash_ex($algo, $str_salt.$str_password.$str_salt, true); |
178 | for ($i=0; $i<$iterations; $i++) { |
178 | for ($i=0; $i<$iterations; $i++) { |
179 | $bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i.$str_salt, true); |
179 | $bin_hash = hash_ex($algo, $str_salt.$bin_hash.$i.$str_salt, true); |
180 | } |
180 | } |
181 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) { |
181 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_HMAC) { |
182 | $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++) { |
183 | for ($i=0; $i<$iterations; $i++) { |
184 | // https://security.stackexchange.com/questions/149299/rounds-in-a-hashing-function |
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); |
185 | $bin_hash = hash_hmac_ex($algo, $str_password, $bin_hash.$i, true); |
186 | } |
186 | } |
187 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
187 | } else if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
188 | // Note: If $iterations=0, then hash_pbkdf2_ex() will correct it to the best value depending on $algo, see _vts_password_default_iterations(). |
188 | // Note: If $iterations=0, then hash_pbkdf2_ex() will correct it to the best value depending on $algo, see _vts_password_default_iterations(). |
189 | $bin_hash = hash_pbkdf2_ex($algo, $str_password, $str_salt, $iterations, 0, true); |
189 | $bin_hash = hash_pbkdf2_ex($algo, $str_password, $str_salt, $iterations, 0, true); |
190 | } else { |
190 | } else { |
191 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
191 | throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2."); |
192 | } |
192 | } |
193 | $bin_salt = $str_salt; |
193 | $bin_salt = $str_salt; |
194 | $params = array(); |
194 | $params = array(); |
195 | $params['a'] = $algo; |
195 | $params['a'] = $algo; |
196 | $params['m'] = $mode; |
196 | $params['m'] = $mode; |
197 | if ($iterations != 0) $params['i'] = $iterations; // i can be omitted if it is 0. |
197 | if ($iterations != 0) $params['i'] = $iterations; // i can be omitted if it is 0. |
198 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params); |
198 | return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params); |
199 | } else { |
199 | } else { |
200 | throw new Exception("Invalid VTS crypt version, expect 1."); |
200 | throw new Exception("Invalid VTS crypt version, expect 1."); |
201 | } |
201 | } |
202 | } |
202 | } |
203 | 203 | ||
204 | function vts_crypt_verify($password, $hash): bool { |
204 | function vts_crypt_verify($password, $hash): bool { |
205 | $ver = vts_crypt_version($hash); |
205 | $ver = vts_crypt_version($hash); |
206 | if ($ver == '1') { |
206 | if ($ver == '1') { |
207 | // Decode the MCF hash parameters |
207 | // Decode the MCF hash parameters |
208 | $data = crypt_modular_format_decode($hash); |
208 | $data = crypt_modular_format_decode($hash); |
209 | if ($data === false) throw new Exception('Invalid auth key'); |
209 | if ($data === false) throw new Exception('Invalid auth key'); |
210 | $id = $data['id']; |
210 | $id = $data['id']; |
211 | $bin_salt = $data['salt']; |
211 | $bin_salt = $data['salt']; |
212 | $bin_hash = $data['hash']; |
212 | $bin_hash = $data['hash']; |
213 | $params = $data['params']; |
213 | $params = $data['params']; |
214 | 214 | ||
215 | if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing'); |
215 | if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing'); |
216 | $algo = $params['a']; |
216 | $algo = $params['a']; |
217 | 217 | ||
218 | if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing'); |
218 | if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing'); |
219 | $mode = $params['m']; |
219 | $mode = $params['m']; |
220 | 220 | ||
221 | if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
221 | if ($mode == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
222 | if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing'); |
222 | if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing'); |
223 | $iterations = $params['i']; |
223 | $iterations = $params['i']; |
224 | } else { |
224 | } else { |
225 | $iterations = isset($params['i']) ? $params['i'] : 0; |
225 | $iterations = isset($params['i']) ? $params['i'] : 0; |
226 | } |
226 | } |
227 | 227 | ||
228 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
228 | // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password |
229 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations); |
229 | $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations); |
230 | 230 | ||
231 | // We rewrite the MCF to make sure that they match (if params have the wrong order) |
231 | // We re-encode the MCF to make sure that it can be compared with the VTS MCF 1.0 (correct sorting of params etc.) |
232 | $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params); |
232 | $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params); |
233 | 233 | ||
234 | return hash_equals($calc_authkey_1, $calc_authkey_2); |
234 | return hash_equals($calc_authkey_2, $calc_authkey_1); |
235 | } else { |
235 | } else { |
236 | throw new Exception("Invalid VTS crypt version, expect 1."); |
236 | throw new Exception("Invalid VTS crypt version, expect 1."); |
237 | } |
237 | } |
238 | } |
238 | } |
239 | 239 | ||
240 | // --- Part 3: Replacement of vts_password_*() functions |
240 | // --- Part 3: Replacement of vts_password_*() functions |
241 | 241 | ||
242 | /** |
242 | /** |
243 | * This function replaces password_algos() by extending it with |
243 | * This function replaces password_algos() by extending it with |
244 | * password hashes that are implemented in vts_password_hash(). |
244 | * password hashes that are implemented in vts_password_hash(). |
245 | * @return array of hashes that can be used in vts_password_hash(). |
245 | * @return array of hashes that can be used in vts_password_hash(). |
246 | */ |
246 | */ |
247 | function vts_password_algos() { |
247 | function vts_password_algos() { |
248 | $hashes = password_algos(); |
248 | $hashes = password_algos(); |
249 | $hashes[] = PASSWORD_STD_DES; // Algorithm from crypt() |
249 | $hashes[] = PASSWORD_STD_DES; // Algorithm from crypt() |
250 | $hashes[] = PASSWORD_EXT_DES; // Algorithm from crypt() |
250 | $hashes[] = PASSWORD_EXT_DES; // Algorithm from crypt() |
251 | $hashes[] = PASSWORD_MD5; // Algorithm from crypt() |
251 | $hashes[] = PASSWORD_MD5; // Algorithm from crypt() |
252 | $hashes[] = PASSWORD_BLOWFISH; // Algorithm from crypt() |
252 | $hashes[] = PASSWORD_BLOWFISH; // Algorithm from crypt() |
253 | $hashes[] = PASSWORD_SHA256; // Algorithm from crypt() |
253 | $hashes[] = PASSWORD_SHA256; // Algorithm from crypt() |
254 | $hashes[] = PASSWORD_SHA512; // Algorithm from crypt() |
254 | $hashes[] = PASSWORD_SHA512; // Algorithm from crypt() |
255 | $hashes[] = PASSWORD_VTS_MCF1; // Algorithm by ViaThinkSoft |
255 | $hashes[] = PASSWORD_VTS_MCF1; // Algorithm by ViaThinkSoft |
256 | return $hashes; |
256 | return $hashes; |
257 | } |
257 | } |
258 | 258 | ||
259 | /** vts_password_get_info() is the same as password_get_info(), |
259 | /** vts_password_get_info() is the same as password_get_info(), |
260 | * but it adds the crypt() and ViaThinkSoft MCF 1.0 algos which can be |
260 | * but it adds the crypt() and ViaThinkSoft MCF 1.0 algos which can be |
261 | * produced by vts_password_hash() |
261 | * produced by vts_password_hash() |
262 | * @param string $hash Hash created by vts_password_hash(), password_hash(), or crypt(). |
262 | * @param string $hash Hash created by vts_password_hash(), password_hash(), or crypt(). |
263 | * @return array Same output like password_get_info(). |
263 | * @return array Same output like password_get_info(). |
264 | */ |
264 | */ |
265 | function vts_password_get_info($hash) { |
265 | function vts_password_get_info($hash) { |
266 | if (vts_crypt_version($hash) == '1') { |
266 | if (vts_crypt_version($hash) == '1') { |
267 | // OID_MCF_VTS_V1 |
267 | // OID_MCF_VTS_V1 |
268 | $mcf = crypt_modular_format_decode($hash); |
268 | $mcf = crypt_modular_format_decode($hash); |
269 | 269 | ||
270 | //$options['salt_length'] = strlen($mcf['salt']); // Note: salt_length is not an MCF option! It's just a hint for vts_password_hash() |
270 | //$options['salt_length'] = strlen($mcf['salt']); // Note: salt_length is not an MCF option! It's just a hint for vts_password_hash() |
271 | 271 | ||
272 | if (!isset($mcf['params']['a'])) throw new Exception('Param "a" (algo) missing'); |
272 | if (!isset($mcf['params']['a'])) throw new Exception('Param "a" (algo) missing'); |
273 | $options['algo'] = $mcf['params']['a']; |
273 | $options['algo'] = $mcf['params']['a']; |
274 | 274 | ||
275 | if (!isset($mcf['params']['m'])) throw new Exception('Param "m" (mode) missing'); |
275 | if (!isset($mcf['params']['m'])) throw new Exception('Param "m" (mode) missing'); |
276 | $options['mode'] = $mcf['params']['m']; |
276 | $options['mode'] = $mcf['params']['m']; |
277 | 277 | ||
278 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
278 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
279 | if (!isset($mcf['params']['i'])) throw new Exception('Param "i" (iterations) missing'); |
279 | if (!isset($mcf['params']['i'])) throw new Exception('Param "i" (iterations) missing'); |
280 | $options['iterations'] = (int)$mcf['params']['i']; |
280 | $options['iterations'] = (int)$mcf['params']['i']; |
281 | } else { |
281 | } else { |
282 | $options['iterations'] = isset($mcf['params']['i']) ? (int)$mcf['params']['i'] : 0; |
282 | $options['iterations'] = isset($mcf['params']['i']) ? (int)$mcf['params']['i'] : 0; |
283 | } |
283 | } |
284 | 284 | ||
285 | return array( |
285 | return array( |
286 | "algo" => PASSWORD_VTS_MCF1, |
286 | "algo" => PASSWORD_VTS_MCF1, |
287 | "algoName" => "vts-mcf-v1", |
287 | "algoName" => "vts-mcf-v1", |
288 | "options" => $options |
288 | "options" => $options |
289 | ); |
289 | ); |
290 | } else if (!str_starts_with($hash, '$') && (strlen($hash) == 13)) { |
290 | } else if (!str_starts_with($hash, '$') && (strlen($hash) == 13)) { |
291 | // PASSWORD_STD_DES |
291 | // PASSWORD_STD_DES |
292 | return array( |
292 | return array( |
293 | "algo" => PASSWORD_STD_DES, |
293 | "algo" => PASSWORD_STD_DES, |
294 | "algoName" => "std-des", |
294 | "algoName" => "std-des", |
295 | "options" => array( |
295 | "options" => array( |
296 | // None |
296 | // None |
297 | ) |
297 | ) |
298 | ); |
298 | ); |
299 | } else if (str_starts_with($hash, '_') && (strlen($hash) == 20)) { |
299 | } else if (str_starts_with($hash, '_') && (strlen($hash) == 20)) { |
300 | // PASSWORD_EXT_DES |
300 | // PASSWORD_EXT_DES |
301 | return array( |
301 | return array( |
302 | "algo" => PASSWORD_EXT_DES, |
302 | "algo" => PASSWORD_EXT_DES, |
303 | "algoName" => "ext-des", |
303 | "algoName" => "ext-des", |
304 | "options" => array( |
304 | "options" => array( |
305 | "iterations" => (int)base64_int_decode(substr($hash,1,4)) |
305 | "iterations" => (int)base64_int_decode(substr($hash,1,4)) |
306 | ) |
306 | ) |
307 | ); |
307 | ); |
308 | } else if (str_starts_with($hash, '$1$')) { |
308 | } else if (str_starts_with($hash, '$1$')) { |
309 | // PASSWORD_MD5 |
309 | // PASSWORD_MD5 |
310 | return array( |
310 | return array( |
311 | "algo" => PASSWORD_MD5, |
311 | "algo" => PASSWORD_MD5, |
312 | "algoName" => "md5", |
312 | "algoName" => "md5", |
313 | "options" => array( |
313 | "options" => array( |
314 | // None |
314 | // None |
315 | ) |
315 | ) |
316 | ); |
316 | ); |
317 | } else if (str_starts_with($hash, '$2$') || str_starts_with($hash, '$2a$') || |
317 | } else if (str_starts_with($hash, '$2$') || str_starts_with($hash, '$2a$') || |
318 | str_starts_with($hash, '$2x$') || str_starts_with($hash, '$2y$')) { |
318 | str_starts_with($hash, '$2x$') || str_starts_with($hash, '$2y$')) { |
319 | // PASSWORD_BLOWFISH |
319 | // PASSWORD_BLOWFISH |
320 | return array( |
320 | return array( |
321 | "algo" => PASSWORD_BLOWFISH, |
321 | "algo" => PASSWORD_BLOWFISH, |
322 | "algoName" => "blowfish", |
322 | "algoName" => "blowfish", |
323 | "options" => array( |
323 | "options" => array( |
324 | "cost" => (int)ltrim(explode('$',$hash)[2],'0') |
324 | "cost" => (int)ltrim(explode('$',$hash)[2],'0') |
325 | ) |
325 | ) |
326 | ); |
326 | ); |
327 | } else if (str_starts_with($hash, '$5$')) { |
327 | } else if (str_starts_with($hash, '$5$')) { |
328 | // PASSWORD_SHA256 |
328 | // PASSWORD_SHA256 |
329 | return array( |
329 | return array( |
330 | "algo" => PASSWORD_SHA256, |
330 | "algo" => PASSWORD_SHA256, |
331 | "algoName" => "sha256", |
331 | "algoName" => "sha256", |
332 | "options" => array( |
332 | "options" => array( |
333 | 'rounds' => (int)str_replace('rounds=','',explode('$',$hash)[2]) |
333 | 'rounds' => (int)str_replace('rounds=','',explode('$',$hash)[2]) |
334 | ) |
334 | ) |
335 | ); |
335 | ); |
336 | } else if (str_starts_with($hash, '$6$')) { |
336 | } else if (str_starts_with($hash, '$6$')) { |
337 | // PASSWORD_SHA512 |
337 | // PASSWORD_SHA512 |
338 | return array( |
338 | return array( |
339 | "algo" => PASSWORD_SHA512, |
339 | "algo" => PASSWORD_SHA512, |
340 | "algoName" => "sha512", |
340 | "algoName" => "sha512", |
341 | "options" => array( |
341 | "options" => array( |
342 | 'rounds' => (int)str_replace('rounds=','',explode('$',$hash)[2]) |
342 | 'rounds' => (int)str_replace('rounds=','',explode('$',$hash)[2]) |
343 | ) |
343 | ) |
344 | ); |
344 | ); |
345 | } else { |
345 | } else { |
346 | // PASSWORD_DEFAULT |
346 | // PASSWORD_DEFAULT |
347 | // PASSWORD_BCRYPT |
347 | // PASSWORD_BCRYPT |
348 | // PASSWORD_ARGON2I |
348 | // PASSWORD_ARGON2I |
349 | // PASSWORD_ARGON2ID |
349 | // PASSWORD_ARGON2ID |
350 | return password_get_info($hash); |
350 | return password_get_info($hash); |
351 | } |
351 | } |
352 | } |
352 | } |
353 | 353 | ||
354 | /** This function extends password_hash() with the algorithms supported by crypt(). |
354 | /** This function extends password_hash() with the algorithms supported by crypt(). |
355 | * It also adds vts_crypt_hash() which implements the ViaThinkSoft Modular Crypt Format 1.0. |
355 | * It also adds vts_crypt_hash() which implements the ViaThinkSoft Modular Crypt Format 1.0. |
356 | * The result can be verified using vts_password_verify(). |
356 | * The result can be verified using vts_password_verify(). |
357 | * @param string $password to be hashed |
357 | * @param string $password to be hashed |
358 | * @param mixed $algo algorithm |
358 | * @param mixed $algo algorithm |
359 | * @param array $options options for the hashing algorithm |
359 | * @param array $options options for the hashing algorithm |
360 | * @return string Crypt style password hash |
360 | * @return string Crypt style password hash |
361 | */ |
361 | */ |
362 | function vts_password_hash($password, $algo, $options=array()): string { |
362 | function vts_password_hash($password, $algo, $options=array()): string { |
363 | $options = vts_password_fill_default_options($algo, $options); |
363 | $options = vts_password_fill_default_options($algo, $options); |
364 | 364 | ||
365 | $crypt_salt = null; |
365 | $crypt_salt = null; |
366 | if (($algo === PASSWORD_STD_DES) && defined('CRYPT_STD_DES')) { |
366 | if (($algo === PASSWORD_STD_DES) && defined('CRYPT_STD_DES')) { |
367 | // Standard DES-based hash with a two character salt from the alphabet "./0-9A-Za-z". Using invalid characters in the salt will cause crypt() to fail. |
367 | // Standard DES-based hash with a two character salt from the alphabet "./0-9A-Za-z". Using invalid characters in the salt will cause crypt() to fail. |
368 | $crypt_salt = des_compat_salt(2); |
368 | $crypt_salt = des_compat_salt(2); |
369 | } else if (($algo === PASSWORD_EXT_DES) && defined('CRYPT_EXT_DES')) { |
369 | } else if (($algo === PASSWORD_EXT_DES) && defined('CRYPT_EXT_DES')) { |
370 | // Extended DES-based hash. The "salt" is a 9-character string consisting of an underscore followed by 4 characters of iteration count and 4 characters of salt. Each of these 4-character strings encode 24 bits, least significant character first. The values 0 to 63 are encoded as ./0-9A-Za-z. Using invalid characters in the salt will cause crypt() to fail. |
370 | // Extended DES-based hash. The "salt" is a 9-character string consisting of an underscore followed by 4 characters of iteration count and 4 characters of salt. Each of these 4-character strings encode 24 bits, least significant character first. The values 0 to 63 are encoded as ./0-9A-Za-z. Using invalid characters in the salt will cause crypt() to fail. |
371 | $iterations = $options['iterations']; |
371 | $iterations = $options['iterations']; |
372 | $crypt_salt = '_' . base64_int_encode($iterations,4) . des_compat_salt(4); |
372 | $crypt_salt = '_' . base64_int_encode($iterations,4) . des_compat_salt(4); |
373 | } else if (($algo === PASSWORD_MD5) && defined('CRYPT_MD5')) { |
373 | } else if (($algo === PASSWORD_MD5) && defined('CRYPT_MD5')) { |
374 | // MD5 hashing with a twelve character salt starting with $1$ |
374 | // MD5 hashing with a twelve character salt starting with $1$ |
375 | $crypt_salt = '$1$'.des_compat_salt(12).'$'; |
375 | $crypt_salt = '$1$'.des_compat_salt(12).'$'; |
376 | } else if (($algo === PASSWORD_BLOWFISH) && defined('CRYPT_BLOWFISH')) { |
376 | } else if (($algo === PASSWORD_BLOWFISH) && defined('CRYPT_BLOWFISH')) { |
377 | // Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithm and must be in range 04-31, values outside this range will cause crypt() to fail. "$2x$" hashes are potentially weak; "$2a$" hashes are compatible and mitigate this weakness. For new hashes, "$2y$" should be used. |
377 | // Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithm and must be in range 04-31, values outside this range will cause crypt() to fail. "$2x$" hashes are potentially weak; "$2a$" hashes are compatible and mitigate this weakness. For new hashes, "$2y$" should be used. |
378 | $algo = '$2y$'; // most secure |
378 | $algo = '$2y$'; // most secure |
379 | $cost = $options['cost']; |
379 | $cost = $options['cost']; |
380 | $crypt_salt = $algo.str_pad($cost,2,'0',STR_PAD_LEFT).'$'.des_compat_salt(22).'$'; |
380 | $crypt_salt = $algo.str_pad($cost,2,'0',STR_PAD_LEFT).'$'.des_compat_salt(22).'$'; |
381 | } else if (($algo === PASSWORD_SHA256) && defined('CRYPT_SHA256')) { |
381 | } else if (($algo === PASSWORD_SHA256) && defined('CRYPT_SHA256')) { |
382 | // SHA-256 hash with a sixteen character salt prefixed with $5$. If the salt string starts with 'rounds=<N>$', the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit. |
382 | // SHA-256 hash with a sixteen character salt prefixed with $5$. If the salt string starts with 'rounds=<N>$', the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit. |
383 | $algo = '$5$'; |
383 | $algo = '$5$'; |
384 | $rounds = $options['rounds']; |
384 | $rounds = $options['rounds']; |
385 | $crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$'; |
385 | $crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$'; |
386 | } else if (($algo === PASSWORD_SHA512) && defined('CRYPT_SHA512')) { |
386 | } else if (($algo === PASSWORD_SHA512) && defined('CRYPT_SHA512')) { |
387 | // SHA-512 hash with a sixteen character salt prefixed with $6$. If the salt string starts with 'rounds=<N>$', the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit. |
387 | // SHA-512 hash with a sixteen character salt prefixed with $6$. If the salt string starts with 'rounds=<N>$', the numeric value of N is used to indicate how many times the hashing loop should be executed, much like the cost parameter on Blowfish. The default number of rounds is 5000, there is a minimum of 1000 and a maximum of 999,999,999. Any selection of N outside this range will be truncated to the nearest limit. |
388 | $algo = '$6$'; |
388 | $algo = '$6$'; |
389 | $rounds = $options['rounds']; |
389 | $rounds = $options['rounds']; |
390 | $crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$'; |
390 | $crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$'; |
391 | } |
391 | } |
392 | 392 | ||
393 | if (!is_null($crypt_salt)) { |
393 | if (!is_null($crypt_salt)) { |
394 | // Algorithms: PASSWORD_STD_DES |
394 | // Algorithms: PASSWORD_STD_DES |
395 | // PASSWORD_EXT_DES |
395 | // PASSWORD_EXT_DES |
396 | // PASSWORD_MD5 |
396 | // PASSWORD_MD5 |
397 | // PASSWORD_BLOWFISH |
397 | // PASSWORD_BLOWFISH |
398 | // PASSWORD_SHA256 |
398 | // PASSWORD_SHA256 |
399 | // PASSWORD_SHA512 |
399 | // PASSWORD_SHA512 |
400 | $out = crypt($password, $crypt_salt); |
400 | $out = crypt($password, $crypt_salt); |
401 | if (strlen($out) < 13) throw new Exception("crypt() failed"); |
401 | if (strlen($out) < 13) throw new Exception("crypt() failed"); |
402 | return $out; |
402 | return $out; |
403 | } else if ($algo === PASSWORD_VTS_MCF1) { |
403 | } else if ($algo === PASSWORD_VTS_MCF1) { |
404 | // Algorithms: PASSWORD_VTS_MCF1 |
404 | // Algorithms: PASSWORD_VTS_MCF1 |
405 | $ver = '1'; |
405 | $ver = '1'; |
406 | $algo = $options['algo']; |
406 | $algo = $options['algo']; |
407 | $mode = $options['mode']; |
407 | $mode = $options['mode']; |
408 | $iterations = $options['iterations']; |
408 | $iterations = $options['iterations']; |
409 | $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 32; // Note: salt_length is not an MCF option! It's just a hint for vts_password_hash() |
409 | $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 32; // Note: salt_length is not an MCF option! It's just a hint for vts_password_hash() |
410 | $salt = random_bytes_ex($salt_len, true, true); |
410 | $salt = random_bytes_ex($salt_len, true, true); |
411 | return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations); |
411 | return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations); |
412 | } else { |
412 | } else { |
413 | // Algorithms: PASSWORD_DEFAULT |
413 | // Algorithms: PASSWORD_DEFAULT |
414 | // PASSWORD_BCRYPT |
414 | // PASSWORD_BCRYPT |
415 | // PASSWORD_ARGON2I |
415 | // PASSWORD_ARGON2I |
416 | // PASSWORD_ARGON2ID |
416 | // PASSWORD_ARGON2ID |
417 | return password_hash($password, $algo, $options); |
417 | return password_hash($password, $algo, $options); |
418 | } |
418 | } |
419 | } |
419 | } |
420 | 420 | ||
421 | /** This function replaces password_needs_rehash() by adding additional algorithms |
421 | /** This function replaces password_needs_rehash() by adding additional algorithms |
422 | * supported by vts_password_hash(). |
422 | * supported by vts_password_hash(). |
423 | * @param string $hash The current hash |
423 | * @param string $hash The current hash |
424 | * @param string|int|null $algo Desired new default algo |
424 | * @param string|int|null $algo Desired new default algo |
425 | * @param array $options Desired new default options |
425 | * @param array $options Desired new default options |
426 | * @return bool True if algo or options of the current hash don't match the current desired values ($algo and $options), otherwise false. |
426 | * @return bool True if algo or options of the current hash don't match the current desired values ($algo and $options), otherwise false. |
427 | */ |
427 | */ |
428 | function vts_password_needs_rehash($hash, $algo, $options=array()) { |
428 | function vts_password_needs_rehash($hash, $algo, $options=array()) { |
429 | $options = vts_password_fill_default_options($algo, $options); |
429 | $options = vts_password_fill_default_options($algo, $options); |
430 | 430 | ||
431 | $info = vts_password_get_info($hash); |
431 | $info = vts_password_get_info($hash); |
432 | $algo2 = $info['algo']; |
432 | $algo2 = $info['algo']; |
433 | $options2 = $info['options']; |
433 | $options2 = $info['options']; |
434 | 434 | ||
435 | // Check if algorithm matches |
435 | // Check if algorithm matches |
436 | if ($algo !== $algo2) return true; |
436 | if ($algo !== $algo2) return true; |
437 | 437 | ||
438 | if (vts_crypt_version($hash) == '1') { |
438 | if (vts_crypt_version($hash) == '1') { |
439 | if (isset($options['salt_length'])) { |
439 | if (isset($options['salt_length'])) { |
440 | // For VTS MCF 1.0, salt_length is a valid option for vts_password_hash(), |
440 | // For VTS MCF 1.0, salt_length is a valid option for vts_password_hash(), |
441 | // but it is not a valid option inside the MCF options |
441 | // but it is not a valid option inside the MCF options |
442 | // and it is not a valid option for vts_password_get_info(). |
442 | // and it is not a valid option for vts_password_get_info(). |
443 | unset($options['salt_length']); |
443 | unset($options['salt_length']); |
444 | } |
444 | } |
445 | 445 | ||
446 | // For PBKDF2, iterations=0 means: Default, depending on the algo |
446 | // For PBKDF2, iterations=0 means: Default, depending on the algo |
447 | if (($options['iterations'] == 0/*default*/) && ($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2)) { |
447 | if (($options['iterations'] == 0/*default*/) && ($options2['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2)) { |
448 | $algo = $options2['algo']; |
448 | $algo = $options2['algo']; |
449 | $userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2'); |
449 | $userland = !hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2'); |
450 | $options['iterations'] = _vts_password_default_iterations($algo, $userland); |
450 | $options['iterations'] = _vts_password_default_iterations($algo, $userland); |
451 | } |
451 | } |
452 | } |
452 | } |
453 | 453 | ||
454 | // Check if options match |
454 | // Check if options match |
455 | if (count($options) !== count($options2)) return true; |
455 | if (count($options) !== count($options2)) return true; |
456 | foreach ($options as $name => $val) { |
456 | foreach ($options as $name => $val) { |
457 | if ($options2[$name] != $val) return true; |
457 | if ($options2[$name] != $val) return true; |
458 | } |
458 | } |
459 | return false; |
459 | return false; |
460 | } |
460 | } |
461 | 461 | ||
462 | /** This function extends password_verify() by adding ViaThinkSoft Modular Crypt Format 1.0. |
462 | /** This function extends password_verify() by adding ViaThinkSoft Modular Crypt Format 1.0. |
463 | * @param string $password to be checked |
463 | * @param string $password to be checked |
464 | * @param string $hash Hash created by crypt(), password_hash(), or vts_password_hash(). |
464 | * @param string $hash Hash created by crypt(), password_hash(), or vts_password_hash(). |
465 | * @return bool true if password is valid |
465 | * @return bool true if password is valid |
466 | */ |
466 | */ |
467 | function vts_password_verify($password, $hash): bool { |
467 | function vts_password_verify($password, $hash): bool { |
468 | if (vts_crypt_version($hash) != '0') { |
468 | if (vts_crypt_version($hash) != '0') { |
469 | // Hash created by vts_password_hash(), or vts_crypt_hash() |
469 | // Hash created by vts_password_hash(), or vts_crypt_hash() |
470 | return vts_crypt_verify($password, $hash); |
470 | return vts_crypt_verify($password, $hash); |
471 | } else { |
471 | } else { |
472 | // Hash created by vts_password_hash(), password_hash(), or crypt() |
472 | // Hash created by vts_password_hash(), password_hash(), or crypt() |
473 | return password_verify($password, $hash); |
473 | return password_verify($password, $hash); |
474 | } |
474 | } |
475 | } |
475 | } |
476 | 476 | ||
477 | // --- Part 4: Functions which include a fallback to a pure-PHP sha3 implementation (requires https://github.com/danielmarschall/php-sha3 ) |
477 | // --- Part 4: Functions which include a fallback to a pure-PHP sha3 implementation (requires https://github.com/danielmarschall/php-sha3 ) |
478 | 478 | ||
479 | function hash_ex($algo, $data, $binary=false, $options=array()) { |
479 | function hash_ex($algo, $data, $binary=false, $options=array()) { |
480 | if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
480 | if (!hash_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) { |
481 | $bits = (int)explode('-',$algo)[1]; |
481 | $bits = (int)explode('-',$algo)[1]; |
482 | $hash = \bb\Sha3\Sha3::hash($data, $bits, $binary); |
482 | $hash = \bb\Sha3\Sha3::hash($data, $bits, $binary); |
483 | } else { |
483 | } else { |
484 | $hash = hash($algo, $data, $binary); |
484 | $hash = hash($algo, $data, $binary); |
485 | } |
485 | } |
486 | return $hash; |
486 | return $hash; |
487 | } |
487 | } |
488 | 488 | ||
489 | function hash_hmac_ex($algo, $data, $key, $binary=false) { |
489 | function hash_hmac_ex($algo, $data, $key, $binary=false) { |
490 | if (!hash_hmac_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) { |
490 | if (!hash_hmac_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) { |
491 | $bits = (int)explode('-',$algo)[1]; |
491 | $bits = (int)explode('-',$algo)[1]; |
492 | $hash = \bb\Sha3\Sha3::hash_hmac($data, $key, $bits, $binary); |
492 | $hash = \bb\Sha3\Sha3::hash_hmac($data, $key, $bits, $binary); |
493 | } else { |
493 | } else { |
494 | $hash = hash_hmac($algo, $data, $key, $binary); |
494 | $hash = hash_hmac($algo, $data, $key, $binary); |
495 | } |
495 | } |
496 | return $hash; |
496 | return $hash; |
497 | } |
497 | } |
498 | 498 | ||
499 | function hash_pbkdf2_ex($algo, $password, $salt, &$iterations=0, $length=0, $binary=false) { |
499 | function hash_pbkdf2_ex($algo, $password, $salt, &$iterations=0, $length=0, $binary=false) { |
500 | if (!hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) { |
500 | if (!hash_pbkdf2_supported_natively($algo) && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) { |
501 | if ($iterations == 0/*default*/) { |
501 | if ($iterations == 0/*default*/) { |
502 | $iterations = _vts_password_default_iterations($algo, true); |
502 | $iterations = _vts_password_default_iterations($algo, true); |
503 | } |
503 | } |
504 | $bits = (int)explode('-',$algo)[1]; |
504 | $bits = (int)explode('-',$algo)[1]; |
505 | $hash = \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, $bits, $length, $binary); |
505 | $hash = \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, $bits, $length, $binary); |
506 | } else { |
506 | } else { |
507 | if ($iterations == 0/*default*/) { |
507 | if ($iterations == 0/*default*/) { |
508 | $iterations = _vts_password_default_iterations($algo, false); |
508 | $iterations = _vts_password_default_iterations($algo, false); |
509 | } |
509 | } |
510 | $hash = hash_pbkdf2($algo, $password, $salt, $iterations, $length, $binary); |
510 | $hash = hash_pbkdf2($algo, $password, $salt, $iterations, $length, $binary); |
511 | } |
511 | } |
512 | return $hash; |
512 | return $hash; |
513 | } |
513 | } |
514 | 514 | ||
515 | // --- Part 5: Useful functions required by the crypt-functions |
515 | // --- Part 5: Useful functions required by the crypt-functions |
516 | 516 | ||
517 | define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'); |
517 | define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'); |
518 | define('BASE64_CRYPT_ALPHABET', './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); |
518 | define('BASE64_CRYPT_ALPHABET', './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'); |
519 | 519 | ||
520 | function des_compat_salt($salt_len) { |
520 | function des_compat_salt($salt_len) { |
521 | if ($salt_len <= 0) return ''; |
521 | if ($salt_len <= 0) return ''; |
522 | $characters = BASE64_CRYPT_ALPHABET; |
522 | $characters = BASE64_CRYPT_ALPHABET; |
523 | $salt = ''; |
523 | $salt = ''; |
524 | $bytes = random_bytes_ex($salt_len, true, true); |
524 | $bytes = random_bytes_ex($salt_len, true, true); |
525 | for ($i=0; $i<$salt_len; $i++) { |
525 | for ($i=0; $i<$salt_len; $i++) { |
526 | $salt .= $characters[ord($bytes[$i]) % strlen($characters)]; |
526 | $salt .= $characters[ord($bytes[$i]) % strlen($characters)]; |
527 | } |
527 | } |
528 | return $salt; |
528 | return $salt; |
529 | } |
529 | } |
530 | 530 | ||
531 | function base64_int_encode($num, $len) { |
531 | function base64_int_encode($num, $len) { |
532 | // https://stackoverflow.com/questions/15534982/which-iteration-rules-apply-on-crypt-using-crypt-ext-des |
532 | // https://stackoverflow.com/questions/15534982/which-iteration-rules-apply-on-crypt-using-crypt-ext-des |
533 | $alphabet_raw = BASE64_CRYPT_ALPHABET; |
533 | $alphabet_raw = BASE64_CRYPT_ALPHABET; |
534 | $alphabet = str_split($alphabet_raw); |
534 | $alphabet = str_split($alphabet_raw); |
535 | $arr = array(); |
535 | $arr = array(); |
536 | $base = sizeof($alphabet); |
536 | $base = sizeof($alphabet); |
537 | while ($num) { |
537 | while ($num) { |
538 | $rem = $num % $base; |
538 | $rem = $num % $base; |
539 | $num = (int)($num / $base); |
539 | $num = (int)($num / $base); |
540 | $arr[] = $alphabet[$rem]; |
540 | $arr[] = $alphabet[$rem]; |
541 | } |
541 | } |
542 | $string = implode($arr); |
542 | $string = implode($arr); |
543 | return str_pad($string, $len, '.', STR_PAD_RIGHT); |
543 | return str_pad($string, $len, '.', STR_PAD_RIGHT); |
544 | } |
544 | } |
545 | 545 | ||
546 | function base64_int_decode($base64) { |
546 | function base64_int_decode($base64) { |
547 | $num = 0; |
547 | $num = 0; |
548 | for ($i=strlen($base64)-1;$i>=0;$i--) { |
548 | for ($i=strlen($base64)-1;$i>=0;$i--) { |
549 | $num += strpos(BASE64_CRYPT_ALPHABET, $base64[$i])*pow(strlen(BASE64_CRYPT_ALPHABET),$i); |
549 | $num += strpos(BASE64_CRYPT_ALPHABET, $base64[$i])*pow(strlen(BASE64_CRYPT_ALPHABET),$i); |
550 | } |
550 | } |
551 | return $num; |
551 | return $num; |
552 | } |
552 | } |
553 | 553 | ||
554 | function crypt_radix64_encode($str) { |
554 | function crypt_radix64_encode($str) { |
555 | $x = $str; |
555 | $x = $str; |
556 | $x = base64_encode($x); |
556 | $x = base64_encode($x); |
557 | $x = rtrim($x, '='); // remove padding |
557 | $x = rtrim($x, '='); // remove padding |
558 | $x = strtr($x, BASE64_RFC4648_ALPHABET, BASE64_CRYPT_ALPHABET); |
558 | $x = strtr($x, BASE64_RFC4648_ALPHABET, BASE64_CRYPT_ALPHABET); |
559 | return $x; |
559 | return $x; |
560 | } |
560 | } |
561 | 561 | ||
562 | function crypt_radix64_decode($str) { |
562 | function crypt_radix64_decode($str) { |
563 | $x = $str; |
563 | $x = $str; |
564 | $x = strtr($x, BASE64_CRYPT_ALPHABET, BASE64_RFC4648_ALPHABET); |
564 | $x = strtr($x, BASE64_CRYPT_ALPHABET, BASE64_RFC4648_ALPHABET); |
565 | $x = base64_decode($x); |
565 | $x = base64_decode($x); |
566 | return $x; |
566 | return $x; |
567 | } |
567 | } |
568 | 568 | ||
569 | function hash_supported_natively($algo) { |
569 | function hash_supported_natively($algo) { |
570 | if (version_compare(PHP_VERSION, '5.1.2') >= 0) { |
570 | if (version_compare(PHP_VERSION, '5.1.2') >= 0) { |
571 | return in_array($algo, hash_algos()); |
571 | return in_array($algo, hash_algos()); |
572 | } else { |
572 | } else { |
573 | return false; |
573 | return false; |
574 | } |
574 | } |
575 | } |
575 | } |
576 | 576 | ||
577 | function hash_hmac_supported_natively($algo): bool { |
577 | function hash_hmac_supported_natively($algo): bool { |
578 | if (version_compare(PHP_VERSION, '7.2.0') >= 0) { |
578 | if (version_compare(PHP_VERSION, '7.2.0') >= 0) { |
579 | return in_array($algo, hash_hmac_algos()); |
579 | return in_array($algo, hash_hmac_algos()); |
580 | } else if (version_compare(PHP_VERSION, '5.1.2') >= 0) { |
580 | } else if (version_compare(PHP_VERSION, '5.1.2') >= 0) { |
581 | return in_array($algo, hash_algos()); |
581 | return in_array($algo, hash_algos()); |
582 | } else { |
582 | } else { |
583 | return false; |
583 | return false; |
584 | } |
584 | } |
585 | } |
585 | } |
586 | 586 | ||
587 | function hash_pbkdf2_supported_natively($algo) { |
587 | function hash_pbkdf2_supported_natively($algo) { |
588 | return hash_supported_natively($algo); |
588 | return hash_supported_natively($algo); |
589 | } |
589 | } |
590 | 590 | ||
591 | function vts_password_fill_default_options($algo, $options) { |
591 | function vts_password_fill_default_options($algo, $options) { |
592 | if ($algo === PASSWORD_STD_DES) { |
592 | if ($algo === PASSWORD_STD_DES) { |
593 | // No options |
593 | // No options |
594 | } else if ($algo === PASSWORD_EXT_DES) { |
594 | } else if ($algo === PASSWORD_EXT_DES) { |
595 | if (!isset($options['iterations'])) { |
595 | if (!isset($options['iterations'])) { |
596 | $options['iterations'] = PASSWORD_EXT_DES_DEFAULT_ITERATIONS; |
596 | $options['iterations'] = PASSWORD_EXT_DES_DEFAULT_ITERATIONS; |
597 | } |
597 | } |
598 | } else if ($algo === PASSWORD_MD5) { |
598 | } else if ($algo === PASSWORD_MD5) { |
599 | // No options |
599 | // No options |
600 | } else if ($algo === PASSWORD_BLOWFISH) { |
600 | } else if ($algo === PASSWORD_BLOWFISH) { |
601 | if (!isset($options['cost'])) { |
601 | if (!isset($options['cost'])) { |
602 | $options['cost'] = PASSWORD_BLOWFISH_DEFAULT_COST; |
602 | $options['cost'] = PASSWORD_BLOWFISH_DEFAULT_COST; |
603 | } |
603 | } |
604 | } else if ($algo === PASSWORD_SHA256) { |
604 | } else if ($algo === PASSWORD_SHA256) { |
605 | if (!isset($options['rounds'])) { |
605 | if (!isset($options['rounds'])) { |
606 | $options['rounds'] = PASSWORD_SHA256_DEFAULT_ROUNDS; |
606 | $options['rounds'] = PASSWORD_SHA256_DEFAULT_ROUNDS; |
607 | } |
607 | } |
608 | } else if ($algo === PASSWORD_SHA512) { |
608 | } else if ($algo === PASSWORD_SHA512) { |
609 | if (!isset($options['rounds'])) { |
609 | if (!isset($options['rounds'])) { |
610 | $options['rounds'] = PASSWORD_SHA512_DEFAULT_ROUNDS; |
610 | $options['rounds'] = PASSWORD_SHA512_DEFAULT_ROUNDS; |
611 | } |
611 | } |
612 | } else if ($algo === PASSWORD_VTS_MCF1) { |
612 | } else if ($algo === PASSWORD_VTS_MCF1) { |
613 | if (!isset($options['algo'])) { |
613 | if (!isset($options['algo'])) { |
614 | $options['algo'] = PASSWORD_VTS_MCF1_DEFAULT_ALGO; |
614 | $options['algo'] = PASSWORD_VTS_MCF1_DEFAULT_ALGO; |
615 | } |
615 | } |
616 | if (!isset($options['mode'])) { |
616 | if (!isset($options['mode'])) { |
617 | $options['mode'] = PASSWORD_VTS_MCF1_DEFAULT_MODE; |
617 | $options['mode'] = PASSWORD_VTS_MCF1_DEFAULT_MODE; |
618 | } |
618 | } |
619 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
619 | if ($options['mode'] == PASSWORD_VTS_MCF1_MODE_PBKDF2) { |
620 | if (!isset($options['iterations'])) { |
620 | if (!isset($options['iterations'])) { |
621 | $options['iterations'] = PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS; |
621 | $options['iterations'] = PASSWORD_VTS_MCF1_DEFAULT_ITERATIONS; |
622 | } |
622 | } |
623 | } else { |
623 | } else { |
624 | $options['iterations'] = isset($options['iterations']) ? $options['iterations'] : 0; |
624 | $options['iterations'] = isset($options['iterations']) ? $options['iterations'] : 0; |
625 | } |
625 | } |
626 | } |
626 | } |
627 | return $options; |
627 | return $options; |
628 | } |
628 | } |
629 | 629 | ||
630 | function _vts_password_default_iterations($algo, $userland) { |
630 | function _vts_password_default_iterations($algo, $userland) { |
631 | if ($userland) { |
631 | if ($userland) { |
632 | return 100; // because the userland implementation is EXTREMELY slow, we must choose a small value, sorry... |
632 | return 100; // because the userland implementation is EXTREMELY slow, we must choose a small value, sorry... |
633 | } else { |
633 | } else { |
634 | // Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
634 | // Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 |
635 | // Note that hash_pbkdf2() implements PBKDF2-HMAC-* |
635 | // Note that hash_pbkdf2() implements PBKDF2-HMAC-* |
636 | if ($algo == 'sha3-512') return 100000; |
636 | if ($algo == 'sha3-512') return 100000; |
637 | else if ($algo == 'sha3-384') return 100000; |
637 | else if ($algo == 'sha3-384') return 100000; |
638 | else if ($algo == 'sha3-256') return 100000; |
638 | else if ($algo == 'sha3-256') return 100000; |
639 | else if ($algo == 'sha3-224') return 100000; |
639 | else if ($algo == 'sha3-224') return 100000; |
640 | else if ($algo == 'sha512') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
640 | else if ($algo == 'sha512') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
641 | else if ($algo == 'sha512/256') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
641 | else if ($algo == 'sha512/256') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
642 | else if ($algo == 'sha512/224') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
642 | else if ($algo == 'sha512/224') return 210000; // value by owasp.org cheatcheat (28 February 2023) |
643 | else if ($algo == 'sha384') return 600000; |
643 | else if ($algo == 'sha384') return 600000; |
644 | else if ($algo == 'sha256') return 600000; // value by owasp.org cheatcheat (28 February 2023) |
644 | else if ($algo == 'sha256') return 600000; // value by owasp.org cheatcheat (28 February 2023) |
645 | else if ($algo == 'sha224') return 600000; |
645 | else if ($algo == 'sha224') return 600000; |
646 | else if ($algo == 'sha1') return 1300000; // value by owasp.org cheatcheat (28 February 2023) |
646 | else if ($algo == 'sha1') return 1300000; // value by owasp.org cheatcheat (28 February 2023) |
647 | else if ($algo == 'md5') return 5000000; |
647 | else if ($algo == 'md5') return 5000000; |
648 | else return 5000; |
648 | else return 5000; |
649 | } |
649 | } |
650 | } |
650 | } |
651 | 651 | ||
652 | // --- Part 6: Selftest |
652 | // --- Part 6: Selftest |
653 | 653 | ||
654 | /* |
654 | /* |
655 | for ($i=0; $i<9999; $i++) { |
655 | for ($i=0; $i<9999; $i++) { |
656 | assert($i===base64_int_decode(base64_int_encode($i,4))); |
656 | assert($i===base64_int_decode(base64_int_encode($i,4))); |
657 | } |
657 | } |
658 | 658 | ||
659 | $rnd = random_bytes_ex(50, true, true); |
659 | $rnd = random_bytes_ex(50, true, true); |
660 | assert(crypt_radix64_decode(crypt_radix64_encode($rnd)) === $rnd); |
660 | assert(crypt_radix64_decode(crypt_radix64_encode($rnd)) === $rnd); |
661 | 661 | ||
662 | $password = random_bytes_ex(20, false, true); |
662 | $password = random_bytes_ex(20, false, true); |
663 | 663 | ||
664 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_STD_DES))); |
664 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_STD_DES))); |
665 | //echo "'$dummy' ".strlen($dummy)."\n"; |
665 | //echo "'$dummy' ".strlen($dummy)."\n"; |
666 | //var_dump(vts_password_get_info($dummy)); |
666 | //var_dump(vts_password_get_info($dummy)); |
667 | 667 | ||
668 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_EXT_DES))); |
668 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_EXT_DES))); |
669 | //echo "'$dummy' ".strlen($dummy)."\n"; |
669 | //echo "'$dummy' ".strlen($dummy)."\n"; |
670 | //var_dump(vts_password_get_info($dummy)); |
670 | //var_dump(vts_password_get_info($dummy)); |
671 | 671 | ||
672 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_MD5))); |
672 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_MD5))); |
673 | //echo "'$dummy' ".strlen($dummy)."\n"; |
673 | //echo "'$dummy' ".strlen($dummy)."\n"; |
674 | //var_dump(vts_password_get_info($dummy)); |
674 | //var_dump(vts_password_get_info($dummy)); |
675 | 675 | ||
676 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_BLOWFISH))); |
676 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_BLOWFISH))); |
677 | //echo "'$dummy' ".strlen($dummy)."\n"; |
677 | //echo "'$dummy' ".strlen($dummy)."\n"; |
678 | //var_dump(vts_password_get_info($dummy)); |
678 | //var_dump(vts_password_get_info($dummy)); |
679 | 679 | ||
680 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_SHA256))); |
680 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_SHA256))); |
681 | //echo "'$dummy' ".strlen($dummy)."\n"; |
681 | //echo "'$dummy' ".strlen($dummy)."\n"; |
682 | //var_dump(vts_password_get_info($dummy)); |
682 | //var_dump(vts_password_get_info($dummy)); |
683 | 683 | ||
684 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_SHA512))); |
684 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_SHA512))); |
685 | //echo "'$dummy' ".strlen($dummy)."\n"; |
685 | //echo "'$dummy' ".strlen($dummy)."\n"; |
686 | //var_dump(vts_password_get_info($dummy)); |
686 | //var_dump(vts_password_get_info($dummy)); |
687 | 687 | ||
688 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
688 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
689 | 'algo' => 'sha3-512', |
689 | 'algo' => 'sha3-512', |
690 | 'mode' => 'pbkdf2', |
690 | 'mode' => 'pbkdf2', |
691 | 'iterations' => 0 |
691 | 'iterations' => 0 |
692 | )))); |
692 | )))); |
693 | //echo "'$dummy' ".strlen($dummy)."\n"; |
693 | //echo "'$dummy' ".strlen($dummy)."\n"; |
694 | //var_dump(vts_password_get_info($dummy)); |
694 | //var_dump(vts_password_get_info($dummy)); |
695 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
695 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
696 | 'salt_length' => 51, |
696 | 'salt_length' => 51, |
697 | 'algo' => 'sha3-512', |
697 | 'algo' => 'sha3-512', |
698 | 'mode' => 'pbkdf2', |
698 | 'mode' => 'pbkdf2', |
699 | 'iterations' => 0 |
699 | 'iterations' => 0 |
700 | ))); |
700 | ))); |
701 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
701 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
702 | 'salt_length' => 50, |
702 | 'salt_length' => 50, |
703 | 'algo' => 'sha3-256', |
703 | 'algo' => 'sha3-256', |
704 | 'mode' => 'pbkdf2', |
704 | 'mode' => 'pbkdf2', |
705 | 'iterations' => 0 |
705 | 'iterations' => 0 |
706 | ))); |
706 | ))); |
707 | 707 | ||
708 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
708 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
709 | 'algo' => 'sha3-512', |
709 | 'algo' => 'sha3-512', |
710 | 'mode' => 'sps', |
710 | 'mode' => 'sps', |
711 | 'iterations' => 2 |
711 | 'iterations' => 2 |
712 | )))); |
712 | )))); |
713 | //echo "'$dummy' ".strlen($dummy)."\n"; |
713 | //echo "'$dummy' ".strlen($dummy)."\n"; |
714 | //var_dump(vts_password_get_info($dummy)); |
714 | //var_dump(vts_password_get_info($dummy)); |
715 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
715 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
716 | 'salt_length' => 51, |
716 | 'salt_length' => 51, |
717 | 'algo' => 'sha3-512', |
717 | 'algo' => 'sha3-512', |
718 | 'mode' => 'sps', |
718 | 'mode' => 'sps', |
719 | 'iterations' => 2 |
719 | 'iterations' => 2 |
720 | ))); |
720 | ))); |
721 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
721 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
722 | 'salt_length' => 50, |
722 | 'salt_length' => 50, |
723 | 'algo' => 'sha3-256', |
723 | 'algo' => 'sha3-256', |
724 | 'mode' => 'sps', |
724 | 'mode' => 'sps', |
725 | 'iterations' => 2 |
725 | 'iterations' => 2 |
726 | ))); |
726 | ))); |
727 | 727 | ||
728 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
728 | assert(vts_password_verify($password,$dummy = vts_password_hash($password, PASSWORD_VTS_MCF1, array( |
729 | 'algo' => 'sha3-512', |
729 | 'algo' => 'sha3-512', |
730 | 'mode' => 'hmac', |
730 | 'mode' => 'hmac', |
731 | 'iterations' => 2 |
731 | 'iterations' => 2 |
732 | )))); |
732 | )))); |
733 | //echo "'$dummy' ".strlen($dummy)."\n"; |
733 | //echo "'$dummy' ".strlen($dummy)."\n"; |
734 | //var_dump(vts_password_get_info($dummy)); |
734 | //var_dump(vts_password_get_info($dummy)); |
735 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
735 | assert(false===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
736 | 'salt_length' => 51, |
736 | 'salt_length' => 51, |
737 | 'algo' => 'sha3-512', |
737 | 'algo' => 'sha3-512', |
738 | 'mode' => 'hmac', |
738 | 'mode' => 'hmac', |
739 | 'iterations' => 2 |
739 | 'iterations' => 2 |
740 | ))); |
740 | ))); |
741 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
741 | assert(true===vts_password_needs_rehash($dummy,PASSWORD_VTS_MCF1,array( |
742 | 'salt_length' => 50, |
742 | 'salt_length' => 50, |
743 | 'algo' => 'sha3-256', |
743 | 'algo' => 'sha3-256', |
744 | 'mode' => 'hmac', |
744 | 'mode' => 'hmac', |
745 | 'iterations' => 2 |
745 | 'iterations' => 2 |
746 | ))); |
746 | ))); |
747 | */ |
747 | */ |
748 | 748 |