Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/PassHash.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
3
4
namespace dokuwiki;
5
6
/**
7
 * Password Hashing Class
8
 *
9
 * This class implements various mechanisms used to hash passwords
10
 *
11
 * @author  Andreas Gohr <[email protected]>
12
 * @author  Schplurtz le Déboulonné <[email protected]>
13
 * @license LGPL2
14
 */
15
class PassHash {
16
    /**
17
     * Verifies a cleartext password against a crypted hash
18
     *
19
     * The method and salt used for the crypted hash is determined automatically,
20
     * then the clear text password is crypted using the same method. If both hashs
21
     * match true is is returned else false
22
     *
23
     * @author  Andreas Gohr <[email protected]>
24
     * @author  Schplurtz le Déboulonné <[email protected]>
25
     *
26
     * @param string $clear Clear-Text password
27
     * @param string $hash  Hash to compare against
28
     * @return  bool
29
     */
30
    public function verify_hash($clear, $hash) {
31
        $method = '';
0 ignored issues
show
$method is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
32
        $salt   = '';
33
        $magic  = '';
34
35
        //determine the used method and salt
36
        if (substr($hash, 0, 2) == 'U$') {
37
            // This may be an updated password from user_update_7000(). Such hashes
38
            // have 'U' added as the first character and need an extra md5().
39
            $hash = substr($hash, 1);
40
            $clear = md5($clear);
41
        }
42
        $len = strlen($hash);
43
        if(preg_match('/^\$1\$([^\$]{0,8})\$/', $hash, $m)) {
44
            $method = 'smd5';
45
            $salt   = $m[1];
46
            $magic  = '1';
47
        } elseif(preg_match('/^\$apr1\$([^\$]{0,8})\$/', $hash, $m)) {
48
            $method = 'apr1';
49
            $salt   = $m[1];
50
            $magic  = 'apr1';
51
        } elseif(preg_match('/^\$S\$(.{52})$/', $hash, $m)) {
52
            $method = 'drupal_sha512';
53
            $salt   = $m[1];
54
            $magic  = 'S';
55
        } elseif(preg_match('/^\$P\$(.{31})$/', $hash, $m)) {
56
            $method = 'pmd5';
57
            $salt   = $m[1];
58
            $magic  = 'P';
59
        } elseif(preg_match('/^\$H\$(.{31})$/', $hash, $m)) {
60
            $method = 'pmd5';
61
            $salt = $m[1];
62
            $magic = 'H';
63
        } elseif(preg_match('/^pbkdf2_(\w+?)\$(\d+)\$(.{12})\$/', $hash, $m)) {
64
            $method = 'djangopbkdf2';
65
            $magic = array(
66
                'algo' => $m[1],
67
                'iter' => $m[2],
68
            );
69
            $salt = $m[3];
70
        } elseif(preg_match('/^PBKDF2(SHA\d+)\$(\d+)\$([[:xdigit:]]+)\$([[:xdigit:]]+)$/', $hash, $m)) {
71
            $method = 'seafilepbkdf2';
72
            $magic = array(
73
                'algo' => $m[1],
74
                'iter' => $m[2],
75
            );
76
            $salt = $m[3];
77
        } elseif(preg_match('/^sha1\$(.{5})\$/', $hash, $m)) {
78
            $method = 'djangosha1';
79
            $salt   = $m[1];
80
        } elseif(preg_match('/^md5\$(.{5})\$/', $hash, $m)) {
81
            $method = 'djangomd5';
82
            $salt   = $m[1];
83
        } elseif(preg_match('/^\$2(a|y)\$(.{2})\$/', $hash, $m)) {
84
            $method = 'bcrypt';
85
            $salt   = $hash;
86
        } elseif(substr($hash, 0, 6) == '{SSHA}') {
87
            $method = 'ssha';
88
            $salt   = substr(base64_decode(substr($hash, 6)), 20);
89
        } elseif(substr($hash, 0, 6) == '{SMD5}') {
90
            $method = 'lsmd5';
91
            $salt   = substr(base64_decode(substr($hash, 6)), 16);
92
        } elseif(preg_match('/^:B:(.+?):.{32}$/', $hash, $m)) {
93
            $method = 'mediawiki';
94
            $salt   = $m[1];
95
        } elseif(preg_match('/^\$6\$(rounds=\d+)?\$?(.+?)\$/', $hash, $m)) {
96
            $method = 'sha512';
97
            $salt   = $m[2];
98
            $magic  = $m[1];
99
        } elseif(preg_match('/^\$(argon2id?)/', $hash, $m)) {
100
            if(!defined('PASSWORD_'.strtoupper($m[1]))) {
101
                throw new \Exception('This PHP installation has no '.strtoupper($m[1]).' support');
102
            }
103
            return password_verify($clear,$hash);
104
        } elseif($len == 32) {
105
            $method = 'md5';
106
        } elseif($len == 40) {
107
            $method = 'sha1';
108
        } elseif($len == 16) {
109
            $method = 'mysql';
110
        } elseif($len == 41 && $hash[0] == '*') {
111
            $method = 'my411';
112
        } elseif($len == 34) {
113
            $method = 'kmd5';
114
            $salt   = $hash;
115
        } else {
116
            $method = 'crypt';
117
            $salt   = substr($hash, 0, 2);
118
        }
119
120
        //crypt and compare
121
        $call = 'hash_'.$method;
122
        $newhash = $this->$call($clear, $salt, $magic);
123
        if(\hash_equals($newhash, $hash)) {
124
            return true;
125
        }
126
        return false;
127
    }
128
129
    /**
130
     * Create a random salt
131
     *
132
     * @param int $len The length of the salt
133
     * @return string
134
     */
135
    public function gen_salt($len = 32) {
136
        $salt  = '';
137
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
138
        for($i = 0; $i < $len; $i++) {
139
            $salt .= $chars[$this->random(0, 61)];
140
        }
141
        return $salt;
142
    }
143
144
    /**
145
     * Initialize the passed variable with a salt if needed.
146
     *
147
     * If $salt is not null, the value is kept, but the lenght restriction is
148
     * applied (unless, $cut is false).
149
     *
150
     * @param string|null &$salt  The salt, pass null if you want one generated
151
     * @param int          $len   The length of the salt
152
     * @param bool         $cut   Apply length restriction to existing salt?
153
     */
154
    public function init_salt(&$salt, $len = 32, $cut = true) {
155
        if(is_null($salt)) {
156
            $salt = $this->gen_salt($len);
157
            $cut  = true; // for new hashes we alway apply length restriction
158
        }
159
        if(strlen($salt) > $len && $cut) $salt = substr($salt, 0, $len);
160
    }
161
162
    // Password hashing methods follow below
163
164
    /**
165
     * Password hashing method 'smd5'
166
     *
167
     * Uses salted MD5 hashs. Salt is 8 bytes long.
168
     *
169
     * The same mechanism is used by Apache's 'apr1' method. This will
170
     * fallback to a implementation in pure PHP if MD5 support is not
171
     * available in crypt()
172
     *
173
     * @author Andreas Gohr <[email protected]>
174
     * @author <mikey_nich at hotmail dot com>
175
     * @link   http://php.net/manual/en/function.crypt.php#73619
176
     *
177
     * @param string $clear The clear text to hash
178
     * @param string $salt  The salt to use, null for random
179
     * @return string Hashed password
180
     */
181
    public function hash_smd5($clear, $salt = null) {
182
        $this->init_salt($salt, 8);
183
184
        if(defined('CRYPT_MD5') && CRYPT_MD5 && $salt !== '') {
185
            return crypt($clear, '$1$'.$salt.'$');
186
        } else {
187
            // Fall back to PHP-only implementation
188
            return $this->hash_apr1($clear, $salt, '1');
189
        }
190
    }
191
192
    /**
193
     * Password hashing method 'lsmd5'
194
     *
195
     * Uses salted MD5 hashs. Salt is 8 bytes long.
196
     *
197
     * This is the format used by LDAP.
198
     *
199
     * @param string $clear The clear text to hash
200
     * @param string $salt  The salt to use, null for random
201
     * @return string Hashed password
202
     */
203
    public function hash_lsmd5($clear, $salt = null) {
204
        $this->init_salt($salt, 8);
205
        return "{SMD5}".base64_encode(md5($clear.$salt, true).$salt);
206
    }
207
208
    /**
209
     * Password hashing method 'apr1'
210
     *
211
     * Uses salted MD5 hashs. Salt is 8 bytes long.
212
     *
213
     * This is basically the same as smd1 above, but as used by Apache.
214
     *
215
     * @author <mikey_nich at hotmail dot com>
216
     * @link   http://php.net/manual/en/function.crypt.php#73619
217
     *
218
     * @param string $clear The clear text to hash
219
     * @param string $salt  The salt to use, null for random
220
     * @param string $magic The hash identifier (apr1 or 1)
221
     * @return string Hashed password
222
     */
223
    public function hash_apr1($clear, $salt = null, $magic = 'apr1') {
224
        $this->init_salt($salt, 8);
225
226
        $len  = strlen($clear);
227
        $text = $clear.'$'.$magic.'$'.$salt;
228
        $bin  = pack("H32", md5($clear.$salt.$clear));
229
        for($i = $len; $i > 0; $i -= 16) {
230
            $text .= substr($bin, 0, min(16, $i));
231
        }
232
        for($i = $len; $i > 0; $i >>= 1) {
233
            $text .= ($i & 1) ? chr(0) : $clear[0];
234
        }
235
        $bin = pack("H32", md5($text));
236
        for($i = 0; $i < 1000; $i++) {
237
            $new = ($i & 1) ? $clear : $bin;
238
            if($i % 3) $new .= $salt;
239
            if($i % 7) $new .= $clear;
240
            $new .= ($i & 1) ? $bin : $clear;
241
            $bin = pack("H32", md5($new));
242
        }
243
        $tmp = '';
244
        for($i = 0; $i < 5; $i++) {
245
            $k = $i + 6;
246
            $j = $i + 12;
247
            if($j == 16) $j = 5;
248
            $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
249
        }
250
        $tmp = chr(0).chr(0).$bin[11].$tmp;
251
        $tmp = strtr(
252
            strrev(substr(base64_encode($tmp), 2)),
253
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
254
            "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
255
        );
256
        return '$'.$magic.'$'.$salt.'$'.$tmp;
257
    }
258
259
    /**
260
     * Password hashing method 'md5'
261
     *
262
     * Uses MD5 hashs.
263
     *
264
     * @param string $clear The clear text to hash
265
     * @return string Hashed password
266
     */
267
    public function hash_md5($clear) {
268
        return md5($clear);
269
    }
270
271
    /**
272
     * Password hashing method 'sha1'
273
     *
274
     * Uses SHA1 hashs.
275
     *
276
     * @param string $clear The clear text to hash
277
     * @return string Hashed password
278
     */
279
    public function hash_sha1($clear) {
280
        return sha1($clear);
281
    }
282
283
    /**
284
     * Password hashing method 'ssha' as used by LDAP
285
     *
286
     * Uses salted SHA1 hashs. Salt is 4 bytes long.
287
     *
288
     * @param string $clear The clear text to hash
289
     * @param string $salt  The salt to use, null for random
290
     * @return string Hashed password
291
     */
292
    public function hash_ssha($clear, $salt = null) {
293
        $this->init_salt($salt, 4);
294
        return '{SSHA}'.base64_encode(pack("H*", sha1($clear.$salt)).$salt);
295
    }
296
297
    /**
298
     * Password hashing method 'crypt'
299
     *
300
     * Uses salted crypt hashs. Salt is 2 bytes long.
301
     *
302
     * @param string $clear The clear text to hash
303
     * @param string $salt  The salt to use, null for random
304
     * @return string Hashed password
305
     */
306
    public function hash_crypt($clear, $salt = null) {
307
        $this->init_salt($salt, 2);
308
        return crypt($clear, $salt);
309
    }
310
311
    /**
312
     * Password hashing method 'mysql'
313
     *
314
     * This method was used by old MySQL systems
315
     *
316
     * @link   http://php.net/mysql
317
     * @author <soren at byu dot edu>
318
     * @param string $clear The clear text to hash
319
     * @return string Hashed password
320
     */
321
    public function hash_mysql($clear) {
322
        $nr      = 0x50305735;
323
        $nr2     = 0x12345671;
324
        $add     = 7;
325
        $charArr = preg_split("//", $clear);
326
        foreach($charArr as $char) {
327
            if(($char == '') || ($char == ' ') || ($char == '\t')) continue;
328
            $charVal = ord($char);
329
            $nr ^= ((($nr & 63) + $add) * $charVal) + ($nr << 8);
330
            $nr2 += ($nr2 << 8) ^ $nr;
331
            $add += $charVal;
332
        }
333
        return sprintf("%08x%08x", ($nr & 0x7fffffff), ($nr2 & 0x7fffffff));
334
    }
335
336
    /**
337
     * Password hashing method 'my411'
338
     *
339
     * Uses SHA1 hashs. This method is used by MySQL 4.11 and above
340
     *
341
     * @param string $clear The clear text to hash
342
     * @return string Hashed password
343
     */
344
    public function hash_my411($clear) {
345
        return '*'.strtoupper(sha1(pack("H*", sha1($clear))));
346
    }
347
348
    /**
349
     * Password hashing method 'kmd5'
350
     *
351
     * Uses salted MD5 hashs.
352
     *
353
     * Salt is 2 bytes long, but stored at position 16, so you need to pass at
354
     * least 18 bytes. You can pass the crypted hash as salt.
355
     *
356
     * @param string $clear The clear text to hash
357
     * @param string $salt  The salt to use, null for random
358
     * @return string Hashed password
359
     */
360
    public function hash_kmd5($clear, $salt = null) {
361
        $this->init_salt($salt);
362
363
        $key   = substr($salt, 16, 2);
364
        $hash1 = strtolower(md5($key.md5($clear)));
365
        $hash2 = substr($hash1, 0, 16).$key.substr($hash1, 16);
366
        return $hash2;
367
    }
368
369
    /**
370
     * Password stretched hashing wrapper.
371
     *
372
     * Initial hash is repeatedly rehashed with same password.
373
     * Any salted hash algorithm supported by PHP hash() can be used. Salt
374
     * is 1+8 bytes long, 1st byte is the iteration count when given. For null
375
     * salts $compute is used.
376
     *
377
     * The actual iteration count is 2 to the power of the given count,
378
     * maximum is 30 (-> 2^30 = 1_073_741_824). If a higher one is given,
379
     * the function throws an exception.
380
     * This iteration count is expected to grow with increasing power of
381
     * new computers.
382
     *
383
     * @author  Andreas Gohr <[email protected]>
384
     * @author  Schplurtz le Déboulonné <[email protected]>
385
     * @link    http://www.openwall.com/phpass/
386
     *
387
     * @param string $algo    The hash algorithm to be used
388
     * @param string $clear   The clear text to hash
389
     * @param string $salt    The salt to use, null for random
390
     * @param string $magic   The hash identifier (P or H)
391
     * @param int    $compute The iteration count for new passwords
392
     * @throws \Exception
393
     * @return string Hashed password
394
     */
395
    protected function stretched_hash($algo, $clear, $salt = null, $magic = 'P', $compute = 8) {
396
        $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
397
        if(is_null($salt)) {
398
            $this->init_salt($salt);
399
            $salt = $itoa64[$compute].$salt; // prefix iteration count
400
        }
401
        $iterc = $salt[0]; // pos 0 of salt is log2(iteration count)
402
        $iter  = strpos($itoa64, $iterc);
403
404
        if($iter > 30) {
405
            throw new \Exception("Too high iteration count ($iter) in ".
406
                                    __CLASS__.'::'.__FUNCTION__);
407
        }
408
409
        $iter = 1 << $iter;
410
        $salt = substr($salt, 1, 8);
411
412
        // iterate
413
        $hash = hash($algo, $salt . $clear, TRUE);
414
        do {
415
            $hash = hash($algo, $hash.$clear, true);
416
        } while(--$iter);
417
418
        // encode
419
        $output = '';
420
        $count  = strlen($hash);
421
        $i      = 0;
422
        do {
423
            $value = ord($hash[$i++]);
424
            $output .= $itoa64[$value & 0x3f];
425
            if($i < $count)
426
                $value |= ord($hash[$i]) << 8;
427
            $output .= $itoa64[($value >> 6) & 0x3f];
428
            if($i++ >= $count)
429
                break;
430
            if($i < $count)
431
                $value |= ord($hash[$i]) << 16;
432
            $output .= $itoa64[($value >> 12) & 0x3f];
433
            if($i++ >= $count)
434
                break;
435
            $output .= $itoa64[($value >> 18) & 0x3f];
436
        } while($i < $count);
437
438
        return '$'.$magic.'$'.$iterc.$salt.$output;
439
    }
440
441
    /**
442
     * Password hashing method 'pmd5'
443
     *
444
     * Repeatedly uses salted MD5 hashs. See stretched_hash() for the
445
     * details.
446
     *
447
     *
448
     * @author  Schplurtz le Déboulonné <[email protected]>
449
     * @link    http://www.openwall.com/phpass/
450
     * @see     PassHash::stretched_hash() for the implementation details.
451
     *
452
     * @param string $clear   The clear text to hash
453
     * @param string $salt    The salt to use, null for random
454
     * @param string $magic   The hash identifier (P or H)
455
     * @param int    $compute The iteration count for new passwords
456
     * @throws Exception
457
     * @return string Hashed password
458
     */
459
    public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) {
460
        return $this->stretched_hash('md5', $clear, $salt, $magic, $compute);
461
    }
