Completed
Push — django_pbkdf2 ( 924cc1 )
by Andreas
04:14
created

PassHash::hash_djangopbkdf2_sha256()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 3
1
<?php
2
/**
3
 * Password Hashing Class
4
 *
5
 * This class implements various mechanisms used to hash passwords
6
 *
7
 * @author  Andreas Gohr <[email protected]>
8
 * @license LGPL2
9
 */
10
class PassHash {
11
    /**
12
     * Verifies a cleartext password against a crypted hash
13
     *
14
     * The method and salt used for the crypted hash is determined automatically,
15
     * then the clear text password is crypted using the same method. If both hashs
16
     * match true is is returned else false
17
     *
18
     * @author  Andreas Gohr <[email protected]>
19
     *
20
     * @param string $clear Clear-Text password
21
     * @param string $hash  Hash to compare against
22
     * @return  bool
23
     */
24
    function verify_hash($clear, $hash) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
25
        $method = '';
0 ignored issues
show
Unused Code introduced by
$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...
26
        $salt   = '';
27
        $magic  = '';
28
29
        //determine the used method and salt
30
        $len = strlen($hash);
31
        if(preg_match('/^\$1\$([^\$]{0,8})\$/', $hash, $m)) {
32
            $method = 'smd5';
33
            $salt   = $m[1];
34
            $magic  = '1';
35
        } elseif(preg_match('/^\$apr1\$([^\$]{0,8})\$/', $hash, $m)) {
36
            $method = 'apr1';
37
            $salt   = $m[1];
38
            $magic  = 'apr1';
39
        } elseif(preg_match('/^\$P\$(.{31})$/', $hash, $m)) {
40
            $method = 'pmd5';
41
            $salt   = $m[1];
42
            $magic  = 'P';
43
        } elseif(preg_match('/^\$H\$(.{31})$/', $hash, $m)) {
44
            $method = 'pmd5';
45
            $salt = $m[1];
46
            $magic = 'H';
47
        } elseif(preg_match('/^pbkdf2_(\w+?)\$(\d+)\$(.{12})\$/', $hash, $m)) {
48
            $method = 'djangopbkdf2';
49
            $magic = array(
50
                'algo' => $m[1],
51
                'iter' => $m[2],
52
            );
53
            $salt = $m[3];
54
        } elseif(preg_match('/^sha1\$(.{5})\$/', $hash, $m)) {
55
            $method = 'djangosha1';
56
            $salt   = $m[1];
57
        } elseif(preg_match('/^md5\$(.{5})\$/', $hash, $m)) {
58
            $method = 'djangomd5';
59
            $salt   = $m[1];
60
        } elseif(preg_match('/^\$2(a|y)\$(.{2})\$/', $hash, $m)) {
61
            $method = 'bcrypt';
62
            $salt   = $hash;
63
        } elseif(substr($hash, 0, 6) == '{SSHA}') {
64
            $method = 'ssha';
65
            $salt   = substr(base64_decode(substr($hash, 6)), 20);
66
        } elseif(substr($hash, 0, 6) == '{SMD5}') {
67
            $method = 'lsmd5';
68
            $salt   = substr(base64_decode(substr($hash, 6)), 16);
69
        } elseif(preg_match('/^:B:(.+?):.{32}$/', $hash, $m)) {
70
            $method = 'mediawiki';
71
            $salt   = $m[1];
72
        } elseif(preg_match('/^\$6\$(.+?)\$/', $hash, $m)) {
73
            $method = 'sha512';
74
            $salt   = $m[1];
75
        } elseif($len == 32) {
76
            $method = 'md5';
77
        } elseif($len == 40) {
78
            $method = 'sha1';
79
        } elseif($len == 16) {
80
            $method = 'mysql';
81
        } elseif($len == 41 && $hash[0] == '*') {
82
            $method = 'my411';
83
        } elseif($len == 34) {
84
            $method = 'kmd5';
85
            $salt   = $hash;
86
        } else {
87
            $method = 'crypt';
88
            $salt   = substr($hash, 0, 2);
89
        }
90
91
        //crypt and compare
92
        $call = 'hash_'.$method;
93
        $newhash = $this->$call($clear, $salt, $magic);
94
        if($newhash === $hash) {
95
            return true;
96
        }
97
        return false;
98
    }
