Elgg  Version 2.2
 All Classes Namespaces Files Functions Variables Pages
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  $SSLstr = '4'; // http://xkcd.com/221/
74 
79  if (function_exists('openssl_random_pseudo_bytes') && substr(PHP_OS, 0, 3) !== 'WIN') {
80  $SSLstr = openssl_random_pseudo_bytes($length, $strong);
81  if ($strong) {
82  return $SSLstr;
83  }
84  }
85 
91  if (function_exists('mcrypt_create_iv') && substr(PHP_OS, 0, 3) !== 'WIN') {
92  $str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
93  if ($str !== false) {
94  return $str;
95  }
96  }
97 
105  $str = '';
106  $bits_per_round = 2; // bits of entropy collected in each clock drift round
107  $msec_per_round = 400; // expected running time of each round in microseconds
108  $hash_len = 20; // SHA-1 Hash length
109  $total = $length; // total bytes of entropy to collect
110 
111  $handle = @fopen('/dev/urandom', 'rb');
112  if ($handle && function_exists('stream_set_read_buffer')) {
113  @stream_set_read_buffer($handle, 0);
114  }
115 
116  do {
117  $bytes = ($total > $hash_len) ? $hash_len : $total;
118  $total -= $bytes;
119 
120  //collect any entropy available from the PHP system and filesystem
121  $entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
122  $entropy .= implode('', @fstat(@fopen(__FILE__, 'r')));
123  $entropy .= memory_get_usage() . getmypid();
124  $entropy .= serialize($_ENV) . serialize($_SERVER);
125  if (function_exists('posix_times')) {
126  $entropy .= serialize(posix_times());
127  }
128  if (function_exists('zend_thread_id')) {
129  $entropy .= zend_thread_id();
130  }
131 
132  if ($handle) {
133  $entropy .= @fread($handle, $bytes);
134  } else {
135  // Measure the time that the operations will take on average
136  for ($i = 0; $i < 3; $i++) {
137  $c1 = microtime(true);
138  $var = sha1(mt_rand());
139  for ($j = 0; $j < 50; $j++) {
140  $var = sha1($var);
141  }
142  $c2 = microtime(true);
143  $entropy .= $c1 . $c2;
144  }
145 
146  // Based on the above measurement determine the total rounds
147  // in order to bound the total running time.
148  $rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000));
149 
150  // Take the additional measurements. On average we can expect
151  // at least $bits_per_round bits of entropy from each measurement.
152  $iter = $bytes * (int) (ceil(8 / $bits_per_round));
153 
154  for ($i = 0; $i < $iter; $i++) {
155  $c1 = microtime();
156  $var = sha1(mt_rand());
157  for ($j = 0; $j < $rounds; $j++) {
158  $var = sha1($var);
159  }
160  $c2 = microtime();
161  $entropy .= $c1 . $c2;
162  }
163  }
164 
165  // We assume sha1 is a deterministic extractor for the $entropy variable.
166  $str .= sha1($entropy, true);
167 
168  } while ($length > strlen($str));
169 
170  if ($handle) {
171  @fclose($handle);
172  }
173 
174  return substr($str, 0, $length);
175  }
176 
186  public function getHmac($data, $algo = 'sha256', $key = '') {
187  if (!$key) {
188  $key = $this->site_secret->get(true);
189  }
190  return new Elgg\Security\Hmac($key, [$this, 'areEqual'], $data, $algo);
191  }
192 
212  public function getRandomString($length, $chars = null) {
213  if ($length < 1) {
214  throw new \InvalidArgumentException('Length should be >= 1');
215  }
216 
217  if (empty($chars)) {
218  $numBytes = ceil($length * 0.75);
219  $bytes = $this->getRandomBytes($numBytes);
220  $string = substr(rtrim(base64_encode($bytes), '='), 0, $length);
221 
222  // Base64 URL
223  return strtr($string, '+/', '-_');
224  }
225 
226  if ($chars == self::CHARS_HEX) {
227  // hex is easy
228  $bytes = $this->getRandomBytes(ceil($length / 2));
229  return substr(bin2hex($bytes), 0, $length);
230  }
231 
232  $listLen = strlen($chars);
233 
234  if ($listLen == 1) {
235  return str_repeat($chars, $length);
236  }
237 
238  $bytes = $this->getRandomBytes($length);
239  $pos = 0;
240  $result = '';
241  for ($i = 0; $i < $length; $i++) {
242  $pos = ($pos + ord($bytes[$i])) % $listLen;
243  $result .= $chars[$pos];
244  }
245 
246  return $result;
247  }
248 
262  public function areEqual($str1, $str2) {
263  $len1 = $this->strlen($str1);
264  $len2 = $this->strlen($str2);
265  if ($len1 !== $len2) {
266  return false;
267  }
268 
269  $status = 0;
270  for ($i = 0; $i < $len1; $i++) {
271  $status |= (ord($str1[$i]) ^ ord($str2[$i]));
272  }
273 
274  return $status === 0;
275  }
276 
295  protected function strlen($binary_string) {
296  if (function_exists('mb_strlen')) {
297  return mb_strlen($binary_string, '8bit');
298  }
299  return strlen($binary_string);
300  }
301 }
areEqual($str1, $str2)
Are two strings equal (compared in constant time)?
Definition: ElggCrypto.php:262
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
$data
Definition: opendd.php:13
$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:212
strlen($binary_string)
Count the number of bytes in a string.
Definition: ElggCrypto.php:295
getHmac($data, $algo= 'sha256', $key= '')
Get an HMAC token builder/validator object.
Definition: ElggCrypto.php:186