diff options
Diffstat (limited to 'engine')
-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); } /** |