462
463
    /**
464
     * Password hashing method 'drupal_sha512'
465
     *
466
     * Implements Drupal salted sha512 hashs. Drupal truncates the hash at 55
467
     * characters. See stretched_hash() for the details;
468
     *
469
     * @author  Schplurtz le Déboulonné <[email protected]>
470
     * @link    https://api.drupal.org/api/drupal/includes%21password.inc/7.x
471
     * @see     PassHash::stretched_hash() for the implementation details.
472
     *
473
     * @param string $clear   The clear text to hash
474
     * @param string $salt    The salt to use, null for random
475
     * @param string $magic   The hash identifier (S)
476
     * @param int    $compute The iteration count for new passwords (defautl is drupal 7's)
477
     * @throws Exception
478
     * @return string Hashed password
479
     */
480
    public function hash_drupal_sha512($clear, $salt = null, $magic = 'S', $compute = 15) {
481
      return substr($this->stretched_hash('sha512', $clear, $salt, $magic, $compute), 0, 55);
482
    }
483
484
    /**
485
     * Alias for hash_pmd5
486
     *
487
     * @param string $clear
488
     * @param null|string $salt
489
     * @param string $magic
490
     * @param int $compute
491
     *
492
     * @return string
493
     * @throws \Exception
494
     */
495
    public function hash_hmd5($clear, $salt = null, $magic = 'H', $compute = 8) {
496
        return $this->hash_pmd5($clear, $salt, $magic, $compute);
497
    }
