Subversion Repositories oidplus

Rev

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

Rev 1102 Rev 1103
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 && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) {
-
 
148
                                $bits = explode('-',$algo)[1];
146
                                $bin_hash = sha3_512($payload, true);
149
                                $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true);
147
                        } else {
150
                        } else {
148
                                $bin_hash = hash($algo, $payload, true);
151
                                $bin_hash = hash($algo, $payload, true);
149
                        }
152
                        }
150
                } else if ($mode == 'ps') {
153
                } else if ($mode == 'ps') {
151
                        $payload = $str_password.$str_salt;
154
                        $payload = $str_password.$str_salt;
-
 
155
                        $algo_supported_natively = in_array($algo, hash_algos());
152
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
156
                        if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) {
-
 
157
                                $bits = explode('-',$algo)[1];
153
                                $bin_hash = sha3_512($payload, true);
158
                                $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true);
154
                        } else {
159
                        } else {
155
                                $bin_hash = hash($algo, $payload, true);
160
                                $bin_hash = hash($algo, $payload, true);
156
                        }
161
                        }
157
                } else if ($mode == 'sps') {
162
                } else if ($mode == 'sps') {
158
                        $payload = $str_salt.$str_password.$str_salt;
163
                        $payload = $str_salt.$str_password.$str_salt;
-
 
164
                        $algo_supported_natively = in_array($algo, hash_algos());
159
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
165
                        if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash')) {
-
 
166
                                $bits = explode('-',$algo)[1];
160
                                $bin_hash = sha3_512($payload, true);
167
                                $bin_hash = \bb\Sha3\Sha3::hash($payload, $bits, true);
161
                        } else {
168
                        } else {
162
                                $bin_hash = hash($algo, $payload, true);
169
                                $bin_hash = hash($algo, $payload, true);
163
                        }
170
                        }
164
                } else if ($mode == 'hmac') {
171
                } else if ($mode == 'hmac') {
-
 
172
                        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
173
                                $algo_supported_natively = in_array($algo, hash_hmac_algos());
-
 
174
                        } else {
-
 
175
                                $algo_supported_natively = in_array($algo, hash_algos());
-
 
176
                        }
166
                        if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512_hmac')) {
177
                        if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_hmac')) {
-
 
178
                                $bits = explode('-',$algo)[1];
167
                                $bin_hash = sha3_512_hmac($str_password, $str_salt, true);
179
                                $bin_hash = \bb\Sha3\Sha3::hash_hmac($str_password, $str_salt, $bits, true);
168
                        } else {
180
                        } else {
169
                                $bin_hash = hash_hmac($algo, $str_password, $str_salt, true);
181
                                $bin_hash = hash_hmac($algo, $str_password, $str_salt, true);
170
                        }
182
                        }
-
 
183
                } else if ($mode == 'pbkdf2') {
-
 
184
                        $algo_supported_natively = in_array($algo, hash_algos());
-
 
185
                        if (!$algo_supported_natively && str_starts_with($algo, 'sha3-') && method_exists('\bb\Sha3\Sha3', 'hash_pbkdf2')) {
-
 
186
                                if ($iterations == 0) {
-
 
187
                                        $iterations = 2000; // because userland implementations are much slower, we must choose a small value...
-
 
188
                                }
-
 
189
                                $bits = explode('-',$algo)[1];
-
 
190
                                $bin_hash = \bb\Sha3\Sha3::hash_pbkdf2($str_password, $str_salt, $iterations, $bits, 0, true);
-
 
191
                        } else {
-
 
192
                                if ($iterations == 0) {
-
 
193
                                        // Recommendations taken from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
-
 
194
                                        // I am not sure if these recommendations are correct. They write PBKDF2-HMAC-SHA1...
-
 
195
                                        // Does this count for us, or does hash_pbkdf2() implement PBKDF2-SHA1 rather than PBKDF2-HMAC-SHA1?
-
 
196
                                        if      ($algo == 'sha3-512')    $iterations =  100000;
-
 
197
                                        else if ($algo == 'sha3-384')    $iterations =  100000;
-
 
198
                                        else if ($algo == 'sha3-256')    $iterations =  100000;
-
 
199
                                        else if ($algo == 'sha3-224')    $iterations =  100000;
-
 
200
                                        else if ($algo == 'sha512')      $iterations =  210000; // value by owasp.org cheatcheat (28.02.2023)
-
 
201
                                        else if ($algo == 'sha512/256')  $iterations =  210000; // value by owasp.org cheatcheat (28.02.2023)
-
 
202
                                        else if ($algo == 'sha512/224')  $iterations =  210000; // value by owasp.org cheatcheat (28.02.2023)
-
 
203
                                        else if ($algo == 'sha384')      $iterations =  600000;
-
 
204
                                        else if ($algo == 'sha256')      $iterations =  600000; // value by owasp.org cheatcheat (28.02.2023)
-
 
205
                                        else if ($algo == 'sha224')      $iterations =  600000;
-
 
206
                                        else if ($algo == 'sha1')        $iterations = 1300000; // value by owasp.org cheatcheat (28.02.2023)
-
 
207
                                        else if ($algo == 'md5')         $iterations = 5000000;
-
 
208
                                        else                             $iterations =    5000;
-
 
209
                                }
-
 
210
                                $bin_hash = hash_pbkdf2($algo, $str_password, $str_salt, $iterations, 0, true);
-
 
211
                        }
171
                } else {
212
                } else {
172
                        throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac.");
213
                        throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, hmac, or pbkdf2.");
173
                }
