translation update
[dokuwiki.git] / inc / PassHash.class.php
blobdb6a3a77cc231e88017a136726214ffb5a5cf4f3
1 <?php
2 /**
3 * Password Hashing Class
5 * This class implements various mechanisms used to hash passwords
7 * @author Andreas Gohr <andi@splitbrain.org>
8 * @license LGPL2
9 */
10 class PassHash {
11 /**
12 * Verifies a cleartext password against a crypted hash
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
18 * @author Andreas Gohr <andi@splitbrain.org>
19 * @param $clear string Clear-Text password
20 * @param $hash string Hash to compare against
21 * @return bool
23 function verify_hash($clear, $hash) {
24 $method = '';
25 $salt = '';
26 $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('/^sha1\$(.{5})\$/', $hash, $m)) {
47 $method = 'djangosha1';
48 $salt = $m[1];
49 } elseif(preg_match('/^md5\$(.{5})\$/', $hash, $m)) {
50 $method = 'djangomd5';
51 $salt = $m[1];
52 } elseif(preg_match('/^\$2a\$(.{2})\$/', $hash, $m)) {
53 $method = 'bcrypt';
54 $salt = $hash;
55 } elseif(substr($hash, 0, 6) == '{SSHA}') {
56 $method = 'ssha';
57 $salt = substr(base64_decode(substr($hash, 6)), 20);
58 } elseif(substr($hash, 0, 6) == '{SMD5}') {
59 $method = 'lsmd5';
60 $salt = substr(base64_decode(substr($hash, 6)), 16);
61 } elseif(preg_match('/^:B:(.+?):.{32}$/', $hash, $m)) {
62 $method = 'mediawiki';
63 $salt = $m[1];
64 } elseif(preg_match('/^\$6\$(.+?)\$/', $hash, $m)) {
65 $method = 'sha512';
66 $salt = $m[1];
67 } elseif($len == 32) {
68 $method = 'md5';
69 } elseif($len == 40) {
70 $method = 'sha1';
71 } elseif($len == 16) {
72 $method = 'mysql';
73 } elseif($len == 41 && $hash[0] == '*') {
74 $method = 'my411';
75 } elseif($len == 34) {
76 $method = 'kmd5';
77 $salt = $hash;
78 } else {
79 $method = 'crypt';
80 $salt = substr($hash, 0, 2);
83 //crypt and compare
84 $call = 'hash_'.$method;
85 if($this->$call($clear, $salt, $magic) === $hash) {
86 return true;
88 return false;
91 /**
92 * Create a random salt
94 * @param int $len The length of the salt
95 * @return string
97 public function gen_salt($len = 32) {
98 $salt = '';
99 $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
100 for($i = 0; $i < $len; $i++) {
101 $salt .= $chars[$this->random(0, 61)];
103 return $salt;
107 * Initialize the passed variable with a salt if needed.
109 * If $salt is not null, the value is kept, but the lenght restriction is
110 * applied (unless, $cut is false).
112 * @param string &$salt The salt, pass null if you want one generated
113 * @param int $len The length of the salt
114 * @param bool $cut Apply length restriction to existing salt?
116 public function init_salt(&$salt, $len = 32, $cut = true) {
117 if(is_null($salt)) {
118 $salt = $this->gen_salt($len);
119 $cut = true; // for new hashes we alway apply length restriction
121 if(strlen($salt) > $len && $cut) $salt = substr($salt, 0, $len);
124 // Password hashing methods follow below
127 * Password hashing method 'smd5'
129 * Uses salted MD5 hashs. Salt is 8 bytes long.
131 * The same mechanism is used by Apache's 'apr1' method. This will
132 * fallback to a implementation in pure PHP if MD5 support is not
133 * available in crypt()
135 * @author Andreas Gohr <andi@splitbrain.org>
136 * @author <mikey_nich at hotmail dot com>
137 * @link http://de.php.net/manual/en/function.crypt.php#73619
138 * @param string $clear The clear text to hash
139 * @param string $salt The salt to use, null for random
140 * @return string Hashed password
142 public function hash_smd5($clear, $salt = null) {
143 $this->init_salt($salt, 8);
145 if(defined('CRYPT_MD5') && CRYPT_MD5 && $salt !== '') {
146 return crypt($clear, '$1$'.$salt.'$');
147 } else {
148 // Fall back to PHP-only implementation
149 return $this->hash_apr1($clear, $salt, '1');
154 * Password hashing method 'lsmd5'
156 * Uses salted MD5 hashs. Salt is 8 bytes long.
158 * This is the format used by LDAP.
160 * @param string $clear The clear text to hash
161 * @param string $salt The salt to use, null for random
162 * @return string Hashed password
164 public function hash_lsmd5($clear, $salt = null) {
165 $this->init_salt($salt, 8);
166 return "{SMD5}".base64_encode(md5($clear.$salt, true).$salt);
170 * Password hashing method 'apr1'
172 * Uses salted MD5 hashs. Salt is 8 bytes long.
174 * This is basically the same as smd1 above, but as used by Apache.
176 * @author <mikey_nich at hotmail dot com>
177 * @link http://de.php.net/manual/en/function.crypt.php#73619
178 * @param string $clear The clear text to hash
179 * @param string $salt The salt to use, null for random
180 * @param string $magic The hash identifier (apr1 or 1)
181 * @return string Hashed password
183 public function hash_apr1($clear, $salt = null, $magic = 'apr1') {
184 $this->init_salt($salt, 8);
186 $len = strlen($clear);
187 $text = $clear.'$'.$magic.'$'.$salt;
188 $bin = pack("H32", md5($clear.$salt.$clear));
189 for($i = $len; $i > 0; $i -= 16) {
190 $text .= substr($bin, 0, min(16, $i));
192 for($i = $len; $i > 0; $i >>= 1) {
193 $text .= ($i & 1) ? chr(0) : $clear{0};
195 $bin = pack("H32", md5($text));
196 for($i = 0; $i < 1000; $i++) {
197 $new = ($i & 1) ? $clear : $bin;
198 if($i % 3) $new .= $salt;
199 if($i % 7) $new .= $clear;
200 $new .= ($i & 1) ? $bin : $clear;
201 $bin = pack("H32", md5($new));
203 $tmp = '';
204 for($i = 0; $i < 5; $i++) {
205 $k = $i + 6;
206 $j = $i + 12;
207 if($j == 16) $j = 5;
208 $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
210 $tmp = chr(0).chr(0).$bin[11].$tmp;
211 $tmp = strtr(
212 strrev(substr(base64_encode($tmp), 2)),
213 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
214 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
216 return '$'.$magic.'$'.$salt.'$'.$tmp;
220 * Password hashing method 'md5'
222 * Uses MD5 hashs.
224 * @param string $clear The clear text to hash
225 * @return string Hashed password
227 public function hash_md5($clear) {
228 return md5($clear);
232 * Password hashing method 'sha1'
234 * Uses SHA1 hashs.
236 * @param string $clear The clear text to hash
237 * @return string Hashed password
239 public function hash_sha1($clear) {
240 return sha1($clear);
244 * Password hashing method 'ssha' as used by LDAP
246 * Uses salted SHA1 hashs. Salt is 4 bytes long.
248 * @param string $clear The clear text to hash
249 * @param string $salt The salt to use, null for random
250 * @return string Hashed password
252 public function hash_ssha($clear, $salt = null) {
253 $this->init_salt($salt, 4);
254 return '{SSHA}'.base64_encode(pack("H*", sha1($clear.$salt)).$salt);
258 * Password hashing method 'crypt'
260 * Uses salted crypt hashs. Salt is 2 bytes long.
262 * @param string $clear The clear text to hash
263 * @param string $salt The salt to use, null for random
264 * @return string Hashed password
266 public function hash_crypt($clear, $salt = null) {
267 $this->init_salt($salt, 2);
268 return crypt($clear, $salt);
272 * Password hashing method 'mysql'
274 * This method was used by old MySQL systems
276 * @link http://www.php.net/mysql
277 * @author <soren at byu dot edu>
278 * @param string $clear The clear text to hash
279 * @return string Hashed password
281 public function hash_mysql($clear) {
282 $nr = 0x50305735;
283 $nr2 = 0x12345671;
284 $add = 7;
285 $charArr = preg_split("//", $clear);
286 foreach($charArr as $char) {
287 if(($char == '') || ($char == ' ') || ($char == '\t')) continue;
288 $charVal = ord($char);
289 $nr ^= ((($nr & 63) + $add) * $charVal) + ($nr << 8);
290 $nr2 += ($nr2 << 8) ^ $nr;
291 $add += $charVal;
293 return sprintf("%08x%08x", ($nr & 0x7fffffff), ($nr2 & 0x7fffffff));
297 * Password hashing method 'my411'
299 * Uses SHA1 hashs. This method is used by MySQL 4.11 and above
301 * @param string $clear The clear text to hash
302 * @return string Hashed password
304 public function hash_my411($clear) {
305 return '*'.sha1(pack("H*", sha1($clear)));
309 * Password hashing method 'kmd5'
311 * Uses salted MD5 hashs.
313 * Salt is 2 bytes long, but stored at position 16, so you need to pass at
314 * least 18 bytes. You can pass the crypted hash as salt.
316 * @param string $clear The clear text to hash
317 * @param string $salt The salt to use, null for random
318 * @return string Hashed password
320 public function hash_kmd5($clear, $salt = null) {
321 $this->init_salt($salt);
323 $key = substr($salt, 16, 2);
324 $hash1 = strtolower(md5($key.md5($clear)));
325 $hash2 = substr($hash1, 0, 16).$key.substr($hash1, 16);
326 return $hash2;
330 * Password hashing method 'pmd5'
332 * Uses salted MD5 hashs. Salt is 1+8 bytes long, 1st byte is the
333 * iteration count when given, for null salts $compute is used.
335 * The actual iteration count is the given count squared, maximum is
336 * 30 (-> 1073741824). If a higher one is given, the function throws
337 * an exception.
339 * @link http://www.openwall.com/phpass/
340 * @param string $clear The clear text to hash
341 * @param string $salt The salt to use, null for random
342 * @param string $magic The hash identifier (P or H)
343 * @param int $compute The iteration count for new passwords
344 * @throws Exception
345 * @return string Hashed password
347 public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) {
348 $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
349 if(is_null($salt)) {
350 $this->init_salt($salt);
351 $salt = $itoa64[$compute].$salt; // prefix iteration count
353 $iterc = $salt[0]; // pos 0 of salt is iteration count
354 $iter = strpos($itoa64, $iterc);
356 if($iter > 30) {
357 throw new Exception("Too high iteration count ($iter) in ".
358 __CLASS__.'::'.__FUNCTION__);
361 $iter = 1 << $iter;
362 $salt = substr($salt, 1, 8);
364 // iterate
365 $hash = md5($salt.$clear, true);
366 do {
367 $hash = md5($hash.$clear, true);
368 } while(--$iter);
370 // encode
371 $output = '';
372 $count = 16;
373 $i = 0;
374 do {
375 $value = ord($hash[$i++]);
376 $output .= $itoa64[$value & 0x3f];
377 if($i < $count)
378 $value |= ord($hash[$i]) << 8;
379 $output .= $itoa64[($value >> 6) & 0x3f];
380 if($i++ >= $count)
381 break;
382 if($i < $count)
383 $value |= ord($hash[$i]) << 16;
384 $output .= $itoa64[($value >> 12) & 0x3f];
385 if($i++ >= $count)
386 break;
387 $output .= $itoa64[($value >> 18) & 0x3f];
388 } while($i < $count);
390 return '$'.$magic.'$'.$iterc.$salt.$output;
394 * Alias for hash_pmd5
396 public function hash_hmd5($clear, $salt = null, $magic = 'H', $compute = 8) {
397 return $this->hash_pmd5($clear, $salt, $magic, $compute);
401 * Password hashing method 'djangosha1'
403 * Uses salted SHA1 hashs. Salt is 5 bytes long.
404 * This is used by the Django Python framework
406 * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords
407 * @param string $clear The clear text to hash
408 * @param string $salt The salt to use, null for random
409 * @return string Hashed password
411 public function hash_djangosha1($clear, $salt = null) {
412 $this->init_salt($salt, 5);
413 return 'sha1$'.$salt.'$'.sha1($salt.$clear);
417 * Password hashing method 'djangomd5'
419 * Uses salted MD5 hashs. Salt is 5 bytes long.
420 * This is used by the Django Python framework
422 * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords
423 * @param string $clear The clear text to hash
424 * @param string $salt The salt to use, null for random
425 * @return string Hashed password
427 public function hash_djangomd5($clear, $salt = null) {
428 $this->init_salt($salt, 5);
429 return 'md5$'.$salt.'$'.md5($salt.$clear);
433 * Passwordhashing method 'bcrypt'
435 * Uses a modified blowfish algorithm called eksblowfish
436 * This method works on PHP 5.3+ only and will throw an exception
437 * if the needed crypt support isn't available
439 * A full hash should be given as salt (starting with $a2$) or this
440 * will break. When no salt is given, the iteration count can be set
441 * through the $compute variable.
443 * @param string $clear The clear text to hash
444 * @param string $salt The salt to use, null for random
445 * @param int $compute The iteration count (between 4 and 31)
446 * @throws Exception
447 * @return string Hashed password
449 public function hash_bcrypt($clear, $salt = null, $compute = 8) {
450 if(!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH != 1) {
451 throw new Exception('This PHP installation has no bcrypt support');
454 if(is_null($salt)) {
455 if($compute < 4 || $compute > 31) $compute = 8;
456 $salt = '$2a$'.str_pad($compute, 2, '0', STR_PAD_LEFT).'$'.
457 $this->gen_salt(22);
460 return crypt($clear, $salt);
464 * Password hashing method SHA512
466 * This is only supported on PHP 5.3.2 or higher and will throw an exception if
467 * the needed crypt support is not available
469 * @param string $clear The clear text to hash
470 * @param string $salt The salt to use, null for random
471 * @return string Hashed password
472 * @throws Exception
474 public function hash_sha512($clear, $salt = null) {
475 if(!defined('CRYPT_SHA512') || CRYPT_SHA512 != 1) {
476 throw new Exception('This PHP installation has no SHA512 support');
478 $this->init_salt($salt, 8, false);
479 return crypt($clear, '$6$'.$salt.'$');
483 * Password hashing method 'mediawiki'
485 * Uses salted MD5, this is referred to as Method B in MediaWiki docs. Unsalted md5
486 * method 'A' is not supported.
488 * @link http://www.mediawiki.org/wiki/Manual_talk:User_table#user_password_column
489 * @param string $clear The clear text to hash
490 * @param string $salt The salt to use, null for random
491 * @return string Hashed password
493 public function hash_mediawiki($clear, $salt = null) {
494 $this->init_salt($salt, 8, false);
495 return ':B:'.$salt.':'.md5($salt.'-'.md5($clear));
499 * Wraps around native hash_hmac() or reimplents it
501 * This is not directly used as password hashing method, and thus isn't callable via the
502 * verify_hash() method. It should be used to create signatures and might be used in other
503 * password hashing methods.
505 * @see hash_hmac()
506 * @author KC Cloyd
507 * @link http://www.php.net/manual/en/function.hash-hmac.php#93440
509 * @param string $algo Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4",
510 * etc..) See hash_algos() for a list of supported algorithms.
511 * @param string $data Message to be hashed.
512 * @param string $key Shared secret key used for generating the HMAC variant of the message digest.
513 * @param bool $raw_output When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
515 * @return string
517 public static function hmac($algo, $data, $key, $raw_output = false) {
518 // use native function if available and not in unit test
519 if(function_exists('hash_hmac') && !defined('SIMPLE_TEST')){
520 return hash_hmac($algo, $data, $key, $raw_output);
523 $algo = strtolower($algo);
524 $pack = 'H' . strlen($algo('test'));
525 $size = 64;
526 $opad = str_repeat(chr(0x5C), $size);
527 $ipad = str_repeat(chr(0x36), $size);
529 if(strlen($key) > $size) {
530 $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
531 } else {
532 $key = str_pad($key, $size, chr(0x00));
535 for($i = 0; $i < strlen($key) - 1; $i++) {
536 $opad[$i] = $opad[$i] ^ $key[$i];
537 $ipad[$i] = $ipad[$i] ^ $key[$i];
540 $output = $algo($opad . pack($pack, $algo($ipad . $data)));
542 return ($raw_output) ? pack($pack, $output) : $output;
546 * Use DokuWiki's secure random generator if available
548 * @param $min
549 * @param $max
551 * @return int
553 protected function random($min, $max){
554 if(function_exists('auth_random')){
555 return auth_random($min, $max);
556 }else{
557 return mt_rand($min, $max);