99
100
    /**
101
     * Create a random salt
102
     *
103
     * @param int $len The length of the salt
104
     * @return string
105
     */
106
    public function gen_salt($len = 32) {
107
        $salt  = '';
108
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
109
        for($i = 0; $i < $len; $i++) {
110
            $salt .= $chars[$this->random(0, 61)];
111
        }
112
        return $salt;
113
    }
114
115
    /**
116
     * Initialize the passed variable with a salt if needed.
117
     *
118
     * If $salt is not null, the value is kept, but the lenght restriction is
119
     * applied (unless, $cut is false).
120
     *
121
     * @param string|null &$salt  The salt, pass null if you want one generated
122
     * @param int          $len   The length of the salt
123
     * @param bool         $cut   Apply length restriction to existing salt?
124
     */
125
    public function init_salt(&$salt, $len = 32, $cut = true) {
126
        if(is_null($salt)) {
127
            $salt = $this->gen_salt($len);
128
            $cut  = true; // for new hashes we alway apply length restriction
129
        }
130
        if(strlen($salt) > $len && $cut) $salt = substr($salt, 0, $len);
131
    }
132
133
    // Password hashing methods follow below
134
135
    /**
136
     * Password hashing method 'smd5'
137
     *
138
     * Uses salted MD5 hashs. Salt is 8 bytes long.
139
     *
140
     * The same mechanism is used by Apache's 'apr1' method. This will
141
     * fallback to a implementation in pure PHP if MD5 support is not
142
     * available in crypt()
143
     *
144
     * @author Andreas Gohr <[email protected]>
145
     * @author <mikey_nich at hotmail dot com>
146
     * @link   http://de.php.net/manual/en/function.crypt.php#73619
147
     *
148
     * @param string $clear The clear text to hash
149
     * @param string $salt  The salt to use, null for random
150
     * @return string Hashed password
151
     */
152
    public function hash_smd5($clear, $salt = null) {
153
        $this->init_salt($salt, 8);
154
155
        if(defined('CRYPT_MD5') && CRYPT_MD5 && $salt !== '') {
156
            return crypt($clear, '$1$'.$salt.'$');
157
        } else {
158
            // Fall back to PHP-only implementation
159
            return $this->hash_apr1($clear, $salt, '1');
160
        }
161
    }
162
163
    /**
164
     * Password hashing method 'lsmd5'
165
     *
166
     * Uses salted MD5 hashs. Salt is 8 bytes long.
167
     *
168
     * This is the format used by LDAP.
169
     *
170
     * @param string $clear The clear text to hash
171
     * @param string $salt  The salt to use, null for random
172
     * @return string Hashed password
173
     */
174
    public function hash_lsmd5($clear, $salt = null) {
175
        $this->init_salt($salt, 8);
176
        return "{SMD5}".base64_encode(md5($clear.$salt, true).$salt);
177
    }
178
179
    /**
180
     * Password hashing method 'apr1'
181
     *
182
     * Uses salted MD5 hashs. Salt is 8 bytes long.
183
     *
184
     * This is basically the same as smd1 above, but as used by Apache.
185
     *
186
     * @author <mikey_nich at hotmail dot com>
187
     * @link   http://de.php.net/manual/en/function.crypt.php#73619
188
     *
189
     * @param string $clear The clear text to hash
190
     * @param string $salt  The salt to use, null for random
191
     * @param string $magic The hash identifier (apr1 or 1)
192
     * @return string Hashed password
193
     */
