PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/Resources/PHP/iSecurity/Security_Randomizer.php

https://github.com/christianjul/FLOW3-Composer
PHP | 503 lines | 319 code | 47 blank | 137 comment | 44 complexity | 5a855e6dbe133d610e786dd61361e417 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * A security library for PHP.
  4. *
  5. * This file is part of the Improved Security project.
  6. * <a href="http://www.improved-security.com/">http://www.improved-security.com/</a>
  7. *
  8. * @copyright Copyright (C) Kai Sellgren 2010
  9. * @package Improved Security
  10. * @since Version 1.00
  11. * @license http://opensource.org/licenses/lgpl-3.0.html GNU Lesser General Public License
  12. */
  13. /**
  14. * A class to generate strong random data and provide useful methods.
  15. *
  16. * @since 1.00
  17. */
  18. class Security_Randomizer
  19. {
  20. private static $chiSquareZMax = 6.0;
  21. private static $chiSquareLogSqrtPi = 0.5723649429247000870717135;
  22. private static $chiSquareISqrtPi = 0.5641895835477562869480795;
  23. private static $chiSquareBigX = 20.0;
  24. private static $entropyNumEvents = 0;
  25. private static $entropyTokenFreqs = array();
  26. private static $entropyTokenProbs = array();
  27. private static $entropyData;
  28. private static $entropyFreData;
  29. private static $entropyValue = 0;
  30. /**
  31. * This is a static class and requires no initialization.
  32. */
  33. private function __constructor()
  34. {
  35. }
  36. /**
  37. * No cloning available.
  38. */
  39. private function __clone()
  40. {
  41. }
  42. /**
  43. * Returns a random token in hex format.
  44. *
  45. * @param int $length
  46. * @return string
  47. */
  48. public static function getRandomToken($length)
  49. {
  50. return bin2hex(Security_Randomizer::getRandomBytes($length));
  51. }
  52. /**
  53. * Returns a random boolean value.
  54. *
  55. * @return bool
  56. */
  57. public static function getRandomBoolean()
  58. {
  59. $randomByte = Security_Randomizer::getRandomBytes(1);
  60. return (ord($randomByte) % 2) ? true : false;
  61. }
  62. /**
  63. * Returns a random string based on the given length and the character set.
  64. *
  65. * @param int $length
  66. * @param string $charset
  67. * @return string
  68. */
  69. public static function getRandomString($length, $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
  70. {
  71. $string = '';
  72. for ($a = 0; $a < $length; $a++)
  73. {
  74. $string .= $charset[Security_Randomizer::getRandomInteger(0,strlen((binary) $charset)-1)];
  75. }
  76. return $string;
  77. }
  78. /**
  79. * Returns a random integer.
  80. *
  81. * @param int $min
  82. * @param int $max
  83. * @return int
  84. */
  85. public static function getRandomInteger($min, $max)
  86. {
  87. if ($min > $max)
  88. throw new Exception('The maximum value cannot be smaller than the minimum value.');
  89. // First we need to determine how many bytes we need to construct $min-$max range.
  90. $difference = $max-$min;
  91. $bytesNeeded = ceil($difference/256);
  92. $randomBytes = Security_Randomizer::getRandomBytes($bytesNeeded);
  93. $sum = 0;
  94. for ($a = 0; $a < $bytesNeeded; $a++)
  95. $sum += ord($randomBytes[$a]);
  96. $sum = $sum % ($difference + 1);
  97. return $sum + $min;
  98. }
  99. /**
  100. * Returns a random float between 0 and 1.
  101. *
  102. * @return float
  103. */
  104. public static function getRandomFloat()
  105. {
  106. // The maximum precision on this platform.
  107. $maximumPrecision = strlen('' . 1/3) - 2;
  108. $float = '';
  109. for ($a = 0; $a < $maximumPrecision; $a++)
  110. $float .= Security_Randomizer::getRandomInteger(0,9);
  111. // All numbers are 0.
  112. if (array_sum(str_split($float)) == 0)
  113. $float = (Security_Randomizer::getRandomBoolean() ? '1.' : '0.') . $float;
  114. else
  115. $float = '0.' . $float;
  116. return (float) $float;
  117. }
  118. /**
  119. * Returns a random GUID.
  120. *
  121. * @return string
  122. */
  123. public static function getRandomGUID()
  124. {
  125. $hex = strtoupper(bin2hex(Security_Randomizer::getRandomBytes(16)));
  126. return substr($hex,0,8) . '-' . substr($hex,8,4) . '-' . substr($hex,12,4) . '-' . substr($hex,16,4) . '-' . substr($hex,20,12);
  127. }
  128. /**
  129. * This method will generate strong random data for cryptographic use.
  130. *
  131. * @param int $length
  132. * @return binary
  133. */
  134. public static function getRandomBytes($length)
  135. {
  136. $length = (int) $length;
  137. if ($length < 1)
  138. throw new Exception('Length cannot be less than 1.');
  139. // Works on systems that have OpenSSL installed, PHP 5.3 and OpenSSL extension loaded.
  140. if (function_exists('openssl_random_pseudo_bytes'))
  141. {
  142. $random = openssl_random_pseudo_bytes($length, $strong);
  143. if ($strong)
  144. return (binary) $random;
  145. }
  146. // Only execute on unix based systems
  147. if (DIRECTORY_SEPARATOR === '/') {
  148. // Works on Sun Solaris, Unix and Linux systems.
  149. $fp = @fopen('/dev/urandom', 'rb');
  150. if ($fp)
  151. {
  152. $random = fread($fp, $length);
  153. fclose($fp);
  154. return (binary) $random;
  155. }
  156. // Works on servers with OpenSSL installed and PHP < 5.3.
  157. // TODO: Make it to work when the install path is different.
  158. $fp = @popen("/usr/bin/openssl rand $length", 'rb');
  159. if ($fp)
  160. {
  161. $random = @stream_get_contents($fp);
  162. pclose($fp);
  163. if ($random)
  164. return (binary) $random;
  165. }
  166. }
  167. // Works on Windows x86.
  168. if (class_exists('COM'))
  169. {
  170. try
  171. {
  172. $csp = new COM('CAPICOM.Utilities.1');
  173. // We are stripping because sometimes the method appends newlines?
  174. $random = substr((binary) base64_decode($csp->getrandom($length, 0)), 0, $length);
  175. unset($csp);
  176. return (binary) $random;
  177. }
  178. catch (Exception $e)
  179. {
  180. }
  181. }
  182. // PHP has a bug that prevents you from creating a byte array via variants. Thus, no CSP support for Windows x64.
  183. // If someone is able to circumvent this problem, please email me.
  184. // Could work on Windows x64.
  185. if (false)
  186. {
  187. if (class_exists('DOTNET'))
  188. {
  189. try
  190. {
  191. $csp = new DOTNET("mscorlib", "System.Security.Cryptography.RNGCryptoServiceProvider");
  192. $array = array_fill(0, $length, null);
  193. $variant = new VARIANT($array, VT_ARRAY | VT_UI1);
  194. $csp->GetBytes($variant);
  195. unset($csp);
  196. return (binary) implode('', $array);
  197. }
  198. catch (Exception $e)
  199. {
  200. }
  201. }
  202. }
  203. // This is random data from OpenSSL that was marked as "weak". It's better than mt_rand().
  204. if (isset($random))
  205. {
  206. return (binary) $random;
  207. }
  208. // Falling back to PHP's mt_rand() and the rest if nothing worked.
  209. // This basically means we are either on a Windows x64 system without OpenSSL or on a really weird system. :|
  210. $random = '';
  211. $backtrace = debug_backtrace();
  212. $stat = stat($backtrace[0]['file']); // Using the name of the caller script.
  213. for ($a = 0; $a < $length; $a++)
  214. {
  215. // Since we can't use any good random generators, we need to use as many poor "random" sources as possible.
  216. $source = mt_rand(); // Weak pseudo random.
  217. $source += microtime(true); // Non-random and is poor in tight loops - yes, like this one.
  218. // $source += uniqid('', true); // The only real reason to use uniqid() here is due to its use of the internal LCG.
  219. $source += memory_get_usage(); // Has a weak avalance effect and is predictable.
  220. $source += getmypid(); // Non-random and doesn't change until the next request.
  221. $source += $stat[7] + substr($stat[8], -3, 3) + substr($stat[9], -3, 3) + substr($stat[10], -3, 3); // File stats.
  222. // Let's make it a byte.
  223. $random .= chr($source % 255);
  224. }
  225. return (binary) $random;
  226. }
  227. /**
  228. * Calculates the arithmetic mean for the given data.
  229. *
  230. * @param mixed $data
  231. * @return float
  232. */
  233. public static function calculateArithmeticMean($data)
  234. {
  235. $value = 0;
  236. for ($a = 0, $b = strlen((binary) $data); $a < $b; $a++)
  237. $value += ord($data[$a]);
  238. return $value/$b;
  239. }
  240. /**
  241. * Calculates the amount of entropy in the given data.
  242. *
  243. * @param mixed $data
  244. * @return float
  245. */
  246. public static function calculateEntropy($data)
  247. {
  248. self::$entropyNumEvents = 0;
  249. self::$entropyTokenFreqs = array();
  250. self::$entropyTokenProbs = array();
  251. self::$entropyValue = 0;
  252. self::$entropyData = $data;
  253. self::$entropyNumEvents = strlen((binary) self::$entropyData);
  254. self::entropyFrequencies();
  255. foreach (self::$entropyTokenFreqs as $token => $frequency)
  256. {
  257. self::$entropyTokenProbs[$token] = $frequency / self::$entropyNumEvents;
  258. self::$entropyValue += self::$entropyTokenProbs[$token] * log(self::$entropyTokenProbs[$token], 2);
  259. }
  260. self::$entropyValue = -self::$entropyValue;
  261. return self::$entropyValue;
  262. }
  263. /**
  264. * Calculates PI with Monte Carlo using the given data.
  265. *
  266. * @param mixed $data
  267. * @return float
  268. */
  269. public static function calculateMonteCarlo($random)
  270. {
  271. $monten = 6;
  272. $inCirc = pow(pow(256.0,$monten / 2) - 1, 2.0);
  273. $mp = 0;
  274. $mCount = 0;
  275. $inMont = 0;
  276. $data = '';
  277. $caret = 0;
  278. $size = strlen((binary) $random);
  279. while (true)
  280. {
  281. if ($caret >= $size)
  282. break;
  283. $data .= substr((binary) $random, $caret, 1);
  284. $caret += 1;
  285. $mp++;
  286. if ($mp >= $monten && strlen((binary) $data) == 6) // every 6th, = 6 bytes
  287. {
  288. $mp = 0;
  289. $mCount++;
  290. $montex = $montey = 0;
  291. for ($mj = 0;$mj < $monten / 2; $mj++)
  292. {
  293. $montex = ($montex * 256.0) + ord($data[$mj]);
  294. $montey = ($montey * 256.0) + ord($data[($monten / 2) + $mj]);
  295. }
  296. if ((($montex * $montex) + ($montey * $montey)) <= $inCirc)
  297. $inMont++;
  298. $data = '';
  299. }
  300. }
  301. $montePi = 4.0 * ($inMont / $mCount);
  302. return $montePi;
  303. }
  304. /**
  305. * Calculates random exceeding for the Chi Square value of the given data.
  306. * This is the most important single factor when considering data to be random.
  307. *
  308. * @param mixed $data
  309. * @return float
  310. */
  311. public static function calculateChiSquare($data)
  312. {
  313. $cCount = array();
  314. for ($a = 0, $b = strlen((binary) $data); $a < $b; $a++)
  315. {
  316. if (isset($cCount[ord($data[$a])]))
  317. $cCount[ord($data[$a])]++;
  318. else
  319. $cCount[ord($data[$a])] = 1;
  320. }
  321. $expC = strlen((binary) $data)/256;
  322. $chiSq = 0;
  323. for ($i = 0; $i < 256; $i++)
  324. {
  325. $a = (isset($cCount[$i]) ? $cCount[$i] : 0) - $expC;
  326. $chiSq += ($a * $a) / $expC;
  327. }
  328. return (self::chiSquarePoChiSq(round($chiSq, 2), 255) * 100);
  329. }
  330. /**
  331. * A private method for Chi Square calculations.
  332. *
  333. * @param float $x
  334. * @return float
  335. */
  336. private static function chiSquareEx($x)
  337. {
  338. return (($x < -self::$chiSquareBigX) ? 0.0 : exp($x));
  339. }
  340. /**
  341. * A private method for Chi Square calculations.
  342. *
  343. * @param float $ax
  344. * @param float $df
  345. * @return float
  346. */
  347. private static function chiSquarePoChiSq($ax, $df)
  348. {
  349. $x = $ax;
  350. if ($x <= 0.0 || $df < 1)
  351. return 1.0;
  352. $a = 0.5 * $x;
  353. $even = (2 * (int) ($df / 2)) == $df;
  354. if ($df > 1)
  355. $y = self::chiSquareEx(-$a);
  356. $s = ($even ? $y : (2.0 * self::chiSquarePoZ(-sqrt($x))));
  357. if ($df > 2)
  358. {
  359. $x = 0.5 * ($df - 1.0);
  360. $z = ($even ? 1.0 : 0.5);
  361. if ($a > self::$chiSquareBigX)
  362. {
  363. $e = ($even ? 0.0 : self::$chiSquareLogSqrtPi);
  364. $c = log($a);
  365. while ($z <= $x)
  366. {
  367. $e = log($z) + $e;
  368. $s += self::chiSquareEx($c * $z - $a - $e);
  369. $z += 1.0;
  370. }
  371. return ($s);
  372. }
  373. else
  374. {
  375. $e = ($even ? 1.0 : (self::$chiSquareISqrtPi / sqrt($a)));
  376. $c = 0.0;
  377. while ($z <= $x)
  378. {
  379. $e = $e * ($a / $z);
  380. $c = $c + $e;
  381. $z += 1.0;
  382. }
  383. return ($c * $y + $s);
  384. }
  385. }
  386. else
  387. return $s;
  388. }
  389. /**
  390. * A private method for Chi Square calculations.
  391. *
  392. * @param float $z
  393. * @return float
  394. */
  395. private static function chiSquarePoZ($z)
  396. {
  397. if ($z == 0.0)
  398. $x = 0.0;
  399. else
  400. {
  401. $y = 0.5 * abs($z);
  402. if ($y >= (self::$chiSquareZMax * 0.5))
  403. $x = 1.0;
  404. else if ($y < 1.0)
  405. {
  406. $w = $y * $y;
  407. $x = ((((((((0.000124818987 * $w
  408. -0.001075204047) * $w +0.005198775019) * $w
  409. -0.019198292004) * $w +0.059054035642) * $w
  410. -0.151968751364) * $w +0.319152932694) * $w
  411. -0.531923007300) * $w +0.797884560593) * $y * 2.0;
  412. }
  413. else
  414. {
  415. $y -= 2.0;
  416. $x = (((((((((((((-0.000045255659 * $y
  417. +0.000152529290) * $y -0.000019538132) * $y
  418. -0.000676904986) * $y +0.001390604284) * $y
  419. -0.000794620820) * $y -0.002034254874) * $y
  420. +0.006549791214) * $y -0.010557625006) * $y
  421. +0.011630447319) * $y -0.009279453341) * $y
  422. +0.005353579108) * $y -0.002141268741) * $y
  423. +0.000535310849) * $y +0.999936657524;
  424. }
  425. }
  426. return ($z > 0.0 ? (($x + 1.0) * 0.5) : ((1.0 - $x) * 0.5));
  427. }
  428. /**
  429. * A private method for returning a byte of a certain position.
  430. *
  431. * @param integer $x
  432. * @return binary
  433. */
  434. private static function entropyDataPosition($x)
  435. {
  436. return substr((binary) self::$entropyData, $x, 1);
  437. }
  438. /**
  439. * A private method for calculating frequencies.
  440. *
  441. * @return void
  442. */
  443. private static function entropyFrequencies()
  444. {
  445. $tokenFreqs = array();
  446. for ($i = 0; $i < self::$entropyNumEvents; $i++)
  447. {
  448. $tmp = ord(self::entropyDataPosition($i));
  449. if (isset($tokenFreqs[$tmp]))
  450. $tokenFreqs[$tmp]++;
  451. else
  452. $tokenFreqs[$tmp] = 1;
  453. }
  454. self::$entropyTokenFreqs = $tokenFreqs;
  455. }
  456. }