Subversion Repositories php_utils

Rev

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

Rev 64 Rev 65
Line 1... Line 1...
1
<?php
1
<?php
2
 
2
 
3
/*
3
/*
4
 * ViaThinkSoft Modular Crypt Format 1.0 / vts_password_hash() / vts_password_verify()
4
 * ViaThinkSoft Modular Crypt Format 1.0 / vts_password_hash() / vts_password_verify()
5
 * Copyright 2023 Daniel Marschall, ViaThinkSoft
5
 * Copyright 2023 Daniel Marschall, ViaThinkSoft
6
 * Revision 2023-02-27
6
 * Revision 2023-02-28
7
 *
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
10
 * You may obtain a copy of the License at
11
 *
11
 *
Line 46... Line 46...
46
Valid <mode> :
46
Valid <mode> :
47
        sp = salt + password
47
        sp = salt + password
48
        ps = password + salt
48
        ps = password + salt
49
        sps = salt + password + salt
49
        sps = salt + password + salt
50
        hmac = HMAC (salt is the key)
50
        hmac = HMAC (salt is the key)
-
 
51
        pbkdf2 = PBKDF2 (Additional param i= contains the number of iterations)
51
Like most Crypt-hashes, <salt> and <hash> are Radix64 coded
52
Like most Crypt-hashes, <salt> and <hash> are Radix64 coded
52
with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding.
53
with alphabet './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' and no padding.
53
Link to the online specification:
54
Link to the online specification:
54
        https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1
55
        https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1
55
Reference implementation in PHP:
56
Reference implementation in PHP:
Line 136... Line 137...
136
        } else {
137
        } else {
137
                return '0';
138
                return '0';
138
        }
139
        }
139
}
140
}
140
 
141
 
141
function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode='ps') {
142
function vts_crypt_hash($algo, $str_password, $str_salt, $ver='1', $mode='ps', $iterations=0/*default*/) {
142
        if ($ver == '1') {
143
        if ($ver == '1') {
143
                if ($mode == 'sp') {
144
                if ($mode == 'sp') {
144
                        $payload = $str_salt.$str_password;
145
                        $payload = $str_salt.$str_password;
-
 
146
                        $algo_supported_natively = in_array($algo, hash_algos());
145
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
147
                        if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512')) {
146
                                $bin_hash = sha3_512($payload, true);
148
                                $bin_hash = sha3_512($payload, true);
147
                        } else {
149
                        } else {
148
                                $bin_hash = hash($algo, $payload, true);
150
                                $bin_hash = hash($algo, $payload, true);
149
                        }
151
                        }
150
                } else if ($mode == 'ps') {
152
                } else if ($mode == 'ps') {
151
                        $payload = $str_password.$str_salt;
153
                        $payload = $str_password.$str_salt;
-
 
154
                        $algo_supported_natively = in_array($algo, hash_algos());
152
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
155
                        if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512')) {
153
                                $bin_hash = sha3_512($payload, true);
156
                                $bin_hash = sha3_512($payload, true);
154
                        } else {
157
                        } else {
155
                                $bin_hash = hash($algo, $payload, true);
158
                                $bin_hash = hash($algo, $payload, true);
156
                        }
159
                        }
157
                } else if ($mode == 'sps') {
160
                } else if ($mode == 'sps') {
158
                        $payload = $str_salt.$str_password.$str_salt;
161
                        $payload = $str_salt.$str_password.$str_salt;
-
 
162
                        $algo_supported_natively = in_array($algo, hash_algos());
159
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
163
                        if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512')) {
160
                                $bin_hash = sha3_512($payload, true);
164
                                $bin_hash = sha3_512($payload, true);
161
                        } else {
165
                        } else {
162
                                $bin_hash = hash($algo, $payload, true);
166
                                $bin_hash = hash($algo, $payload, true);
163
                        }
167
                        }
164
                } else if ($mode == 'hmac') {
168
                } else if ($mode == 'hmac') {
-
 
169
                        if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
165
                        // Note: Actually, we should use hash_hmac_algos(), but this requires PHP 7.2, and we would like to stay compatible with PHP 7.0 for now
170
                                $algo_supported_natively = in_array($algo, hash_hmac_algos());
-
 
171
                        } else {
-
 
172
                                $algo_supported_natively = in_array($algo, hash_algos());
-
 
173
                        }
166
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512_hmac')) {
174
                        if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512_hmac')) {
167
                                $bin_hash = sha3_512_hmac($str_password, $str_salt, true);
175
                                $bin_hash = sha3_512_hmac($str_password, $str_salt, true);
168
                        } else {
176
                        } else {
169
                                $bin_hash = hash_hmac($algo, $str_password, $str_salt, true);
177
                                $bin_hash = hash_hmac($algo, $str_password, $str_salt, true);
170
                        }
178
                        }
-
 
