Subversion Repositories php_utils

Rev

Rev 75 | Rev 90 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 75 Rev 76
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://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
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 rewrite the MCF to make sure that they match (if params have the wrong order)
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_1, $calc_authkey_2);
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 = 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 = 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 = 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