194
    public function hash_apr1($clear, $salt = null, $magic = 'apr1') {
195
        $this->init_salt($salt, 8);
196
197
        $len  = strlen($clear);
198
        $text = $clear.'$'.$magic.'$'.$salt;
199
        $bin  = pack("H32", md5($clear.$salt.$clear));
200
        for($i = $len; $i > 0; $i -= 16) {
201
            $text .= substr($bin, 0, min(16, $i));
202
        }
203
        for($i = $len; $i > 0; $i >>= 1) {
204
            $text .= ($i & 1) ? chr(0) : $clear{0};
205
        }
206
        $bin = pack("H32", md5($text));
207
        for($i = 0; $i < 1000; $i++) {
208
            $new = ($i & 1) ? $clear : $bin;
209
            if($i % 3) $new .= $salt;
210
            if($i % 7) $new .= $clear;
211
            $new .= ($i & 1) ? $bin : $clear;
212
            $bin = pack("H32", md5($new));
213
        }
214
        $tmp = '';
215
        for($i = 0; $i < 5; $i++) {
216
            $k = $i + 6;
217
            $j = $i + 12;
218
            if($j == 16) $j = 5;
219
            $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
220
        }
221
        $tmp = chr(0).chr(0).$bin[11].$tmp;
222
        $tmp = strtr(
223
            strrev(substr(base64_encode($tmp), 2)),
224
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
225
            "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
226
        );
227
        return '$'.$magic.'$'.$salt.'$'.$tmp;
228
    }
229
230
    /**
231
     * Password hashing method 'md5'
232
     *
233
     * Uses MD5 hashs.
234
     *
235
     * @param string $clear The clear text to hash
236
     * @return string Hashed password
237
     */
238
    public function hash_md5($clear) {
239
        return md5($clear);
240
    }
241
242
    /**
243
     * Password hashing method 'sha1'
244
     *
245
     * Uses SHA1 hashs.
246
     *
247
     * @param string $clear The clear text to hash
248
     * @return string Hashed password
249
     */
250
    public function hash_sha1($clear) {
251
        return sha1($clear);
252
    }
253
254
    /**
255
     * Password hashing method 'ssha' as used by LDAP
256
     *
257
     * Uses salted SHA1 hashs. Salt is 4 bytes long.
258
     *
259
     * @param string $clear The clear text to hash
260
     * @param string $salt  The salt to use, null for random
261
     * @return string Hashed password
262
     */
263
    public function hash_ssha($clear, $salt = null) {
264
        $this->init_salt($salt, 4);
265
        return '{SSHA}'.base64_encode(pack("H*", sha1($clear.$salt)).$salt);
266
    }
267
268
    /**
269
     * Password hashing method 'crypt'
270
     *
271
     * Uses salted crypt hashs. Salt is 2 bytes long.
272
     *
273
     * @param string $clear The clear text to hash
274
     * @param string $salt  The salt to use, null for random
275
     * @return string Hashed password
276
     */
277
    public function hash_crypt($clear, $salt = null) {
278
        $this->init_salt($salt, 2);
279
        return crypt($clear, $salt);
280
    }
281
282
    /**
283
     * Password hashing method 'mysql'
284
     *
285
     * This method was used by old MySQL systems
286
     *
287
     * @link   http://www.php.net/mysql
288
     * @author <soren at byu dot edu>
289
     * @param string $clear The clear text to hash
290
     * @return string Hashed password
291
     */
292
    public function hash_mysql($clear) {
293
        $nr      = 0x50305735;
294
        $nr2     = 0x12345671;
295
        $add     = 7;
296
        $charArr = preg_split("//", $clear);
297
        foreach($charArr as $char) {
298
            if(($char == '') || ($char == ' ') || ($char == '\t')) continue;
299
            $charVal = ord($char);
300
            $nr ^= ((($nr & 63) + $add) * $charVal) + ($nr << 8);
301
            $nr2 += ($nr2 << 8) ^ $nr;
302
            $add += $charVal;
303
        }
304
        return sprintf("%08x%08x", ($nr & 0x7fffffff), ($nr2 & 0x7fffffff));
305
    }
306
307
    /**
308
     * Password hashing method 'my411'
309
     *
310
     * Uses SHA1 hashs. This method is used by MySQL 4.11 and above
311
     *
312
     * @param string $clear The clear text to hash
313
     * @return string Hashed password
314
     */