498
499
    /**
500
     * Password hashing method 'djangosha1'
501
     *
502
     * Uses salted SHA1 hashs. Salt is 5 bytes long.
503
     * This is used by the Django Python framework
504
     *
505
     * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords
506
     *
507
     * @param string $clear The clear text to hash
508
     * @param string $salt  The salt to use, null for random
509
     * @return string Hashed password
510
     */
511
    public function hash_djangosha1($clear, $salt = null) {
512
        $this->init_salt($salt, 5);
513
        return 'sha1$'.$salt.'$'.sha1($salt.$clear);
514
    }
515
516
    /**
517
     * Password hashing method 'djangomd5'
518
     *
519
     * Uses salted MD5 hashs. Salt is 5 bytes long.
520
     * This is used by the Django Python framework
521
     *
522
     * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords
523
     *
524
     * @param string $clear The clear text to hash
525
     * @param string $salt  The salt to use, null for random
526
     * @return string Hashed password
527
     */
528
    public function hash_djangomd5($clear, $salt = null) {
529
        $this->init_salt($salt, 5);
530
        return 'md5$'.$salt.'$'.md5($salt.$clear);
531
    }
532
533
    /**
534
     * Password hashing method 'seafilepbkdf2'
535
     *
536
     * An algorithm and iteration count should be given in the opts array.
537
     *
538
     * Hash algorithm is the string that is in the password string in seafile
539
     * database. It has to be converted to a php algo name.
540
     *
541
     * @author Schplurtz le Déboulonné <[email protected]>
542
     * @see https://stackoverflow.com/a/23670177
543
     *
544
     * @param string $clear The clear text to hash
545
     * @param string $salt  The salt to use, null for random
546
     * @param array $opts ('algo' => hash algorithm, 'iter' => iterations)
547
     * @return string Hashed password
548
     * @throws Exception when PHP is missing support for the method/algo
549
     */
