aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engine/classes/ElggCrypto.php181
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);
}
/**