Subversion Repositories php_utils

Rev

Rev 63 | Rev 65 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 63 Rev 64
Line 59... Line 59...
59
 
59
 
60
require_once __DIR__ . '/misc_functions.inc.php';
60
require_once __DIR__ . '/misc_functions.inc.php';
61
 
61
 
62
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) }
62
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) }
63
 
63
 
-
 
64
// Valid algorithms for vts_password_hash():
64
define('PASSWORD_STD_DES',   'std_des');
65
define('PASSWORD_STD_DES',   'std_des');       // Algorithm from crypt()
65
define('PASSWORD_EXT_DES',   'ext_des');
66
define('PASSWORD_EXT_DES',   'ext_des');       // Algorithm from crypt()
66
define('PASSWORD_MD5',       'md5');
67
define('PASSWORD_MD5',       'md5');           // Algorithm from crypt()
67
define('PASSWORD_BLOWFISH',  'blowfish');
68
define('PASSWORD_BLOWFISH',  'blowfish');      // Algorithm from crypt()
68
define('PASSWORD_SHA256',    'sha256');
69
define('PASSWORD_SHA256',    'sha256');        // Algorithm from crypt()
69
define('PASSWORD_SHA512',    'sha512');
70
define('PASSWORD_SHA512',    'sha512');        // Algorithm from crypt()
70
define('PASSWORD_VTS_MCF1',  OID_MCF_VTS_V1);
71
define('PASSWORD_VTS_MCF1',  OID_MCF_VTS_V1);  // Algorithm from ViaThinkSoft
71
 
-
 
72
define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/');
72
// Other valid values (already defined in PHP):
73
define('BASE64_CRYPT_ALPHABET',   './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
73
// - PASSWORD_DEFAULT
-
 
74
// - PASSWORD_BCRYPT
-
 
75
// - PASSWORD_ARGON2I
-
 
76
// - PASSWORD_ARGON2ID
74
 
77
 
75
// --- Part 1: Modular Crypt Format encode/decode
78
// --- Part 1: Modular Crypt Format encode/decode
76
 
79
 
77
function crypt_modular_format($id, $bin_salt, $bin_hash, $params=null) {
80
function crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params=null) {
78
        // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
81
        // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
79
        $out = '$'.$id;
82
        $out = '$'.$id;
80
        if (!is_null($params)) {
83
        if (!is_null($params)) {
81
                $ary_params = array();
84
                $ary_params = array();
82
                //ksort($params);
-
 
83
                foreach ($params as $name => $value) {
85
                foreach ($params as $name => $value) {
84
                        $ary_params[] = "$name=$value";
86
                        $ary_params[] = "$name=$value";
85
                }
87
                }
86
                $out .= '$'.implode(',',$ary_params);
88
                $out .= '$'.implode(',',$ary_params);
87
        }
89
        }
Line 126... Line 128...
126
        return array('id' => $id, 'salt' => $bin_salt, 'hash' => $bin_hash, 'params' => $params);
128
        return array('id' => $id, 'salt' => $bin_salt, 'hash' => $bin_hash, 'params' => $params);
127
}
129
}
128
 
130
 
129
// --- Part 2: ViaThinkSoft Modular Crypt Format 1.0
131
// --- Part 2: ViaThinkSoft Modular Crypt Format 1.0
130
 
132
 
-
 
133
function vts_crypt_version($hash) {
-
 
134
        if (str_starts_with($hash, '$'.OID_MCF_VTS_V1.'$')) {
-
 
135
                return '1';
-
 
136
        } else {
-
 
137
                return '0';
-
 
138
        }
-
 
139
}
-
 
140
 
