Elgg  Version 2.3
ElggCrypto.php
Go to the documentation of this file.
1 <?php
2 
3 use \Elgg\Database\SiteSecret;
4 
13 class ElggCrypto {
14 
18  const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789';
19 
23  const CHARS_HEX = '0123456789abcdef';
24 
28  private $site_secret;
29 
35  public function __construct(SiteSecret $site_secret = null) {
36  $this->site_secret = $site_secret;
37  }
38 
72  public function getRandomBytes($length) {
73  if (is_callable('random_bytes')) {
74  try {
75  return random_bytes($length);
76  } catch (\Exception $e) {}
77  }
78 
79  $SSLstr = '4'; // http://xkcd.com/221/
80 
85  if (function_exists('openssl_random_pseudo_bytes') && substr(PHP_OS, 0, 3) !== 'WIN') {
86  $SSLstr = openssl_random_pseudo_bytes($length, $strong);
87  if ($strong) {
88  return $SSLstr;
89  }
90  }
91 
97  if (function_exists('mcrypt_create_iv') && substr(PHP_OS, 0, 3) !== 'WIN') {
98  $str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
99  if ($str !== false) {
100  return $str;
101  }
102  }
103 
111  $str = '';
112  $bits_per_round = 2; // bits of entropy collected in each clock drift round
113  $msec_per_round = 400; // expected running time of each round in microseconds
114  $hash_len = 20; // SHA-1 Hash length
115  $total = $length; // total bytes of entropy to collect
116 
117  $handle = @fopen('/dev/urandom', 'rb');
118  if ($handle && function_exists('stream_set_read_buffer')) {
119  @stream_set_read_buffer($handle, 0);
120  }
121 
122  do {
123  $bytes = ($total > $hash_len) ? $hash_len : $total;
124  $total -= $bytes;
125 
126  //collect any entropy available from the PHP system and filesystem
127  $entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
128  $entropy .= implode('', @fstat(@fopen(__FILE__, 'r')));
129  $entropy .= memory_get_usage() . getmypid();
130  $entropy .= serialize($_ENV) . serialize($_SERVER);
131  if (function_exists('posix_times')) {
132  $entropy .= serialize(posix_times());
133  }
134  if (function_exists('zend_thread_id')) {
135  $entropy .= zend_thread_id();
136  }
137 
138  if ($handle) {
139  $entropy .= @fread($handle, $bytes);
140  } else {
141  // In the future we should consider outright refusing to generate bytes without an
142  // official random source, as many consider hacks like this irresponsible.
143 
144  // Measure the time that the operations will take on average
145  for ($i = 0; $i < 3; $i++) {
146  $c1 = microtime(true);
147  $var = sha1(mt_rand());
148  for ($j = 0; $j < 50; $j++) {
149  $var = sha1($var);
150  }
151  $c2 = microtime(true);
152  $entropy .= $c1 . $c2;
153  }
154 
155  // Based on the above measurement determine the total rounds
156  // in order to bound the total running time.
157  if ($c2 - $c1 == 0) {
158  // This occurs on some Windows systems. On a late 2013 MacBook Pro, 2.6 GHz Intel Core i7
159  // this averaged 400, so we're just going with that. With all the other entropy gathered
160  // this should be sufficient.
161  $rounds = 400;
162  } else {
163  $rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000));
164  }
165 
166  // Take the additional measurements. On average we can expect
167  // at least $bits_per_round bits of entropy from each measurement.
168  $iter = $bytes * (int) (ceil(8 / $bits_per_round));
169 
170  for ($i = 0; $i < $iter; $i++) {
171  $c1 = microtime();
172  $var = sha1(mt_rand());
173  for ($j = 0; $j < $rounds; $j++) {
174  $var = sha1($var);
175  }
176  $c2 = microtime();
177  $entropy .= $c1 . $c2;
178  }
179  }
180 
181  // We assume sha1 is a deterministic extractor for the $entropy variable.
182  $str .= sha1($entropy, true);
183 
184  } while ($length > strlen($str));
185 
186  if ($handle) {
187  @fclose($handle);
188  }
189 
190  return substr($str, 0, $length);
191  }
192 
202  public function getHmac($data, $algo = 'sha256', $key = '') {
203  if (!$key) {
204  $key = $this->site_secret->get(true);
205  }
206  return new Elgg\Security\Hmac($key, [$this, 'areEqual'], $data, $algo);
207  }
208 
228  public function getRandomString($length, $chars = null) {
229  if ($length < 1) {
230  throw new \InvalidArgumentException('Length should be >= 1');
231  }
232 
233  if (empty($chars)) {
234  $numBytes = ceil($length * 0.75);
235  $bytes = $this->getRandomBytes($numBytes);
236  $string = substr(rtrim(base64_encode($bytes), '='), 0, $length);
237 
238  // Base64 URL
239  return strtr($string, '+/', '-_');
240  }
241 
242  if ($chars == self::CHARS_HEX) {
243  // hex is easy
244  $bytes = $this->getRandomBytes(ceil($length / 2));
245  return substr(bin2hex($bytes), 0, $length);
246  }
247 
248  $listLen = strlen($chars);
249 
250  if ($listLen == 1) {
251  return str_repeat($chars, $length);
252  }
253 
254  $bytes = $this->getRandomBytes($length);
255  $pos = 0;
256  $result = '';
257  for ($i = 0; $i < $length; $i++) {
258  $pos = ($pos + ord($bytes[$i])) % $listLen;
259  $result .= $chars[$pos];
260  }
261 
262  return $result;
263  }
264 
278  public function areEqual($str1, $str2) {
279  $len1 = $this->strlen($str1);
280  $len2 = $this->strlen($str2);
281  if ($len1 !== $len2) {
282  return false;
283  }
284 
285  $status = 0;
286  for ($i = 0; $i < $len1; $i++) {
287  $status |= (ord($str1[$i]) ^ ord($str2[$i]));
288  }
289 
290  return $status === 0;
291  }
292 
311  protected function strlen($binary_string) {
312  if (function_exists('mb_strlen')) {
313  return mb_strlen($binary_string, '8bit');
314  }
315  return strlen($binary_string);
316  }
317 }
areEqual($str1, $str2)
Are two strings equal (compared in constant time)?
Definition: ElggCrypto.php:278
const CHARS_PASSWORD
Character set for temp passwords (no risk of embedded profanity/glyphs that look similar) ...
Definition: ElggCrypto.php:18
getRandomBytes($length)
Generate a string of highly randomized bytes (over the full 8-bit range).
Definition: ElggCrypto.php:72
$e
Definition: metadata.php:12
$data
Definition: opendd.php:13
$length
Definition: excerpt.php:14
$string
$key
Definition: summary.php:34
Component for creating HMAC tokens.
Definition: Hmac.php:7
__construct(SiteSecret $site_secret=null)
Constructor.
Definition: ElggCrypto.php:35
const CHARS_HEX
Character set for hexadecimal.
Definition: ElggCrypto.php:23
getRandomString($length, $chars=null)
Generate a random string of specified length.
Definition: ElggCrypto.php:228
strlen($binary_string)
Count the number of bytes in a string.
Definition: ElggCrypto.php:311
getHmac($data, $algo= 'sha256', $key= '')
Get an HMAC token builder/validator object.
Definition: ElggCrypto.php:202