550
    public function hash_seafilepbkdf2($clear, $salt=null, $opts=array()) {
551
        $this->init_salt($salt, 64);
552
        if(empty($opts['algo'])) {
553
            $prefixalgo='SHA256';
554
        } else {
555
            $prefixalgo=$opts['algo'];
556
        }
557
        $algo = strtolower($prefixalgo);
558
        if(empty($opts['iter'])) {
559
            $iter = 10000;
560
        } else {
561
            $iter = (int) $opts['iter'];
562
        }
563
        if(!function_exists('hash_pbkdf2')) {
564
            throw new Exception('This PHP installation has no PBKDF2 support');
565
        }
566
        if(!in_array($algo, hash_algos())) {
567
            throw new Exception("This PHP installation has no $algo support");
568
        }
569
570
        $hash = hash_pbkdf2($algo, $clear, hex2bin($salt), $iter, 0);
571
        return "PBKDF2$prefixalgo\$$iter\$$salt\$$hash";
572
    }
573
574
    /**
575
     * Password hashing method 'djangopbkdf2'
576
     *
577
     * An algorithm and iteration count should be given in the opts array.
578
     * Defaults to sha256 and 24000 iterations
579
     *
580
     * @param string $clear The clear text to hash
581
     * @param string $salt  The salt to use, null for random
582
     * @param array $opts ('algo' => hash algorithm, 'iter' => iterations)
583
     * @return string Hashed password
584
     * @throws \Exception when PHP is missing support for the method/algo
585
     */