131
function vts_crypt($algo, $str_password, $str_salt, $ver='1', $mode='ps') {
141
function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode='ps') {
132
        if ($ver == '1') {
142
        if ($ver == '1') {
133
                if ($mode == 'sp') {
143
                if ($mode == 'sp') {
134
                        $payload = $str_salt.$str_password;
144
                        $payload = $str_salt.$str_password;
135
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
145
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
136
                                $bin_hash = sha3_512($payload, true);
146
                                $bin_hash = sha3_512($payload, true);
Line 160... Line 170...
160
                        }
170
                        }
161
                } else {
171
                } else {
162
                        throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac.");
172
                        throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac.");
163
                }
173
                }
164
                $bin_salt = $str_salt;
174
                $bin_salt = $str_salt;
165
                return crypt_modular_format(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode));
175
                return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode));
166
        } else {
176
        } else {
167
                throw new Exception("Invalid VTS crypt version, expect 1.");
177
                throw new Exception("Invalid VTS crypt version, expect 1.");
168
        }
178
        }
169
}
179
}
170
 
180
 
171
// --- Part 3: vts_password_hash() and vts_password_verify()
-
 
172
 
-
 
173
/** This function extends password_verify() by adding ViaThinkSoft Modular Crypt Format 1.0.
-
 
174
 * @param string $password to be checked
-
 
175
 * @param string $hash Hash created by crypt(), password_hash(), or vts_password_hash().
-
 
176
 * @return bool true if password is valid
181
function vts_crypt_verify($password, $hash): bool {
177
 */
-
 
178
function vts_password_verify($password, $hash): bool {
182
        $ver = vts_crypt_version($hash);
179
        if (str_starts_with($hash, '$'.PASSWORD_VTS_MCF1.'$')) {
183
        if ($ver == '1') {
180
 
-
 
181
                // Decode the MCF hash parameters
184
                // Decode the MCF hash parameters
182
                $data = crypt_modular_format_decode($hash);
185
                $data = crypt_modular_format_decode($hash);
183
                if ($data === false) throw new Exception('Invalid auth key');
186
                if ($data === false) throw new Exception('Invalid auth key');
184
                $id = $data['id'];
187
                $id = $data['id'];
185
                $bin_salt = $data['salt'];
188
                $bin_salt = $data['salt'];
186
                $bin_hash = $data['hash'];
189
                $bin_hash = $data['hash'];
187
                $params = $data['params'];
190
                $params = $data['params'];
188
                $algo = $params['a'];
191
                $algo = $params['a'];
189
                $mode = $params['m'];
192
                $mode = $params['m'];
190
                $ver = '1';
-
 
191
 
193
 
192
                // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password
194
                // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password
193
                $calc_authkey_1 = vts_crypt($algo, $password, $bin_salt, $ver, $mode);
195
                $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode);
194
 
196
 
195
                // We rewrite the MCF to make sure that they match (if params) have the wrong order
197
                // We rewrite the MCF to make sure that they match (if params have the wrong order)
196
                $calc_authkey_2 = crypt_modular_format($id, $bin_salt, $bin_hash, $params);
198
                $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params);
197
 
199
 
198
                return hash_equals($calc_authkey_1, $calc_authkey_2);
200
                return hash_equals($calc_authkey_1, $calc_authkey_2);
-
 
201
        } else {
-
 
202
                throw new Exception("Invalid VTS crypt version, expect 1.");
-
 
203
        }
-
 
204
}
199
 
205
 
-
 
206
// --- Part 3: vts_password_hash() and vts_password_verify()
-
 
207
 
-
 
208
/** This function extends password_verify() by adding ViaThinkSoft Modular Crypt Format 1.0.
-
 
209
 * @param string $password to be checked
-
 
210
 * @param string $hash Hash created by crypt(), password_hash(), or vts_password_hash().
-
 
211
 * @return bool true if password is valid
-
 
212
 */
-
 
213
function vts_password_verify($password, $hash): bool {
-
 
214
        if (vts_crypt_version($hash) != '0') {
-
 
215
                // Hash created by vts_password_hash(), or vts_crypt_hash()
-
 
216
                return vts_crypt_verify($password, $hash);
200
        } else {
217
        } else {
201
                // password_hash() and crypt() hashes
218
                // Hash created by vts_password_hash(), password_hash(), or crypt()
202
                return password_verify($password, $hash);
219
                return password_verify($password, $hash);
203
        }
220
        }