315
    public function hash_my411($clear) {
316
        return '*'.sha1(pack("H*", sha1($clear)));
317
    }
318
319
    /**
320
     * Password hashing method 'kmd5'
321
     *
322
     * Uses salted MD5 hashs.
323
     *
324
     * Salt is 2 bytes long, but stored at position 16, so you need to pass at
325
     * least 18 bytes. You can pass the crypted hash as salt.
326
     *
327
     * @param string $clear The clear text to hash
328
     * @param string $salt  The salt to use, null for random
329
     * @return string Hashed password
330
     */
331
    public function hash_kmd5($clear, $salt = null) {
332
        $this->init_salt($salt);
333
334
        $key   = substr($salt, 16, 2);
335
        $hash1 = strtolower(md5($key.md5($clear)));
336
        $hash2 = substr($hash1, 0, 16).$key.substr($hash1, 16);
337
        return $hash2;
338
    }
339
340
    /**
341
     * Password hashing method 'pmd5'
342
     *
343
     * Uses salted MD5 hashs. Salt is 1+8 bytes long, 1st byte is the
344
     * iteration count when given, for null salts $compute is used.
345
     *
346
     * The actual iteration count is the given count squared, maximum is
347
     * 30 (-> 1073741824). If a higher one is given, the function throws
348
     * an exception.
349
     *
350
     * @link  http://www.openwall.com/phpass/
351
     *
352
     * @param string $clear   The clear text to hash
353
     * @param string $salt    The salt to use, null for random
354
     * @param string $magic   The hash identifier (P or H)
355
     * @param int    $compute The iteration count for new passwords
356
     * @throws Exception
357
     * @return string Hashed password
358
     */
359
    public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) {
360
        $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
361
        if(is_null($salt)) {
362
            $this->init_salt($salt);
363
            $salt = $itoa64[$compute].$salt; // prefix iteration count
364
        }
365
        $iterc = $salt[0]; // pos 0 of salt is iteration count
366
        $iter  = strpos($itoa64, $iterc);
367
368
        if($iter > 30) {
369
            throw new Exception("Too high iteration count ($iter) in ".
370
                                    __CLASS__.'::'.__FUNCTION__);
371
        }
372
373
        $iter = 1 << $iter;
374
        $salt = substr($salt, 1, 8);
375
376
        // iterate
377
        $hash = md5($salt.$clear, true);
378
        do {
379
            $hash = md5($hash.$clear, true);
380
        } while(--$iter);
381
382
        // encode
383
        $output = '';
384
        $count  = 16;
385
        $i      = 0;
386
        do {
387
            $value = ord($hash[$i++]);
388
            $output .= $itoa64[$value & 0x3f];
389
            if($i < $count)
390
                $value |= ord($hash[$i]) << 8;
391
            $output .= $itoa64[($value >> 6) & 0x3f];
392
            if($i++ >= $count)
393
                break;
394
            if($i < $count)
395
                $value |= ord($hash[$i]) << 16;
396
            $output .= $itoa64[($value >> 12) & 0x3f];
397
            if($i++ >= $count)
398
                break;
399
            $output .= $itoa64[($value >> 18) & 0x3f];
400
        } while($i < $count);
401
402
        return '$'.$magic.'$'.$iterc.$salt.$output;
403
    }
404
405
    /**
406
     * Alias for hash_pmd5
407
     */
408
    public function hash_hmd5($clear, $salt = null, $magic = 'H', $compute = 8) {
409
        return $this->hash_pmd5($clear, $salt, $magic, $compute);
410
    }
411
412
    /**
413
     * Password hashing method 'djangosha1'
414
     *
415
     * Uses salted SHA1 hashs. Salt is 5 bytes long.
416
     * This is used by the Django Python framework
417
     *
418
     * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords
419
     *
420
     * @param string $clear The clear text to hash
421
     * @param string $salt  The salt to use, null for random
422
     * @return string Hashed password
423
     */
424
    public function hash_djangosha1($clear, $salt = null) {
425
        $this->init_salt($salt, 5);
426
        return 'sha1$'.$salt.'$'.sha1($salt.$clear);
427
    }