586
    public function hash_djangopbkdf2($clear, $salt=null, $opts=array()) {
587
        $this->init_salt($salt, 12);
588
        if(empty($opts['algo'])) {
589
            $algo = 'sha256';
590
        } else {
591
            $algo = $opts['algo'];
592
        }
593
        if(empty($opts['iter'])) {
594
            $iter = 24000;
595
        } else {
596
            $iter = (int) $opts['iter'];
597
        }
598
        if(!function_exists('hash_pbkdf2')) {
599
            throw new \Exception('This PHP installation has no PBKDF2 support');
600
        }
601
        if(!in_array($algo, hash_algos())) {
602
            throw new \Exception("This PHP installation has no $algo support");
603
        }
604
605
        $hash = base64_encode(hash_pbkdf2($algo, $clear, $salt, $iter, 0, true));
606
        return "pbkdf2_$algo\$$iter\$$salt\$$hash";
607
    }
608
609
    /**
610
     * Alias for djangopbkdf2 defaulting to sha256 as hash algorithm
611
     *
612
     * @param string $clear The clear text to hash
613
     * @param string $salt  The salt to use, null for random
614
     * @param array $opts ('iter' => iterations)
615
     * @return string Hashed password
616
     * @throws \Exception when PHP is missing support for the method/algo
617
     */