204
}
221
}
205
 
222
 
206
/** This function extends password_hash() with the algorithms supported by crypt().
223
/** This function extends password_hash() with the algorithms supported by crypt().
207
 * It also adds ViaThinkSoft Modular Crypt Format 1.0.
224
 * It also adds vts_crypt_hash() which implements the ViaThinkSoft Modular Crypt Format 1.0.
208
 * The result can be verified using vts_password_verify().
225
 * The result can be verified using vts_password_verify().
209
 * @param string $password to be hashed
226
 * @param string $password to be hashed
210
 * @param mixed $algo algorithm
227
 * @param mixed $algo algorithm
211
 * @param array $options options for the hashing algorithm
228
 * @param array $options options for the hashing algorithm
212
 * @return string Crypt compatible password hash
229
 * @return string Crypt style password hash
213
 */
230
 */
214
function vts_password_hash($password, $algo, $options=array()): string {
231
function vts_password_hash($password, $algo, $options=array()): string {
215
        $crypt_salt = null;
232
        $crypt_salt = null;
216
        if (($algo === PASSWORD_STD_DES) && defined('CRYPT_STD_DES')) {
233
        if (($algo === PASSWORD_STD_DES) && defined('CRYPT_STD_DES')) {
217
                // 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.
234
                // 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.
Line 239... Line 256...
239
                $rounds = isset($options['rounds']) ? $options['rounds'] : 5000;
256
                $rounds = isset($options['rounds']) ? $options['rounds'] : 5000;
240
                $crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$';
257
                $crypt_salt = $algo.'rounds='.$rounds.'$'.des_compat_salt(16).'$';
241
        }
258
        }
242
 
259
 
243
        if (!is_null($crypt_salt)) {
260
        if (!is_null($crypt_salt)) {
-
 
261
                // Algorithms: PASSWORD_STD_DES
-
 
262
                //             PASSWORD_EXT_DES
-
 
263
                //             PASSWORD_MD5
-
 
264
                //             PASSWORD_BLOWFISH
-
 
265
                //             PASSWORD_SHA256
-
 
266
                //             PASSWORD_SHA512
244
                $out = crypt($password, $crypt_salt);
267
                $out = crypt($password, $crypt_salt);
245
                if (strlen($out) < 13) throw new Exception("crypt() failed");
268
                if (strlen($out) < 13) throw new Exception("crypt() failed");
246
                return $out;
269
                return $out;
247
        } else if ($algo === PASSWORD_VTS_MCF1) {
270
        } else if ($algo === PASSWORD_VTS_MCF1) {
-
 
271
                // Algorithms: PASSWORD_VTS_MCF1
248
                $ver  = '1';
272
                $ver  = '1';
249
                $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512';
273
                $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512';
250
                $mode = isset($options['mode']) ? $options['mode'] : 'ps';
274
                $mode = isset($options['mode']) ? $options['mode'] : 'ps';
251
                $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50;
275
                $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50;
252
                $salt = random_bytes_ex($salt_len, true, true);
276
                $salt = random_bytes_ex($salt_len, true, true);
253
                return vts_crypt($algo, $password, $salt, $ver, $mode);
277
                return vts_crypt_hash($algo, $password, $salt, $ver, $mode);
254
        } else {
278
        } else {
255
                // $algo === PASSWORD_DEFAULT
279
                // Algorithms: PASSWORD_DEFAULT
256
                // $algo === PASSWORD_BCRYPT
280
                //             PASSWORD_BCRYPT
257
                // $algo === PASSWORD_ARGON2I
281
                //             PASSWORD_ARGON2I
258
                // $algo === PASSWORD_ARGON2ID
282
                //             PASSWORD_ARGON2ID
259
                return password_hash($password, $algo, $options);
283
                return password_hash($password, $algo, $options);
260
        }
284
        }
261
}
285
}
262
 
286
 
263
// --- Part 4: Useful functions required by the above functions
287
// --- Part 4: Useful functions required by the crypt-functions
-
 
288
 
-
 