428
429
    /**
430
     * Password hashing method 'djangomd5'
431
     *
432
     * Uses salted MD5 hashs. Salt is 5 bytes long.
433
     * This is used by the Django Python framework
434
     *
435
     * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords
436
     *
437
     * @param string $clear The clear text to hash
438
     * @param string $salt  The salt to use, null for random
439
     * @return string Hashed password
440
     */
441
    public function hash_djangomd5($clear, $salt = null) {
442
        $this->init_salt($salt, 5);
443
        return 'md5$'.$salt.'$'.md5($salt.$clear);
444
    }
445
446
    /**
447
     * Password hashing method 'djangopbkdf2'
448
     *
449
     * An algorithm and iteration count should be given in the opts array.
450
     * Defaults to sha256 and 24000 iterations
451
     *
452
     * @param string $clear The clear text to hash
453
     * @param string $salt  The salt to use, null for random
454
     * @param array $opts ('algo' => hash algorithm, 'iter' => iterations)
455
     * @return string Hashed password
456
     * @throws Exception when PHP is missing support for the method/algo
457
     */
458
    public function hash_djangopbkdf2($clear, $salt=null, $opts=array()) {
459
        $this->init_salt($salt, 12);
460
        if(empty($opts['algo'])) {
461
            $algo = 'sha256';
462
        } else {
463
            $algo = $opts['algo'];
464
        }
465
        if(empty($opts['iter'])) {
466
            $iter = 24000;
467
        } else {
468
            $iter = (int) $opts['iter'];
469
        }
470
        if(!function_exists('hash_pbkdf2')) {
471
            throw new Exception('This PHP installation has no PBKDF2 support');
472
        }
473
        if(!in_array($algo, hash_algos())) {
474
            throw new Exception("This PHP installation has no $algo support");
475
        }
476
477
        $hash = base64_encode(hash_pbkdf2($algo, $clear, $salt, $iter, 0, true));
478
        return "pbkdf2_$algo\$$iter\$$salt\$$hash";
479
    }
480
481
    /**
482
     * Alias for djangopbkdf2 defaulting to sha256 as hash algorithm
483
     *
484
     * @param string $clear The clear text to hash
485
     * @param string $salt  The salt to use, null for random
486
     * @param array $opts ('iter' => iterations)
487
     * @return string Hashed password
488
     * @throws Exception when PHP is missing support for the method/algo
489
     */
490
    public function hash_djangopbkdf2_sha256($clear, $salt=null, $opts=array()) {
491
        $opts['algo'] = 'sha256';
492
        return $this->hash_djangopbkdf2($clear, $salt, $opts);
493
    }
494
495
    /**
496
     * Alias for djangopbkdf2 defaulting to sha1 as hash algorithm
497
     *
498
     * @param string $clear The clear text to hash
499
     * @param string $salt  The salt to use, null for random
500
     * @param array $opts ('iter' => iterations)
501
     * @return string Hashed password
502
     * @throws Exception when PHP is missing support for the method/algo
503
     */
504
    public function hash_djangopbkdf2_sha1($clear, $salt=null, $opts=array()) {
505
        $opts['algo'] = 'sha1';
506
        return $this->hash_djangopbkdf2($clear, $salt, $opts);
507
    }
508
509
    /**
510
     * Passwordhashing method 'bcrypt'
511
     *
512
     * Uses a modified blowfish algorithm called eksblowfish
513
     * This method works on PHP 5.3+ only and will throw an exception
514
     * if the needed crypt support isn't available
515
     *
516
     * A full hash should be given as salt (starting with $a2$) or this
517
     * will break. When no salt is given, the iteration count can be set
518
     * through the $compute variable.
519
     *
520
     * @param string $clear   The clear text to hash
521
     * @param string $salt    The salt to use, null for random
522
     * @param int    $compute The iteration count (between 4 and 31)
523
     * @throws Exception
524
     * @return string Hashed password
525
     */
