]> gitweb.fluxo.info Git - lorea/elgg.git/commitdiff
PRNG replace Drupal's with George Argyros'
authorSteve Clay <steve@mrclay.org>
Thu, 13 Jun 2013 18:05:33 +0000 (14:05 -0400)
committerPaweł Sroka <srokap@gmail.com>
Mon, 4 Nov 2013 03:24:47 +0000 (04:24 +0100)
engine/classes/ElggCrypto.php

index 364af4542487b6f98379f70dfa78614b13ec49fb..358b721ea77a546fb980d8fe45ddbba76dd1a00b 100644 (file)
@@ -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);
        }
 
        /**