179
                } else if ($mode == 'pbkdf2') {
-
 
180
                        if ($iterations == 0) {
-
 
181
                                // TODO: Find good value for iterations, see https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
-
 
182
                                $iterations = 500000;
-
 
183
                        }
-
 
184
                        $algo_supported_natively = in_array($algo, hash_algos());
-
 
185
                        if (!$algo_supported_natively && ($algo === 'sha3-512') && function_exists('sha3_512_pbkdf2')) {
-
 
186
                                $bin_hash = sha3_512_pbkdf2($str_password, $str_salt, $iterations, 0, true);
-
 
187
                        } else {
-
 
188
                                $bin_hash = hash_pbkdf2($algo, $str_password, $str_salt, $iterations, 0, true);
-
 
189
                        }
171
                } else {
190
                } else {
172
                        throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac.");
191
                        throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2.");
173
                }
192
                }
174
                $bin_salt = $str_salt;
193
                $bin_salt = $str_salt;
-
 
194
                $params = array();
-
 
195
                $params['a'] = $algo;
-
 
196
                $params['m'] = $mode;
-
 
197
                if ($mode == 'pbkdf2') $params['i'] = $iterations;
175
                return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode));
198
                return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params);
176
        } else {
199
        } else {
177
                throw new Exception("Invalid VTS crypt version, expect 1.");
200
                throw new Exception("Invalid VTS crypt version, expect 1.");
178
        }
201
        }
179
}
202
}
180
 
203
 
Line 186... Line 209...
186
                if ($data === false) throw new Exception('Invalid auth key');
209
                if ($data === false) throw new Exception('Invalid auth key');
187
                $id = $data['id'];
210
                $id = $data['id'];
188
                $bin_salt = $data['salt'];
211
                $bin_salt = $data['salt'];
189
                $bin_hash = $data['hash'];
212
                $bin_hash = $data['hash'];
190
                $params = $data['params'];
213
                $params = $data['params'];
-
 
214
 
-
 
215
                if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing');
191
                $algo = $params['a'];
216
                $algo = $params['a'];
-
 
217
 
-
 
218
                if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing');
192
                $mode = $params['m'];
219
                $mode = $params['m'];
193
 
220
 
-
 
221
                if (($mode == 'pbkdf2') && !isset($params['i'])) throw new Exception('Param "i" (iterations) missing');
-
 
222
                $iterations = $params['i'];
-
 
223
 
194
                // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password
224
                // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password
195
                $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode);
225
                $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations);
196
 
226
 
197
                // We rewrite the MCF to make sure that they match (if params have the wrong order)
227
                // We rewrite the MCF to make sure that they match (if params have the wrong order)
198
                $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params);
228
                $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params);
199
 
229
 
200
                return hash_equals($calc_authkey_1, $calc_authkey_2);
230
                return hash_equals($calc_authkey_1, $calc_authkey_2);
Line 270... Line 300...
270
        } else if ($algo === PASSWORD_VTS_MCF1) {
300
        } else if ($algo === PASSWORD_VTS_MCF1) {
271
                // Algorithms: PASSWORD_VTS_MCF1
301
                // Algorithms: PASSWORD_VTS_MCF1
272
                $ver  = '1';
302
                $ver  = '1';
273
                $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512';
303
                $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512';
274
                $mode = isset($options['mode']) ? $options['mode'] : 'ps';
304
                $mode = isset($options['mode']) ? $options['mode'] : 'ps';
-
 
305
                $iterations = isset($options['iterations']) ? $options['iterations'] : 0/*default*/;
275
                $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50;
306
                $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50;
276
                $salt = random_bytes_ex($salt_len, true, true);
307
                $salt = random_bytes_ex($salt_len, true, true);
277
                return vts_crypt_hash($algo, $password, $salt, $ver, $mode);
308
                return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations);
278
        } else {
309
        } else {
279
                // Algorithms: PASSWORD_DEFAULT
310
                // Algorithms: PASSWORD_DEFAULT
280
                //             PASSWORD_BCRYPT
311
                //             PASSWORD_BCRYPT
281
                //             PASSWORD_ARGON2I
312
                //             PASSWORD_ARGON2I
282
                //             PASSWORD_ARGON2ID
313
                //             PASSWORD_ARGON2ID
Line 341... Line 372...
341
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES)));
372
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES)));
342
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5)));
373
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5)));
343
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH)));
374
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH)));
344
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256)));
375
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256)));
345
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512)));
376
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512)));
346
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_VTS_MCF1)));
377
assert(vts_password_verify($password,$debug = vts_password_hash($password, PASSWORD_VTS_MCF1, array(
-
 
378
        'algo' => 'sha3-512',
-
 
379
        'mode' => 'pbkdf2',
-
 
380
        'iterations' => 5000
-
 
381
))));
-
 
382
echo "$debug\n";
347
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT)));
383
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT)));
348
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT)));
384
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT)));
349
if (defined('PASSWORD_ARGON2I'))
385
if (defined('PASSWORD_ARGON2I'))
350
        assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I)));
386
        assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I)));
351
if (defined('PASSWORD_ARGON2ID'))
387
if (defined('PASSWORD_ARGON2ID'))