289
define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/');
-
 
290
define('BASE64_CRYPT_ALPHABET',   './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
264
 
291
 
265
function des_compat_salt($salt_len) {
292
function des_compat_salt($salt_len) {
266
        if ($salt_len <= 0) return '';
293
        if ($salt_len <= 0) return '';
267
        $characters = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
294
        $characters = BASE64_CRYPT_ALPHABET;
268
        $salt = '';
295
        $salt = '';
269
        $bytes = random_bytes_ex($salt_len, true, true);
296
        $bytes = random_bytes_ex($salt_len, true, true);
270
        for ($i=0; $i<$salt_len; $i++) {
297
        for ($i=0; $i<$salt_len; $i++) {
271
                $salt .= $characters[ord($bytes[$i]) % strlen($characters)];
298
                $salt .= $characters[ord($bytes[$i]) % strlen($characters)];
272
        }
299
        }
273
        return $salt;
300
        return $salt;
274
}
301
}
275
 
302
 
276
function base64_int_encode($num) {
303
function base64_int_encode($num) {
277
        // https://stackoverflow.com/questions/15534982/which-iteration-rules-apply-on-crypt-using-crypt-ext-des
304
        // https://stackoverflow.com/questions/15534982/which-iteration-rules-apply-on-crypt-using-crypt-ext-des
278
        $alphabet_raw='./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
305
        $alphabet_raw = BASE64_CRYPT_ALPHABET;
279
        $alphabet=str_split($alphabet_raw);
306
        $alphabet = str_split($alphabet_raw);
280
        $arr=array();
307
        $arr = array();
281
        $base=sizeof($alphabet);
308
        $base = sizeof($alphabet);
282
        while($num) {
309
        while ($num) {
283
                $rem=$num % $base;
310
                $rem = $num % $base;
Line 289... Line 316...
289
}
316
}
290
 
317
 
291
function crypt_radix64_encode($str) {
318
function crypt_radix64_encode($str) {
292
        $x = $str;
319
        $x = $str;
293
        $x = base64_encode($x);
320
        $x = base64_encode($x);
294
        $x = rtrim($x, '=');
321
        $x = rtrim($x, '='); // remove padding
295
        $x = strtr($x, BASE64_RFC4648_ALPHABET, BASE64_CRYPT_ALPHABET);
322
        $x = strtr($x, BASE64_RFC4648_ALPHABET, BASE64_CRYPT_ALPHABET);
296
        return $x;
323
        return $x;
297
}
324
}
298
 
325
 
299
function crypt_radix64_decode($str) {
326
function crypt_radix64_decode($str) {
Line 304... Line 331...
304
}
331
}
305
 
332
 
306
// --- Part 5: Selftest
333
// --- Part 5: Selftest
307
 
334
 
308
/*
335
/*
-
 
336
$rnd = random_bytes_ex(50, true, true);
309
assert(crypt_radix64_decode(crypt_radix64_encode('test123')) === 'test123');
337
assert(crypt_radix64_decode(crypt_radix64_encode($rnd)) === $rnd);
310
 
338
 
-
 
339
$password = random_bytes_ex(20, false, true);
311
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_STD_DES)));
340
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_STD_DES)));
312
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_EXT_DES)));
341
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES)));
313
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_MD5)));
342
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5)));
314
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_BLOWFISH)));
343
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH)));
315
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_SHA256)));
344
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256)));
316
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_SHA512)));
345
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512)));
317
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_VTS_MCF1)));
346
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_VTS_MCF1)));
318
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_DEFAULT)));
347
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT)));
319
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_BCRYPT)));
348
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT)));
-
 
349
if (defined('PASSWORD_ARGON2I'))
320
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_ARGON2I)));
350
        assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I)));
-
 
351
if (defined('PASSWORD_ARGON2ID'))
321
assert(vts_password_Verify('test123',vts_password_hash('test123', PASSWORD_ARGON2ID)));
352
        assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2ID)));
-
 
353
echo "OK, Password $password\n";
322
*/
354
*/
323
 
-