1 <?php
  2 /**
  3  * @package     FrameworkOnFramework
  4  * @subpackage  utils
  5  * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
  6  * @license     GNU General Public License version 2 or later; see LICENSE.txt
  7  */
  8 
  9 // Protect from unauthorized access
 10 defined('FOF_INCLUDED') or die;
 11 
 12 /**
 13  * Generates cryptographically-secure random values.
 14  */
 15 class FOFEncryptRandval implements FOFEncryptRandvalinterface
 16 {
 17     /**
 18      * @var FOFUtilsPhpfunc
 19      */
 20     protected $phpfunc;
 21 
 22     /**
 23      *
 24      * Constructor.
 25      *
 26      * @param FOFUtilsPhpfunc $phpfunc An object to intercept PHP function calls;
 27      *                         this makes testing easier.
 28      *
 29      */
 30     public function __construct(FOFUtilsPhpfunc $phpfunc = null)
 31     {
 32         if (!is_object($phpfunc) || !($phpfunc instanceof FOFUtilsPhpfunc))
 33         {
 34             $phpfunc = new FOFUtilsPhpfunc();
 35         }
 36 
 37         $this->phpfunc = $phpfunc;
 38     }
 39 
 40     /**
 41      *
 42      * Returns a cryptographically secure random value.
 43      *
 44      * @param   integer  $bytes  How many bytes to return
 45      *
 46      * @return  string
 47      */
 48     public function generate($bytes = 32)
 49     {
 50         if ($this->phpfunc->extension_loaded('openssl') && (version_compare(PHP_VERSION, '5.3.4') >= 0 || IS_WIN))
 51         {
 52             $strong = false;
 53             $randBytes = openssl_random_pseudo_bytes($bytes, $strong);
 54 
 55             if ($strong)
 56             {
 57                 return $randBytes;
 58             }
 59         }
 60 
 61         if ($this->phpfunc->extension_loaded('mcrypt'))
 62         {
 63             return $this->phpfunc->mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
 64         }
 65 
 66         return $this->genRandomBytes($bytes);
 67     }
 68 
 69     /**
 70      * Generate random bytes. Adapted from Joomla! 3.2.
 71      *
 72      * @param   integer  $length  Length of the random data to generate
 73      *
 74      * @return  string  Random binary data
 75      */
 76     public function genRandomBytes($length = 32)
 77     {
 78         $length = (int) $length;
 79         $sslStr = '';
 80 
 81         /*
 82          * Collect any entropy available in the system along with a number
 83          * of time measurements of operating system randomness.
 84          */
 85         $bitsPerRound = 2;
 86         $maxTimeMicro = 400;
 87         $shaHashLength = 20;
 88         $randomStr = '';
 89         $total = $length;
 90 
 91         // Check if we can use /dev/urandom.
 92         $urandom = false;
 93         $handle = null;
 94 
 95         // This is PHP 5.3.3 and up
 96         if ($this->phpfunc->function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
 97         {
 98             $handle = @fopen('/dev/urandom', 'rb');
 99 
100             if ($handle)
101             {
102                 $urandom = true;
103             }
104         }
105 
106         while ($length > strlen($randomStr))
107         {
108             $bytes = ($total > $shaHashLength)? $shaHashLength : $total;
109             $total -= $bytes;
110 
111             /*
112              * Collect any entropy available from the PHP system and filesystem.
113              * If we have ssl data that isn't strong, we use it once.
114              */
115             $entropy = rand() . uniqid(mt_rand(), true) . $sslStr;
116             $entropy .= implode('', @fstat(fopen(__FILE__, 'r')));
117             $entropy .= memory_get_usage();
118             $sslStr = '';
119 
120             if ($urandom)
121             {
122                 stream_set_read_buffer($handle, 0);
123                 $entropy .= @fread($handle, $bytes);
124             }
125             else
126             {
127                 /*
128                  * There is no external source of entropy so we repeat calls
129                  * to mt_rand until we are assured there's real randomness in
130                  * the result.
131                  *
132                  * Measure the time that the operations will take on average.
133                  */
134                 $samples = 3;
135                 $duration = 0;
136 
137                 for ($pass = 0; $pass < $samples; ++$pass)
138                 {
139                     $microStart = microtime(true) * 1000000;
140                     $hash = sha1(mt_rand(), true);
141 
142                     for ($count = 0; $count < 50; ++$count)
143                     {
144                         $hash = sha1($hash, true);
145                     }
146 
147                     $microEnd = microtime(true) * 1000000;
148                     $entropy .= $microStart . $microEnd;
149 
150                     if ($microStart >= $microEnd)
151                     {
152                         $microEnd += 1000000;
153                     }
154 
155                     $duration += $microEnd - $microStart;
156                 }
157 
158                 $duration = $duration / $samples;
159 
160                 /*
161                  * Based on the average time, determine the total rounds so that
162                  * the total running time is bounded to a reasonable number.
163                  */
164                 $rounds = (int) (($maxTimeMicro / $duration) * 50);
165 
166                 /*
167                  * Take additional measurements. On average we can expect
168                  * at least $bitsPerRound bits of entropy from each measurement.
169                  */
170                 $iter = $bytes * (int) ceil(8 / $bitsPerRound);
171 
172                 for ($pass = 0; $pass < $iter; ++$pass)
173                 {
174                     $microStart = microtime(true);
175                     $hash = sha1(mt_rand(), true);
176 
177                     for ($count = 0; $count < $rounds; ++$count)
178                     {
179                         $hash = sha1($hash, true);
180                     }
181 
182                     $entropy .= $microStart . microtime(true);
183                 }
184             }
185 
186             $randomStr .= sha1($entropy, true);
187         }
188 
189         if ($urandom)
190         {
191             @fclose($handle);
192         }
193 
194         return substr($randomStr, 0, $length);
195     }
196 }
197