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