618
    public function hash_djangopbkdf2_sha256($clear, $salt=null, $opts=array()) {
619
        $opts['algo'] = 'sha256';
620
        return $this->hash_djangopbkdf2($clear, $salt, $opts);
621
    }
622
623
    /**
624
     * Alias for djangopbkdf2 defaulting to sha1 as hash algorithm
625
     *
626
     * @param string $clear The clear text to hash
627
     * @param string $salt  The salt to use, null for random
628
     * @param array $opts ('iter' => iterations)
629
     * @return string Hashed password
630
     * @throws \Exception when PHP is missing support for the method/algo
631
     */
632
    public function hash_djangopbkdf2_sha1($clear, $salt=null, $opts=array()) {
633
        $opts['algo'] = 'sha1';
634
        return $this->hash_djangopbkdf2($clear, $salt, $opts);
635
    }
636
637
    /**
638
     * Passwordhashing method 'bcrypt'
639
     *
640
     * Uses a modified blowfish algorithm called eksblowfish
641
     * This method works on PHP 5.3+ only and will throw an exception
642
     * if the needed crypt support isn't available
643
     *
644
     * A full hash should be given as salt (starting with $a2$) or this
645
     * will break. When no salt is given, the iteration count can be set
646
     * through the $compute variable.
647
     *
648
     * @param string $clear   The clear text to hash
649
     * @param string $salt    The salt to use, null for random
650
     * @param int    $compute The iteration count (between 4 and 31)
651
     * @throws \Exception
652
     * @return string Hashed password
653
     */