214
                }
174
                $bin_salt = $str_salt;
215
                $bin_salt = $str_salt;
-
 
216
                $params = array();
-
 
217
                $params['a'] = $algo;
-
 
218
                $params['m'] = $mode;
-
 
219
                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));
220
                return crypt_modular_format_encode(OID_MCF_VTS_V1, $bin_salt, $bin_hash, $params);
176
        } else {
221
        } else {
177
                throw new Exception("Invalid VTS crypt version, expect 1.");
222
                throw new Exception("Invalid VTS crypt version, expect 1.");
178
        }
223
        }
179
}
224
}
180
 
225
 
Line 186... Line 231...
186
                if ($data === false) throw new Exception('Invalid auth key');
231
                if ($data === false) throw new Exception('Invalid auth key');
187
                $id = $data['id'];
232
                $id = $data['id'];
188
                $bin_salt = $data['salt'];
233
                $bin_salt = $data['salt'];
189
                $bin_hash = $data['hash'];
234
                $bin_hash = $data['hash'];
190
                $params = $data['params'];
235
                $params = $data['params'];
-
 
236
 
-
 
237
                if (!isset($params['a'])) throw new Exception('Param "a" (algo) missing');
191
                $algo = $params['a'];
238
                $algo = $params['a'];
-
 
239
 
-
 
240
                if (!isset($params['m'])) throw new Exception('Param "m" (mode) missing');
192
                $mode = $params['m'];
241
                $mode = $params['m'];
193
 
242
 
-
 
243
                if ($mode == 'pbkdf2') {
-
 
244
                        if (!isset($params['i'])) throw new Exception('Param "i" (iterations) missing');
-
 
245
                        $iterations = $params['i'];
-
 
246
                } else {
-
 
247
                        $iterations = 0;
-
 
248
                }
-
 
249
 
194
                // Create a VTS MCF 1.0 hash based on the parameters of $hash and the password $password
250
                // 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);
251
                $calc_authkey_1 = vts_crypt_hash($algo, $password, $bin_salt, $ver, $mode, $iterations);
196
 
252
 
197
                // We rewrite the MCF to make sure that they match (if params have the wrong order)
253
                // 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);
254
                $calc_authkey_2 = crypt_modular_format_encode($id, $bin_salt, $bin_hash, $params);
199
 
255
 
200
                return hash_equals($calc_authkey_1, $calc_authkey_2);
256
                return hash_equals($calc_authkey_1, $calc_authkey_2);
Line 270... Line 326...
270
        } else if ($algo === PASSWORD_VTS_MCF1) {
326
        } else if ($algo === PASSWORD_VTS_MCF1) {
271
                // Algorithms: PASSWORD_VTS_MCF1
327
                // Algorithms: PASSWORD_VTS_MCF1
272
                $ver  = '1';
328
                $ver  = '1';
273
                $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512';
329
                $algo = isset($options['algo']) ? $options['algo'] : 'sha3-512';
274
                $mode = isset($options['mode']) ? $options['mode'] : 'ps';
330
                $mode = isset($options['mode']) ? $options['mode'] : 'ps';
-
 
331
                $iterations = isset($options['iterations']) ? $options['iterations'] : 0/*default*/;
275
                $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50;
332
                $salt_len = isset($options['salt_length']) ? $options['salt_length'] : 50;
276
                $salt = random_bytes_ex($salt_len, true, true);
333
                $salt = random_bytes_ex($salt_len, true, true);
277
                return vts_crypt_hash($algo, $password, $salt, $ver, $mode);
334
                return vts_crypt_hash($algo, $password, $salt, $ver, $mode, $iterations);
278
        } else {
335
        } else {
279
                // Algorithms: PASSWORD_DEFAULT
336
                // Algorithms: PASSWORD_DEFAULT
280
                //             PASSWORD_BCRYPT
337
                //             PASSWORD_BCRYPT
281
                //             PASSWORD_ARGON2I
338
                //             PASSWORD_ARGON2I
282
                //             PASSWORD_ARGON2ID
339
                //             PASSWORD_ARGON2ID
Line 341... Line 398...
341
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES)));
398
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_EXT_DES)));
342
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5)));
399
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_MD5)));
343
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH)));
400
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BLOWFISH)));
344
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256)));
401
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA256)));
345
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512)));
402
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_SHA512)));
346
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_VTS_MCF1)));
403
assert(vts_password_verify($password,$debug = vts_password_hash($password, PASSWORD_VTS_MCF1, array(
-
 
404
        'algo' => 'sha3-512',
-
 
405
        'mode' => 'pbkdf2',
-
 
406
        'iterations' => 5000
-
 
407
))));
-
 
408
echo "$debug\n";
347
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT)));
409
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_DEFAULT)));
348
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT)));
410
assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_BCRYPT)));
349
if (defined('PASSWORD_ARGON2I'))
411
if (defined('PASSWORD_ARGON2I'))
350
        assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I)));
412
        assert(vts_password_verify($password,vts_password_hash($password, PASSWORD_ARGON2I)));
351
if (defined('PASSWORD_ARGON2ID'))
413
if (defined('PASSWORD_ARGON2ID'))