From 22f44d031dd846cd1ff2f032ea10bc1ff1797f42 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Tue, 1 May 2012 21:18:17 +0200 Subject: avoid integer overflow in PassHash::pmd5 method Input iteration counts are squared in the function and passing something above 30 is giving integer overflows on 32 bit systems (and causes insane iteration counts on 64bit systems). --- inc/PassHash.class.php | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'inc/PassHash.class.php') diff --git a/inc/PassHash.class.php b/inc/PassHash.class.php index 3fb1349d2..d825057f0 100644 --- a/inc/PassHash.class.php +++ b/inc/PassHash.class.php @@ -316,6 +316,11 @@ class PassHash { * Uses salted MD5 hashs. Salt is 1+8 bytes long, 1st byte is the * iteration count when given, for null salts $compute is used. * + * The actual iteration count is the given count squared, maximum is + * 30 (-> 1073741824). If a higher one is given, the function throws + * an exception. + * + * @link http://www.openwall.com/phpass/ * @param string $clear - the clear text to hash * @param string $salt - the salt to use, null for random * @param string $magic - the hash identifier (P or H) @@ -330,6 +335,12 @@ class PassHash { } $iterc = $salt[0]; // pos 0 of salt is iteration count $iter = strpos($itoa64,$iterc); + + if($iter > 30){ + throw new Exception("Too high iteration count ($iter) in ". + __class__.'::'.__function__); + } + $iter = 1 << $iter; $salt = substr($salt,1,8); -- cgit v1.2.3 From 29fbab8dda3a6c084b02400830218b57bd39aeee Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 23 Jun 2012 14:08:40 +0200 Subject: code cleanup --- inc/PassHash.class.php | 277 +++++++++++++++++++++++++------------------------ 1 file changed, 142 insertions(+), 135 deletions(-) (limited to 'inc/PassHash.class.php') diff --git a/inc/PassHash.class.php b/inc/PassHash.class.php index d825057f0..f85766723 100644 --- a/inc/PassHash.class.php +++ b/inc/PassHash.class.php @@ -16,65 +16,67 @@ class PassHash { * match true is is returned else false * * @author Andreas Gohr + * @param $clear string Clear-Text password + * @param $hash string Hash to compare against * @return bool */ - function verify_hash($clear,$hash){ - $method=''; - $salt=''; - $magic=''; + function verify_hash($clear, $hash) { + $method = ''; + $salt = ''; + $magic = ''; //determine the used method and salt $len = strlen($hash); - if(preg_match('/^\$1\$([^\$]{0,8})\$/',$hash,$m)){ + if(preg_match('/^\$1\$([^\$]{0,8})\$/', $hash, $m)) { $method = 'smd5'; $salt = $m[1]; $magic = '1'; - }elseif(preg_match('/^\$apr1\$([^\$]{0,8})\$/',$hash,$m)){ + } elseif(preg_match('/^\$apr1\$([^\$]{0,8})\$/', $hash, $m)) { $method = 'apr1'; $salt = $m[1]; $magic = 'apr1'; - }elseif(preg_match('/^\$P\$(.{31})$/',$hash,$m)){ + } elseif(preg_match('/^\$P\$(.{31})$/', $hash, $m)) { $method = 'pmd5'; $salt = $m[1]; $magic = 'P'; - }elseif(preg_match('/^\$H\$(.{31})$/',$hash,$m)){ + } elseif(preg_match('/^\$H\$(.{31})$/', $hash, $m)) { $method = 'pmd5'; $salt = $m[1]; $magic = 'H'; - }elseif(preg_match('/^sha1\$(.{5})\$/',$hash,$m)){ + } elseif(preg_match('/^sha1\$(.{5})\$/', $hash, $m)) { $method = 'djangosha1'; $salt = $m[1]; - }elseif(preg_match('/^md5\$(.{5})\$/',$hash,$m)){ + } elseif(preg_match('/^md5\$(.{5})\$/', $hash, $m)) { $method = 'djangomd5'; $salt = $m[1]; - }elseif(preg_match('/^\$2a\$(.{2})\$/',$hash,$m)){ + } elseif(preg_match('/^\$2a\$(.{2})\$/', $hash, $m)) { $method = 'bcrypt'; $salt = $hash; - }elseif(substr($hash,0,6) == '{SSHA}'){ + } elseif(substr($hash, 0, 6) == '{SSHA}') { $method = 'ssha'; - $salt = substr(base64_decode(substr($hash, 6)),20); - }elseif(substr($hash,0,6) == '{SMD5}'){ + $salt = substr(base64_decode(substr($hash, 6)), 20); + } elseif(substr($hash, 0, 6) == '{SMD5}') { $method = 'lsmd5'; - $salt = substr(base64_decode(substr($hash, 6)),16); - }elseif($len == 32){ + $salt = substr(base64_decode(substr($hash, 6)), 16); + } elseif($len == 32) { $method = 'md5'; - }elseif($len == 40){ + } elseif($len == 40) { $method = 'sha1'; - }elseif($len == 16){ + } elseif($len == 16) { $method = 'mysql'; - }elseif($len == 41 && $hash[0] == '*'){ + } elseif($len == 41 && $hash[0] == '*') { $method = 'my411'; - }elseif($len == 34){ + } elseif($len == 34) { $method = 'kmd5'; $salt = $hash; - }else{ + } else { $method = 'crypt'; - $salt = substr($hash,0,2); + $salt = substr($hash, 0, 2); } //crypt and compare $call = 'hash_'.$method; - if($this->$call($clear,$salt,$magic) === $hash){ + if($this->$call($clear, $salt, $magic) === $hash) { return true; } return false; @@ -83,13 +85,14 @@ class PassHash { /** * Create a random salt * - * @param int $len - The length of the salt + * @param int $len The length of the salt + * @return string */ - public function gen_salt($len=32){ + public function gen_salt($len = 32) { $salt = ''; $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - for($i=0; $i<$len; $i++){ - $salt .= $chars[mt_rand(0,61)]; + for($i = 0; $i < $len; $i++) { + $salt .= $chars[mt_rand(0, 61)]; } return $salt; } @@ -100,12 +103,12 @@ class PassHash { * If $salt is not null, the value is kept, but the lenght restriction is * applied. * - * @param stringref $salt - The salt, pass null if you want one generated - * @param int $len - The length of the salt + * @param string &$salt The salt, pass null if you want one generated + * @param int $len The length of the salt */ - public function init_salt(&$salt,$len=32){ + public function init_salt(&$salt, $len = 32) { if(is_null($salt)) $salt = $this->gen_salt($len); - if(strlen($salt) > $len) $salt = substr($salt,0,$len); + if(strlen($salt) > $len) $salt = substr($salt, 0, $len); } // Password hashing methods follow below @@ -122,36 +125,37 @@ class PassHash { * @author Andreas Gohr * @author * @link http://de.php.net/manual/en/function.crypt.php#73619 - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @param string $magic - the hash identifier (apr1 or 1) - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_smd5($clear, $salt=null){ - $this->init_salt($salt,8); + public function hash_smd5($clear, $salt = null) { + $this->init_salt($salt, 8); - if(defined('CRYPT_MD5') && CRYPT_MD5){ - return crypt($clear,'$1$'.$salt.'$'); - }else{ + if(defined('CRYPT_MD5') && CRYPT_MD5) { + return crypt($clear, '$1$'.$salt.'$'); + } else { // Fall back to PHP-only implementation return $this->hash_apr1($clear, $salt, '1'); } } - /** * Password hashing method 'lsmd5' * * Uses salted MD5 hashs. Salt is 8 bytes long. * * This is the format used by LDAP. + * + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_lsmd5($clear, $salt=null){ - $this->init_salt($salt,8); + public function hash_lsmd5($clear, $salt = null) { + $this->init_salt($salt, 8); return "{SMD5}".base64_encode(md5($clear.$salt, true).$salt); } - /** * Password hashing method 'apr1' * @@ -161,17 +165,17 @@ class PassHash { * * @author * @link http://de.php.net/manual/en/function.crypt.php#73619 - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @param string $magic - the hash identifier (apr1 or 1) - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @param string $magic The hash identifier (apr1 or 1) + * @return string Hashed password */ - public function hash_apr1($clear, $salt=null, $magic='apr1'){ - $this->init_salt($salt,8); + public function hash_apr1($clear, $salt = null, $magic = 'apr1') { + $this->init_salt($salt, 8); - $len = strlen($clear); + $len = strlen($clear); $text = $clear.'$'.$magic.'$'.$salt; - $bin = pack("H32", md5($clear.$salt.$clear)); + $bin = pack("H32", md5($clear.$salt.$clear)); for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); } @@ -181,22 +185,24 @@ class PassHash { $bin = pack("H32", md5($text)); for($i = 0; $i < 1000; $i++) { $new = ($i & 1) ? $clear : $bin; - if ($i % 3) $new .= $salt; - if ($i % 7) $new .= $clear; + if($i % 3) $new .= $salt; + if($i % 7) $new .= $clear; $new .= ($i & 1) ? $bin : $clear; $bin = pack("H32", md5($new)); } $tmp = ''; - for ($i = 0; $i < 5; $i++) { + for($i = 0; $i < 5; $i++) { $k = $i + 6; $j = $i + 12; - if ($j == 16) $j = 5; + if($j == 16) $j = 5; $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp; } $tmp = chr(0).chr(0).$bin[11].$tmp; - $tmp = strtr(strrev(substr(base64_encode($tmp), 2)), - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + $tmp = strtr( + strrev(substr(base64_encode($tmp), 2)), + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + ); return '$'.$magic.'$'.$salt.'$'.$tmp; } @@ -205,10 +211,10 @@ class PassHash { * * Uses MD5 hashs. * - * @param string $clear - the clear text to hash - * @returns string - hashed password + * @param string $clear The clear text to hash + * @return string Hashed password */ - public function hash_md5($clear){ + public function hash_md5($clear) { return md5($clear); } @@ -217,10 +223,10 @@ class PassHash { * * Uses SHA1 hashs. * - * @param string $clear - the clear text to hash - * @returns string - hashed password + * @param string $clear The clear text to hash + * @return string Hashed password */ - public function hash_sha1($clear){ + public function hash_sha1($clear) { return sha1($clear); } @@ -229,12 +235,12 @@ class PassHash { * * Uses salted SHA1 hashs. Salt is 4 bytes long. * - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_ssha($clear, $salt=null){ - $this->init_salt($salt,4); + public function hash_ssha($clear, $salt = null) { + $this->init_salt($salt, 4); return '{SSHA}'.base64_encode(pack("H*", sha1($clear.$salt)).$salt); } @@ -243,13 +249,13 @@ class PassHash { * * Uses salted crypt hashs. Salt is 2 bytes long. * - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_crypt($clear, $salt=null){ - $this->init_salt($salt,2); - return crypt($clear,$salt); + public function hash_crypt($clear, $salt = null) { + $this->init_salt($salt, 2); + return crypt($clear, $salt); } /** @@ -259,16 +265,16 @@ class PassHash { * * @link http://www.php.net/mysql * @author - * @param string $clear - the clear text to hash - * @returns string - hashed password + * @param string $clear The clear text to hash + * @return string Hashed password */ - public function hash_mysql($clear){ - $nr=0x50305735; - $nr2=0x12345671; - $add=7; + public function hash_mysql($clear) { + $nr = 0x50305735; + $nr2 = 0x12345671; + $add = 7; $charArr = preg_split("//", $clear); - foreach ($charArr as $char) { - if (($char == '') || ($char == ' ') || ($char == '\t')) continue; + foreach($charArr as $char) { + if(($char == '') || ($char == ' ') || ($char == '\t')) continue; $charVal = ord($char); $nr ^= ((($nr & 63) + $add) * $charVal) + ($nr << 8); $nr2 += ($nr2 << 8) ^ $nr; @@ -282,10 +288,10 @@ class PassHash { * * Uses SHA1 hashs. This method is used by MySQL 4.11 and above * - * @param string $clear - the clear text to hash - * @returns string - hashed password + * @param string $clear The clear text to hash + * @return string Hashed password */ - public function hash_my411($clear){ + public function hash_my411($clear) { return '*'.sha1(pack("H*", sha1($clear))); } @@ -297,16 +303,16 @@ class PassHash { * Salt is 2 bytes long, but stored at position 16, so you need to pass at * least 18 bytes. You can pass the crypted hash as salt. * - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_kmd5($clear, $salt=null){ + public function hash_kmd5($clear, $salt = null) { $this->init_salt($salt); - $key = substr($salt, 16, 2); - $hash1 = strtolower(md5($key . md5($clear))); - $hash2 = substr($hash1, 0, 16) . $key . substr($hash1, 16); + $key = substr($salt, 16, 2); + $hash1 = strtolower(md5($key.md5($clear))); + $hash2 = substr($hash1, 0, 16).$key.substr($hash1, 16); return $hash2; } @@ -321,54 +327,55 @@ class PassHash { * an exception. * * @link http://www.openwall.com/phpass/ - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @param string $magic - the hash identifier (P or H) - * @param int $compute - the iteration count for new passwords - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @param string $magic The hash identifier (P or H) + * @param int $compute The iteration count for new passwords + * @throws Exception + * @return string Hashed password */ - public function hash_pmd5($clear, $salt=null, $magic='P',$compute=8){ + public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) { $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - if(is_null($salt)){ + if(is_null($salt)) { $this->init_salt($salt); $salt = $itoa64[$compute].$salt; // prefix iteration count } $iterc = $salt[0]; // pos 0 of salt is iteration count - $iter = strpos($itoa64,$iterc); + $iter = strpos($itoa64, $iterc); - if($iter > 30){ + if($iter > 30) { throw new Exception("Too high iteration count ($iter) in ". - __class__.'::'.__function__); + __CLASS__.'::'.__FUNCTION__); } $iter = 1 << $iter; - $salt = substr($salt,1,8); + $salt = substr($salt, 1, 8); // iterate - $hash = md5($salt . $clear, true); + $hash = md5($salt.$clear, true); do { - $hash = md5($hash . $clear, true); - } while (--$iter); + $hash = md5($hash.$clear, true); + } while(--$iter); // encode $output = ''; - $count = 16; - $i = 0; + $count = 16; + $i = 0; do { $value = ord($hash[$i++]); $output .= $itoa64[$value & 0x3f]; - if ($i < $count) + if($i < $count) $value |= ord($hash[$i]) << 8; $output .= $itoa64[($value >> 6) & 0x3f]; - if ($i++ >= $count) + if($i++ >= $count) break; - if ($i < $count) + if($i < $count) $value |= ord($hash[$i]) << 16; $output .= $itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) + if($i++ >= $count) break; $output .= $itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); + } while($i < $count); return '$'.$magic.'$'.$iterc.$salt.$output; } @@ -376,7 +383,7 @@ class PassHash { /** * Alias for hash_pmd5 */ - public function hash_hmd5($clear, $salt=null, $magic='H', $compute=8){ + public function hash_hmd5($clear, $salt = null, $magic = 'H', $compute = 8) { return $this->hash_pmd5($clear, $salt, $magic, $compute); } @@ -387,12 +394,12 @@ class PassHash { * This is used by the Django Python framework * * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_djangosha1($clear, $salt=null){ - $this->init_salt($salt,5); + public function hash_djangosha1($clear, $salt = null) { + $this->init_salt($salt, 5); return 'sha1$'.$salt.'$'.sha1($salt.$clear); } @@ -403,16 +410,15 @@ class PassHash { * This is used by the Django Python framework * * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @return string Hashed password */ - public function hash_djangomd5($clear, $salt=null){ - $this->init_salt($salt,5); + public function hash_djangomd5($clear, $salt = null) { + $this->init_salt($salt, 5); return 'md5$'.$salt.'$'.md5($salt.$clear); } - /** * Passwordhashing method 'bcrypt' * @@ -424,20 +430,21 @@ class PassHash { * will break. When no salt is given, the iteration count can be set * through the $compute variable. * - * @param string $clear - the clear text to hash - * @param string $salt - the salt to use, null for random - * @param int $compute - the iteration count (between 4 and 31) - * @returns string - hashed password + * @param string $clear The clear text to hash + * @param string $salt The salt to use, null for random + * @param int $compute The iteration count (between 4 and 31) + * @throws Exception + * @return string Hashed password */ - public function hash_bcrypt($clear, $salt=null, $compute=8){ - if(!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH != 1){ + public function hash_bcrypt($clear, $salt = null, $compute = 8) { + if(!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH != 1) { throw new Exception('This PHP installation has no bcrypt support'); } - if(is_null($salt)){ + if(is_null($salt)) { if($compute < 4 || $compute > 31) $compute = 8; $salt = '$2a$'.str_pad($compute, 2, '0', STR_PAD_LEFT).'$'. - $this->gen_salt(22); + $this->gen_salt(22); } return crypt($clear, $salt); -- cgit v1.2.3