654
    public function hash_bcrypt($clear, $salt = null, $compute = 10) {
655
        if(!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH != 1) {
656
            throw new \Exception('This PHP installation has no bcrypt support');
657
        }
658
659
        if(is_null($salt)) {
660
            if($compute < 4 || $compute > 31) $compute = 8;
661
            $salt = '$2y$'.str_pad($compute, 2, '0', STR_PAD_LEFT).'$'.
662
                $this->gen_salt(22);
663
        }
664
665
        return crypt($clear, $salt);
666
    }
667
668
    /**
669
     * Password hashing method SHA512
670
     *
671
     * This is only supported on PHP 5.3.2 or higher and will throw an exception if
672
     * the needed crypt support is not available
673
     *
674
     * @param string $clear The clear text to hash
675
     * @param string $salt  The salt to use, null for random
676
     * @param string $magic The rounds for sha512 (for example "rounds=3000"), null for default value
677
     * @return string Hashed password
678
     * @throws \Exception
679
     */
680
    public function hash_sha512($clear, $salt = null, $magic = null) {
681
        if(!defined('CRYPT_SHA512') || CRYPT_SHA512 != 1) {
682
            throw new \Exception('This PHP installation has no SHA512 support');
683
        }
684
        $this->init_salt($salt, 8, false);
685
        if(empty($magic)) {
686
            return crypt($clear, '$6$'.$salt.'$');
687
        }else{
688
            return crypt($clear, '$6$'.$magic.'$'.$salt.'$');
689
        }
690
    }
691
692
    /**
693
     * Password hashing method 'mediawiki'
694
     *
695
     * Uses salted MD5, this is referred to as Method B in MediaWiki docs. Unsalted md5
696
     * method 'A' is not supported.
697
     *
698
     * @link  http://www.mediawiki.org/wiki/Manual_talk:User_table#user_password_column
699
     *
700
     * @param string $clear The clear text to hash
701
     * @param string $salt  The salt to use, null for random
702
     * @return string Hashed password
703
     */
