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