526
    public function hash_bcrypt($clear, $salt = null, $compute = 8) {
527
        if(!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH != 1) {
528
            throw new Exception('This PHP installation has no bcrypt support');
529
        }
530
531
        if(is_null($salt)) {
532
            if($compute < 4 || $compute > 31) $compute = 8;
533
            $salt = '$2a$'.str_pad($compute, 2, '0', STR_PAD_LEFT).'$'.
534
                $this->gen_salt(22);
535
        }
536
537
        return crypt($clear, $salt);
538
    }
539
540
    /**
541
     * Password hashing method SHA512
542
     *
543
     * This is only supported on PHP 5.3.2 or higher and will throw an exception if
544
     * the needed crypt support is not available
545
     *
546
     * @param string $clear The clear text to hash
547
     * @param string $salt  The salt to use, null for random
548
     * @return string Hashed password
549
     * @throws Exception
550
     */
551
    public function hash_sha512($clear, $salt = null) {
552
        if(!defined('CRYPT_SHA512') || CRYPT_SHA512 != 1) {
553
            throw new Exception('This PHP installation has no SHA512 support');
554
        }
555
        $this->init_salt($salt, 8, false);
556
        return crypt($clear, '$6$'.$salt.'$');
557
    }
558
559
    /**
560
     * Password hashing method 'mediawiki'
561
     *
562
     * Uses salted MD5, this is referred to as Method B in MediaWiki docs. Unsalted md5
563
     * method 'A' is not supported.
564
     *
565
     * @link  http://www.mediawiki.org/wiki/Manual_talk:User_table#user_password_column
566
     *
567
     * @param string $clear The clear text to hash
568
     * @param string $salt  The salt to use, null for random
569
     * @return string Hashed password
570
     */
571
    public function hash_mediawiki($clear, $salt = null) {
572
        $this->init_salt($salt, 8, false);
573
        return ':B:'.$salt.':'.md5($salt.'-'.md5($clear));
574
    }
575
576
    /**
577
     * Wraps around native hash_hmac() or reimplents it
578
     *
579
     * This is not directly used as password hashing method, and thus isn't callable via the
580
     * verify_hash() method. It should be used to create signatures and might be used in other
581
     * password hashing methods.
582
     *
583
     * @see hash_hmac()
584
     * @author KC Cloyd
585
     * @link http://www.php.net/manual/en/function.hash-hmac.php#93440
586
     *
587
     * @param string $algo Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4",
588
     *                     etc..) See hash_algos() for a list of supported algorithms.
589
     * @param string $data Message to be hashed.
590
     * @param string $key  Shared secret key used for generating the HMAC variant of the message digest.
591
     * @param bool $raw_output When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
592
     * @return string
593
     */
594
    public static function hmac($algo, $data, $key, $raw_output = false) {
595
        // use native function if available and not in unit test
596
        if(function_exists('hash_hmac') && !defined('SIMPLE_TEST')){
597
            return hash_hmac($algo, $data, $key, $raw_output);
598
        }
599
600
        $algo = strtolower($algo);
601
        $pack = 'H' . strlen($algo('test'));
602
        $size = 64;
603
        $opad = str_repeat(chr(0x5C), $size);
604
        $ipad = str_repeat(chr(0x36), $size);
605
606
        if(strlen($key) > $size) {
607
            $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
608
        } else {
609
            $key = str_pad($key, $size, chr(0x00));
610
        }
611
612
        for($i = 0; $i < strlen($key) - 1; $i++) {
613
            $opad[$i] = $opad[$i] ^ $key[$i];
614
            $ipad[$i] = $ipad[$i] ^ $key[$i];
615
        }
616
617
        $output = $algo($opad . pack($pack, $algo($ipad . $data)));
618
619
        return ($raw_output) ? pack($pack, $output) : $output;
620
    }
621
622
    /**
623
     * Use DokuWiki's secure random generator if available
624
     *
625
     * @param int $min
626
     * @param int $max
627
     * @return int
628
     */
629
    protected function random($min, $max){
630
        if(function_exists('auth_random')){
631
            return auth_random($min, $max);
632
        }else{
633
            return mt_rand($min, $max);
634
        }
635
    }
636
}
637