From 483b6238a3599595a678f995b2c7c9e9f07a7ce7 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 18:46:02 +0200 Subject: Add truly random numbers and use them in places where randomness matters --- inc/auth.php | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 6 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 537d44c01..ace98f51f 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -294,7 +294,7 @@ function auth_validateToken($token) { * @return string The auth token */ function auth_createToken() { - $token = md5(mt_rand()); + $token = md5(auth_randombytes(16)); @session_start(); // reopen the session if needed $_SESSION[DOKU_COOKIE]['auth']['token'] = $token; session_write_close(); @@ -349,6 +349,102 @@ function auth_cookiesalt($addsession = false) { return $salt; } +/** + * Return truly (pseudo) random bytes if available, otherwise fall back to mt_rand + * + * @author Mark Seecof + * @author Michael Hamann + * @link http://www.php.net/manual/de/function.mt-rand.php#83655 + * @param int $length number of bytes to get + * @throws Exception when no usable random generator is found + * @return string binary random strings + */ +function auth_randombytes($length) { + $strong = false; + $rbytes = false; + + if (function_exists('openssl_random_pseudo_bytes') + && (version_compare(PHP_VERSION, '5.3.4') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $rbytes = openssl_random_pseudo_bytes($length, $strong); + } + + if (!$strong && function_exists('mcrypt_create_iv') + && (version_compare(PHP_VERSION, '5.3.7') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $rbytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($rbytes !== false && strlen($rbytes) === $length) { + $strong = true; + } + } + + + // If no strong randoms available, try OS the specific ways + if(!$strong) { + // Unix/Linux platform + $fp = @fopen('/dev/urandom', 'rb'); + if($fp !== false) { + $rbytes = fread($fp, $length); + fclose($fp); + } + + // MS-Windows platform + if(class_exists('COM')) { + // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx + try { + $CAPI_Util = new COM('CAPICOM.Utilities.1'); + $rbytes = $CAPI_Util->GetRandom($length, 0); + + // if we ask for binary data PHP munges it, so we + // request base64 return value. + if($rbytes) $rbytes = base64_decode($rbytes); + } catch(Exception $ex) { + // fail + } + } + } + if(strlen($rbytes) < $length) $rbytes = false; + + // still no random bytes available - fall back to mt_rand() + if($rbytes === false) { + $rbytes = ''; + for ($i = 0; $i < $length; ++$i) { + $rbytes .= chr(mt_rand(0, 255)); + } + } + + return $rbytes; +} + +/** + * Random number generator using the best available source + * + * @author Michael Samuel + * @author Michael Hamann + * @param int $min + * @param int $max + * @return int + */ +function auth_random($min, $max) { + $abs_max = $max - $min; + + $nbits = 0; + for ($n = $abs_max; $n > 0; $n >>= 1) { + ++$nbits; + } + + $mask = (1 << $nbits) - 1; + do { + $bytes = auth_randombytes(PHP_INT_SIZE); + $integers = unpack('Inum', $bytes); + $integer = $integers["num"] & $mask; + } while ($integer > $abs_max); + + return $min + $integer; +} + /** * Log out the current user * @@ -703,12 +799,12 @@ function auth_pwgen($foruser = '') { //use thre syllables... for($i = 0; $i < 3; $i++) { - $data['password'] .= $c[mt_rand(0, strlen($c) - 1)]; - $data['password'] .= $v[mt_rand(0, strlen($v) - 1)]; - $data['password'] .= $a[mt_rand(0, strlen($a) - 1)]; + $data['password'] .= $c[auth_random(0, strlen($c) - 1)]; + $data['password'] .= $v[auth_random(0, strlen($v) - 1)]; + $data['password'] .= $a[auth_random(0, strlen($a) - 1)]; } //... and add a nice number and special - $data['password'] .= mt_rand(10, 99).$s[mt_rand(0, strlen($s) - 1)]; + $data['password'] .= auth_random(10, 99).$s[auth_random(0, strlen($s) - 1)]; } $evt->advise_after(); @@ -1007,7 +1103,7 @@ function act_resendpwd() { } // generate auth token - $token = md5(uniqid(mt_rand(), true)); // random secret + $token = md5(auth_randombytes(16)); // random secret $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; $url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&'); -- cgit v1.2.3 From 30d544a4c371bf69023e4d9958bc2b00d84387d9 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 18:47:58 +0200 Subject: Use a new, truly random secret for cookie encryption --- inc/auth.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index ace98f51f..a1da971ae 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -219,7 +219,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { if($auth->checkPass($user, $pass)) { // make logininfo globally available $_SERVER['REMOTE_USER'] = $user; - $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky); return true; } else { @@ -250,7 +250,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { return true; } // no we don't trust it yet - recheck pass but silent - $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session $pass = PMA_blowfish_decrypt($pass, $secret); return auth_login($user, $pass, $sticky, true); } @@ -333,14 +333,18 @@ function auth_browseruid() { * * @author Andreas Gohr * @param bool $addsession if true, the sessionid is added to the salt + * @param bool $secure if security is more important than keeping the old value * @return string */ -function auth_cookiesalt($addsession = false) { +function auth_cookiesalt($addsession = false, $secure = false) { global $conf; $file = $conf['metadir'].'/_htcookiesalt'; + if ($secure || !file_exists($file)) { + $file = $conf['metadir'].'/_htcookiesalt2'; + } $salt = io_readFile($file); if(empty($salt)) { - $salt = uniqid(rand(), true); + $salt = bin2hex(auth_randombytes(64)); io_saveFile($file, $salt); } if($addsession) { @@ -988,7 +992,7 @@ function updateprofile() { // update cookie and session with the changed data if($changes['pass']) { list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); - $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky)); + $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky); } return true; -- cgit v1.2.3 From 04369c3eae728e14962c41d1ab259f9e7ed99144 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Tue, 30 Jul 2013 18:50:28 +0200 Subject: Add AES from phpseclib and use it for cookie encryption This replaces the deprecated and broken Blowfish implementation that has previously been used and should provide a lot more security. --- inc/auth.php | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index a1da971ae..f02bfebca 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -220,7 +220,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { // make logininfo globally available $_SERVER['REMOTE_USER'] = $user; $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session - auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky); + auth_setCookie($user, auth_encrypt($pass, $secret), $sticky); return true; } else { //invalid credentials - log off @@ -251,7 +251,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { } // no we don't trust it yet - recheck pass but silent $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session - $pass = PMA_blowfish_decrypt($pass, $secret); + $pass = auth_decrypt($pass, $secret); return auth_login($user, $pass, $sticky, true); } } @@ -449,6 +449,40 @@ function auth_random($min, $max) { return $min + $integer; } +/** + * Encrypt data using the given secret using AES + * + * The mode is CBC with a random initialization vector, the key is derived + * using pbkdf2. + * + * @param string $data The data that shall be encrypted + * @param string $secret The secret/password that shall be used + * @return string The ciphertext + */ +function auth_encrypt($data, $secret) { + $iv = auth_randombytes(16); + $cipher = new Crypt_AES(); + $cipher->setPassword($secret); + + return $cipher->encrypt($iv.$data); +} + +/** + * Decrypt the given AES ciphertext + * + * The mode is CBC, the key is derived using pbkdf2 + * + * @param string $ciphertext The encrypted data + * @param string $secret The secret/password that shall be used + * @return string The decrypted data + */ +function auth_decrypt($ciphertext, $secret) { + $cipher = new Crypt_AES(); + $cipher->setPassword($secret); + + return substr($cipher->decrypt($ciphertext), 16); +} + /** * Log out the current user * @@ -992,7 +1026,7 @@ function updateprofile() { // update cookie and session with the changed data if($changes['pass']) { list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); - $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); + $pass = auth_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky); } return true; -- cgit v1.2.3 From 8269996a43469c1ce5295a22248ad9a9ab34efc8 Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Wed, 31 Jul 2013 11:56:16 +0200 Subject: auth_random: remove exception comment as there is no exception --- inc/auth.php | 1 - 1 file changed, 1 deletion(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index f02bfebca..227ee80fd 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -360,7 +360,6 @@ function auth_cookiesalt($addsession = false, $secure = false) { * @author Michael Hamann * @link http://www.php.net/manual/de/function.mt-rand.php#83655 * @param int $length number of bytes to get - * @throws Exception when no usable random generator is found * @return string binary random strings */ function auth_randombytes($length) { -- cgit v1.2.3 From 7b650cef79bb603087a8ef43b22a1f7c3d86b7ef Mon Sep 17 00:00:00 2001 From: Michael Hamann Date: Wed, 31 Jul 2013 11:56:58 +0200 Subject: auth_en/decrypt: Add explanation and more efficient decryption Added an explanation that what we do is like normal CBC but that we additionally encrypt the IV which is actually suggested by the NIST for non-random (but unique) IVs. In the decryption process it's not necessary to decrypt the IV, this should save some time. --- inc/auth.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'inc/auth.php') diff --git a/inc/auth.php b/inc/auth.php index 227ee80fd..96b80e19e 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -459,10 +459,16 @@ function auth_random($min, $max) { * @return string The ciphertext */ function auth_encrypt($data, $secret) { - $iv = auth_randombytes(16); + $iv = auth_randombytes(16); $cipher = new Crypt_AES(); $cipher->setPassword($secret); + /* + this uses the encrypted IV as IV as suggested in + http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, Appendix C + for unique but necessarily random IVs. The resulting ciphertext is + compatible to ciphertext that was created using a "normal" IV. + */ return $cipher->encrypt($iv.$data); } @@ -476,10 +482,12 @@ function auth_encrypt($data, $secret) { * @return string The decrypted data */ function auth_decrypt($ciphertext, $secret) { + $iv = substr($ciphertext, 0, 16); $cipher = new Crypt_AES(); $cipher->setPassword($secret); + $cipher->setIV($iv); - return substr($cipher->decrypt($ciphertext), 16); + return $cipher->decrypt(substr($ciphertext, 16)); } /** -- cgit v1.2.3