704
    public function hash_mediawiki($clear, $salt = null) {
705
        $this->init_salt($salt, 8, false);
706
        return ':B:'.$salt.':'.md5($salt.'-'.md5($clear));
707
    }
708
709
710
    /**
711
     * Password hashing method 'argon2i'
712
     *
713
     * Uses php's own password_hash function to create argon2i password hash
714
     * Default Cost and thread options are used for now.
715
     *
716
     * @link  https://www.php.net/manual/de/function.password-hash.php
717
     *
718
     * @param string $clear The clear text to hash
719
     * @return string Hashed password
720
     */
721
    public function hash_argon2i($clear) {
722
        if(!defined('PASSWORD_ARGON2I')) {
723
            throw new \Exception('This PHP installation has no ARGON2I support');
724
        }
725
        return password_hash($clear,PASSWORD_ARGON2I);   
726
    }
727
728
    /**
729
     * Password hashing method 'argon2id'
730
     *
731
     * Uses php's own password_hash function to create argon2id password hash
732
     * Default Cost and thread options are used for now.
733
     *
734
     * @link  https://www.php.net/manual/de/function.password-hash.php
735
     *
736
     * @param string $clear The clear text to hash
737
     * @return string Hashed password
738
     */
739
    public function hash_argon2id($clear) {
740
        if(!defined('PASSWORD_ARGON2ID')) {
741
            throw new \Exception('This PHP installation has no ARGON2ID support');
742
        }
743
        return password_hash($clear,PASSWORD_ARGON2ID);   
744
    }
745
746
    /**
747
     * Wraps around native hash_hmac() or reimplents it
748
     *
749
     * This is not directly used as password hashing method, and thus isn't callable via the
750
     * verify_hash() method. It should be used to create signatures and might be used in other
751
     * password hashing methods.
752
     *
753
     * @see hash_hmac()
754
     * @author KC Cloyd
755
     * @link http://php.net/manual/en/function.hash-hmac.php#93440
756
     *
757
     * @param string $algo Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4",
758
     *                     etc..) See hash_algos() for a list of supported algorithms.
759
     * @param string $data Message to be hashed.
760
     * @param string $key  Shared secret key used for generating the HMAC variant of the message digest.
761
     * @param bool $raw_output When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
762
     * @return string
763
     */
764
    public static function hmac($algo, $data, $key, $raw_output = false) {
765
        // use native function if available and not in unit test
766
        if(function_exists('hash_hmac') && !defined('SIMPLE_TEST')){
767
            return hash_hmac($algo, $data, $key, $raw_output);
768
        }
769
770
        $algo = strtolower($algo);
771
        $pack = 'H' . strlen($algo('test'));
772
        $size = 64;
773
        $opad = str_repeat(chr(0x5C), $size);
774
        $ipad = str_repeat(chr(0x36), $size);
775
776
        if(strlen($key) > $size) {
777
            $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
778
        } else {
779
            $key = str_pad($key, $size, chr(0x00));
780
        }
781
782
        for($i = 0; $i < strlen($key) - 1; $i++) {
783
            $opad[$i] = $opad[$i] ^ $key[$i];
784
            $ipad[$i] = $ipad[$i] ^ $key[$i];
785
        }
786
787
        $output = $algo($opad . pack($pack, $algo($ipad . $data)));
788
789
        return ($raw_output) ? pack($pack, $output) : $output;
790
    }
791
792
    /**
793
     * Use a secure random generator
794
     *
795
     * @param int $min
796
     * @param int $max
797
     * @return int
798
     */
799
    protected function random($min, $max){
800
        try {
801
            return random_int($min, $max);
802
        } catch (\Exception $e) {
803
            // availability of random source is checked elsewhere in DokuWiki
804
            // we demote this to an unchecked runtime exception here
805
            throw new \RuntimeException($e->getMessage(), $e->getCode(), $e);
806
        }
807
    }
808
}
809