diff options
| author | Steve Clay <steve@mrclay.org> | 2013-06-13 14:05:33 -0400 | 
|---|---|---|
| committer | Paweł Sroka <srokap@gmail.com> | 2013-11-04 04:24:47 +0100 | 
| commit | 4bcca223409915e075dd08f0aaca9f23ea63f610 (patch) | |
| tree | 328b73f4a0bbb019c22c91c963c6c0a6cf018980 /engine/classes | |
| parent | e98f933857548be9cd078416a93011ea9c2f3e3a (diff) | |
| download | elgg-4bcca223409915e075dd08f0aaca9f23ea63f610.tar.gz elgg-4bcca223409915e075dd08f0aaca9f23ea63f610.tar.bz2  | |
PRNG replace Drupal's with George Argyros'
Diffstat (limited to 'engine/classes')
| -rw-r--r-- | engine/classes/ElggCrypto.php | 181 | 
1 files changed, 128 insertions, 53 deletions
diff --git a/engine/classes/ElggCrypto.php b/engine/classes/ElggCrypto.php index 364af4542..358b721ea 100644 --- a/engine/classes/ElggCrypto.php +++ b/engine/classes/ElggCrypto.php @@ -15,70 +15,145 @@ class ElggCrypto {  	const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789';  	/** -	 * Returns a string of highly randomized bytes (over the full 8-bit range). +	 * Generate a string of highly randomized bytes (over the full 8-bit range).  	 * -	 * This function is better than simply calling mt_rand() or any other built-in -	 * PHP function because it can return a long string of bytes (compared to < 4 -	 * bytes normally from mt_rand()) and uses the best available pseudo-random -	 * source. +	 * @param int $length Number of bytes needed +	 * @return string Random bytes  	 * -	 * @param int $count The number of characters (bytes) to return in the string. -	 * @return string +	 * @author George Argyros <argyros.george@gmail.com> +	 * @copyright 2012, George Argyros. All rights reserved. +	 * @license Modified BSD +	 * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original  	 * -	 * @copyright Copyright 2001 - 2012 by the original authors -	 *            https://github.com/drupal/drupal/blob/7.x/COPYRIGHT.txt -	 * @license   https://github.com/drupal/drupal/blob/7.x/LICENSE.txt GPL 2 +	 * Redistribution and use in source and binary forms, with or without +	 * modification, are permitted provided that the following conditions are met: +	 *    * Redistributions of source code must retain the above copyright +	 *      notice, this list of conditions and the following disclaimer. +	 *    * Redistributions in binary form must reproduce the above copyright +	 *      notice, this list of conditions and the following disclaimer in the +	 *      documentation and/or other materials provided with the distribution. +	 *    * Neither the name of the <organization> nor the +	 *      names of its contributors may be used to endorse or promote products +	 *      derived from this software without specific prior written permission.  	 * -	 * @see https://github.com/drupal/drupal/blob/7.x/includes/bootstrap.inc#L1942 +	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +	 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +	 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +	 * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY +	 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +	 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +	 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +	 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +	 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +	 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  	 */ -	public static function getRandomBytes($count)  { -		// $random_state does not use drupal_static as it stores random bytes. -		static $random_state, $bytes, $php_compatible; -		// Initialize on the first call. The contents of $_SERVER includes a mix of -		// user-specific and system information that varies a little with each page. -		if (!isset($random_state)) { -			$random_state = print_r($_SERVER, true); -			if (function_exists('getmypid')) { -				// Further initialize with the somewhat random PHP process ID. -				$random_state .= getmypid(); +	public function getRandomBytes($length) { +		/** +		 * Our primary choice for a cryptographic strong randomness function is +		 * openssl_random_pseudo_bytes. +		 */ +		$SSLstr = '4'; // http://xkcd.com/221/ +		if (function_exists('openssl_random_pseudo_bytes') +				&& (version_compare(PHP_VERSION, '5.3.4') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { +			$SSLstr = openssl_random_pseudo_bytes($length, $strong); +			if ($strong) { +				return $SSLstr;  			} -			$bytes = '';  		} -		if (strlen($bytes) < $count) { -			// PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes() -			// locking on Windows and rendered it unusable. -			if (!isset($php_compatible)) { -				$php_compatible = version_compare(PHP_VERSION, '5.3.4', '>='); + +		/** +		 * If mcrypt extension is available then we use it to gather entropy from +		 * the operating system's PRNG. This is better than reading /dev/urandom +		 * directly since it avoids reading larger blocks of data than needed. +		 * Older versions of mcrypt_create_iv may be broken or take too much time +		 * to finish so we only use this function with PHP 5.3.7 and above. +		 * @see https://bugs.php.net/bug.php?id=55169 +		 */ +		if (function_exists('mcrypt_create_iv') +				&& (version_compare(PHP_VERSION, '5.3.7') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { +			$str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); +			if ($str !== false) { +				return $str; +			} +		} + +		/** +		 * No build-in crypto randomness function found. We collect any entropy +		 * available in the PHP core PRNGs along with some filesystem info and memory +		 * stats. To make this data cryptographically strong we add data either from +		 * /dev/urandom or if its unavailable, we gather entropy by measuring the +		 * time needed to compute a number of SHA-1 hashes. +		 */ +		$str = ''; +		$bits_per_round = 2; // bits of entropy collected in each clock drift round +		$msec_per_round = 400; // expected running time of each round in microseconds +		$hash_len = 20; // SHA-1 Hash length +		$total = $length; // total bytes of entropy to collect + +		$handle = @fopen('/dev/urandom', 'rb'); +		if ($handle && function_exists('stream_set_read_buffer')) { +			@stream_set_read_buffer($handle, 0); +		} + +		do { +			$bytes = ($total > $hash_len)? $hash_len : $total; +			$total -= $bytes; + +			//collect any entropy available from the PHP system and filesystem +			$entropy = rand() . uniqid(mt_rand(), true) . $SSLstr; +			$entropy .= implode('', @fstat(@fopen( __FILE__, 'r'))); +			$entropy .= memory_get_usage() . getmypid(); +			$entropy .= serialize($_ENV) . serialize($_SERVER); +			if (function_exists('posix_times')) { +				$entropy .= serialize(posix_times());  			} -			// /dev/urandom is available on many *nix systems and is considered the -			// best commonly available pseudo-random source. -			if ($fh = @fopen('/dev/urandom', 'rb')) { -				// PHP only performs buffered reads, so in reality it will always read -				// at least 4096 bytes. Thus, it costs nothing extra to read and store -				// that much so as to speed any additional invocations. -				$bytes .= fread($fh, max(4096, $count)); -				fclose($fh); -			} elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) { -				// openssl_random_pseudo_bytes() will find entropy in a system-dependent -				// way. -				$bytes .= openssl_random_pseudo_bytes($count - strlen($bytes)); +			if (function_exists('zend_thread_id')) { +				$entropy .= zend_thread_id();  			} -			// If /dev/urandom is not available or returns no bytes, this loop will -			// generate a good set of pseudo-random bytes on any system. -			// Note that it may be important that our $random_state is passed -			// through hash() prior to being rolled into $output, that the two hash() -			// invocations are different, and that the extra input into the first one - -			// the microtime() - is prepended rather than appended. This is to avoid -			// directly leaking $random_state via the $output stream, which could -			// allow for trivial prediction of further "random" numbers. -			while (strlen($bytes) < $count) { -				$random_state = hash('sha256', microtime() . mt_rand() . $random_state); -				$bytes .= hash('sha256', mt_rand() . $random_state, true); + +			if ($handle) { +				$entropy .= @fread($handle, $bytes); +			} else  { +				// Measure the time that the operations will take on average +				for ($i = 0; $i < 3; $i++) { +					$c1 = microtime(true); +					$var = sha1(mt_rand()); +					for ($j = 0; $j < 50; $j++) { +						$var = sha1($var); +					} +					$c2 = microtime(true); +					$entropy .= $c1 . $c2; +				} + +				// Based on the above measurement determine the total rounds +				// in order to bound the total running time. +				$rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000)); + +				// Take the additional measurements. On average we can expect +				// at least $bits_per_round bits of entropy from each measurement. +				$iter = $bytes * (int) (ceil(8 / $bits_per_round)); + +				for ($i = 0; $i < $iter; $i++) { +					$c1 = microtime(); +					$var = sha1(mt_rand()); +					for ($j = 0; $j < $rounds; $j++) { +						$var = sha1($var); +					} +					$c2 = microtime(); +					$entropy .= $c1 . $c2; +				}  			} + +			// We assume sha1 is a deterministic extractor for the $entropy variable. +			$str .= sha1($entropy, true); + +		} while ($length > strlen($str)); + +		if ($handle) { +			@fclose($handle);  		} -		$output = substr($bytes, 0, $count); -		$bytes = substr($bytes, $count); -		return $output; + +		return substr($str, 0, $length);  	}  	/**  | 
