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 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 13 * @license LGPL2 14 */ 15class 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 <andi@splitbrain.org> 24 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 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 = ''; 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 <andi@splitbrain.org> 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 <andi@splitbrain.org> 384 * @author Schplurtz le Déboulonné <Schplurtz@laposte.net> 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é <Schplurtz@laposte.net> 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é <Schplurtz@laposte.net> 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é <Schplurtz@laposte.net> 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