PageRenderTime 41ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/PassHash.class.php

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