Refactor crypt_random (renaming it to crypt_random_string)

...and update all the calls to it accordingly
This commit is contained in:
terrafrost 2012-12-16 02:20:16 -06:00
parent a731220785
commit 35832fe2a1
5 changed files with 214 additions and 147 deletions

View File

@ -79,11 +79,11 @@ if (!class_exists('Math_BigInteger')) {
/**
* Include Crypt_Random
*/
// the class_exists() will only be called if the crypt_random function hasn't been defined and
// the class_exists() will only be called if the crypt_random_string function hasn't been defined and
// will trigger a call to __autoload() if you're wanting to auto-load classes
// call function_exists() a second time to stop the require_once from being called outside
// of the auto loader
if (!function_exists('crypt_random') && !class_exists('Crypt_Random') && !function_exists('crypt_random')) {
if (!function_exists('crypt_random_string') && !class_exists('Crypt_Random') && !function_exists('crypt_random_string')) {
require_once('Crypt/Random.php');
}
@ -560,7 +560,6 @@ class Crypt_RSA {
extract($this->_generateMinMax($temp));
$generator = new Math_BigInteger();
$generator->setRandomGenerator('crypt_random');
$n = $this->one->copy();
if (!empty($partial)) {
@ -740,7 +739,7 @@ class Crypt_RSA {
$source.= pack('Na*', strlen($private), $private);
$hashkey = 'putty-private-key-file-mac-key';
} else {
$private.= $this->_random(16 - (strlen($private) & 15));
$private.= crypt_random_string(16 - (strlen($private) & 15));
$source.= pack('Na*', strlen($private), $private);
if (!class_exists('Crypt_AES')) {
require_once('Crypt/AES.php');
@ -800,7 +799,7 @@ class Crypt_RSA {
$RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
if (!empty($this->password) || is_string($this->password)) {
$iv = $this->_random(8);
$iv = crypt_random_string(8);
$symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key
$symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8);
if (!class_exists('Crypt_TripleDES')) {
@ -1733,23 +1732,6 @@ class Crypt_RSA {
$this->sLen = $sLen;
}
/**
* Generates a random string x bytes long
*
* @access public
* @param Integer $bytes
* @param optional Integer $nonzero
* @return String
*/
function _random($bytes, $nonzero = false)
{
$temp = '';
for ($i = 0; $i < $bytes; $i++) {
$temp.= chr(crypt_random($nonzero, 255));
}
return $temp;
}
/**
* Integer-to-Octet-String primitive
*
@ -1832,7 +1814,6 @@ class Crypt_RSA {
}
$one = new Math_BigInteger(1);
$one->setRandomGenerator('crypt_random');
$r = $one->random($one, $smallest->subtract($one));
@ -2040,7 +2021,7 @@ class Crypt_RSA {
$lHash = $this->hash->hash($l);
$ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
$db = $lHash . $ps . chr(1) . $m;
$seed = $this->_random($this->hLen);
$seed = crypt_random_string($this->hLen);
$dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
$maskedDB = $db ^ $dbMask;
$seedMask = $this->_mgf1($maskedDB, $this->hLen);
@ -2154,8 +2135,13 @@ class Crypt_RSA {
}
// EME-PKCS1-v1_5 encoding
$ps = $this->_random($this->k - $mLen - 3, true);
$psLen = $this->k - $mLen - 3;
$ps = '';
while (strlen($ps) != $psLen) {
$temp = crypt_random_string($psLen - strlen($ps));
$temp = str_replace("\x00", '', $temp);
$ps.= $temp;
}
$em = chr(0) . chr(2) . $ps . chr(0) . $m;
// RSA encryption
@ -2251,7 +2237,7 @@ class Crypt_RSA {
return false;
}
$salt = $this->_random($sLen);
$salt = crypt_random_string($sLen);
$m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
$h = $this->hash->hash($m2);
$ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);

View File

@ -11,7 +11,7 @@
* <?php
* include('Crypt/Random.php');
*
* echo crypt_random();
* echo bin2hex(crypt_random_string(8));
* ?>
* </code>
*
@ -43,78 +43,129 @@
*/
/**
* Generate a random value.
* Generate a random string.
*
* On 32-bit machines, the largest distance that can exist between $min and $max is 2**31.
* If $min and $max are farther apart than that then the last ($max - range) numbers.
* Although microoptimizations are generally discouraged as they impair readability this function is ripe with
* microoptimizations because this function has the potential of being called a huge number of times.
* eg. for RSA key generation.
*
* Depending on how this is being used, it may be worth while to write a replacement. For example,
* a PHP-based web app that stores its data in an SQL database can collect more entropy than this function
* can.
*
* @param optional Integer $min
* @param optional Integer $max
* @return Integer
* @param Integer $length
* @return String
* @access public
*/
function crypt_random($min = 0, $max = 0x7FFFFFFF)
{
if ($min == $max) {
return $min;
}
if (function_exists('openssl_random_pseudo_bytes')) {
// openssl_random_pseudo_bytes() is slow on windows per the following:
// http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
extract(unpack('Nrandom', openssl_random_pseudo_bytes(4)));
return abs($random) % ($max - $min) + $min;
function crypt_random_string($length) {
// PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
if ((PHP_OS & "\xDF\xDF\xDF") === 'WIN') {
// method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call.
// ie. class_alias is a function that was introduced in PHP 5.3
if (function_exists('mcrypt_create_iv') && function_exists('class_alias')) {
return mcrypt_create_iv($length);
}
// method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was,
// to quote <http://php.net/ChangeLog-5.php#5.3.4>, "possible blocking behavior". as of 5.3.4
// openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both
// call php_win32_get_random_bytes():
//
// https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008
// https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392
//
// php_win32_get_random_bytes() is defined thusly:
//
// https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80
//
// we're calling it, all the same, in the off chance that the mcrypt extension is not available
if (function_exists('openssl_random_pseudo_bytes') && version_compare(PHP_VERSION, '5.3.4', '>=')) {
return openssl_random_pseudo_bytes($length);
}
} else {
// method 1. the fastest
if (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($length);
}
// method 2
static $fp = true;
if ($fp === true) {
// warning's will be output unles the error suppression operator is used. errors such as
// "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
$fp = @fopen('/dev/urandom', 'rb');
}
if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
return fread($urandom, $length);
}
// method 3. pretty much does the same thing as method 2 per the following url:
// https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
// surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're
// not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir
// restrictions or some such
if (function_exists('mcrypt_create_iv')) {
return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
}
}
// at this point we have no choice but to use a pure-PHP CSPRNG
// see http://en.wikipedia.org/wiki//dev/random
static $urandom = true;
if ($urandom === true) {
// Warning's will be output unles the error suppression operator is used. Errors such as
// "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
$urandom = @fopen('/dev/urandom', 'rb');
}
if (!is_bool($urandom)) {
extract(unpack('Nrandom', fread($urandom, 4)));
// say $min = 0 and $max = 3. if we didn't do abs() then we could have stuff like this:
// -4 % 3 + 0 = -1, even though -1 < $min
return abs($random) % ($max - $min) + $min;
}
/* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called.
Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here:
http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/
The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro:
http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */
if (version_compare(PHP_VERSION, '5.2.5', '<=')) {
static $seeded;
if (!isset($seeded)) {
$seeded = true;
mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF));
// cascade entropy across multiple PHP instances by fixing the session and collecting all
// environmental variables, including the previous session data and the current session
// data
static $crypto = false, $v;
if ($crypto === false) {
// save old session data
$old_session_id = session_id();
$old_use_cookies = ini_get('session.use_cookies');
$old_session_cache_limiter = session_cache_limiter();
if (isset($_SESSION)) {
$_OLD_SESSION = $_SESSION;
}
}
static $crypto;
// The CSPRNG's Yarrow and Fortuna periodically reseed. This function can be reseeded by hitting F5
// in the browser and reloading the page.
if (!isset($crypto)) {
$key = $iv = '';
for ($i = 0; $i < 8; $i++) {
$key.= pack('n', mt_rand(0, 0xFFFF));
$iv .= pack('n', mt_rand(0, 0xFFFF));
if ($old_session_id != '') {
session_write_close();
}
session_id(1);
ini_set('session.use_cookies', 0);
session_cache_limiter('');
session_start();
$v = $seed = $_SESSION['seed'] = pack('H*', sha1(
serialize($_SERVER) .
serialize($_POST) .
serialize($_GET) .
serialize($_COOKIE) .
serialize($_GLOBAL) .
serialize($_SESSION) .
serialize($_OLD_SESSION)
));
if (!isset($_SESSION['count'])) {
$_SESSION['count'] = 0;
}
$_SESSION['count']++;
session_write_close();
// restore old session data
if ($old_session_id != '') {
session_id($old_session_id);
session_start();
ini_set('session.use_cookies', $old_use_cookies);
session_cache_limiter($old_session_cache_limiter);
} else {
if (isset($_OLD_SESSION)) {
$_SESSION = $_OLD_SESSION;
unset($_OLD_SESSION);
} else {
unset($_SESSION);
}
}
// in SSH2 a shared secret and an exchange hash are generated through the key exchange process.
// the IV client to server is the hash of that "nonce" with the letter A and for the encryption key it's the letter C.
// if the hash doesn't produce enough a key or an IV that's long enough concat successive hashes of the
// original hash and the current hash. we'll be emulating that. for more info see the following URL:
//
// http://tools.ietf.org/html/rfc4253#section-7.2
//
// see the is_string($crypto) part for an example of how to expand the keys
$key = pack('H*', sha1($seed . 'A'));
$iv = pack('H*', sha1($seed . 'C'));
switch (true) {
case class_exists('Crypt_AES'):
$crypto = new Crypt_AES(CRYPT_AES_MODE_CTR);
@ -129,14 +180,47 @@ function crypt_random($min = 0, $max = 0x7FFFFFFF)
$crypto = new Crypt_RC4();
break;
default:
extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF)))));
return abs($random) % ($max - $min) + $min;
$crypto = $seed;
return crypt_random_string($length);
}
$crypto->setKey($key);
$crypto->setIV($iv);
$crypto->enableContinuousBuffer();
}
extract(unpack('Nrandom', $crypto->encrypt("\0\0\0\0")));
return abs($random) % ($max - $min) + $min;
}
if (is_string($crypto)) {
// the following is based off of ANSI X9.31:
//
// http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
//
// OpenSSL uses that same standard for it's random numbers:
//
// http://www.opensource.apple.com/source/OpenSSL/OpenSSL-38/openssl/fips-1.0/rand/fips_rand.c
// (do a search for "ANS X9.31 A.2.4")
//
// ANSI X9.31 recommends ciphers be used and phpseclib does use them if they're available (see
// later on in the code) but if they're not we'll use sha1
$result = '';
while (strlen($result) < $length) { // each loop adds 20 bytes
// microtime() isn't packed as "densely" as it could be but then neither is that the idea.
// the idea is simply to ensure that each "block" has a unique element to it.
$i = pack('H*', sha1(microtime()));
$r = pack('H*', sha1($i ^ $v));
$v = pack('H*', sha1($r ^ $i));
$result.= $r;
}
return substr($result, 0, $length);
}
//return $crypto->encrypt(str_repeat("\0", $length));
$result = '';
while (strlen($result) < $length) {
$i = $crypto->encrypt(microtime());
$r = $crypto->encrypt($i ^ $v);
$v = $crypto->encrypt($r ^ $i);
$result.= $r;
}
return substr($result, 0, $length);
}

View File

@ -2995,20 +2995,13 @@ class Math_BigInteger {
/**
* Set random number generator function
*
* $generator should be the name of a random generating function whose first parameter is the minimum
* value and whose second parameter is the maximum value. If this function needs to be seeded, it should
* be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime()
* This function is deprecated.
*
* If the random generating function is not explicitly set, it'll be assumed to be mt_rand().
*
* @see random()
* @see randomPrime()
* @param optional String $generator
* @param String $generator
* @access public
*/
function setRandomGenerator($generator)
{
$this->generator = $generator;
}
/**
@ -3045,27 +3038,43 @@ class Math_BigInteger {
$max = $max->subtract($min);
$max = ltrim($max->toBytes(), chr(0));
$size = strlen($max) - 1;
$random = '';
$bytes = $size & 1;
for ($i = 0; $i < $bytes; ++$i) {
$random.= chr($generator(0, 255));
}
$blocks = $size >> 1;
for ($i = 0; $i < $blocks; ++$i) {
// mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems
$random.= pack('n', $generator(0, 0xFFFF));
}
$temp = new Math_BigInteger($random, 256);
if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) {
$random = chr($generator(0, ord($max[0]) - 1)) . $random;
$crypt_random = function_exists('crypt_random_string') || (!class_exists('Crypt_Random') && function_exists('crypt_random_string'));
if ($crypt_random) {
$random = crypt_random_string($size);
} else {
$random = chr($generator(0, ord($max[0]) )) . $random;
$random = '';
if ($size & 1) {
$random.= chr(mt_rand(0, 255));
}
$blocks = $size >> 1;
for ($i = 0; $i < $blocks; ++$i) {
// mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems
$random.= pack('n', mt_rand(0, 0xFFFF));
}
}
$random = new Math_BigInteger($random, 256);
$fragment = new Math_BigInteger($random, 256);
$leading = $fragment->compare(new Math_BigInteger(substr($max, 1), 256)) > 0 ?
ord($max[0]) - 1 : ord($max[0]);
if (!$crypt_random) {
$msb = chr(mt_rand(0, $leading));
} else {
$cutoff = floor(0xFF / $leading) * $leading;
while (true) {
$msb = ord(crypt_random_string(1));
if ($msb <= $cutoff) {
$msb%= $leading;
break;
}
}
$msb = chr($msb);
}
$random = new Math_BigInteger($msb . $random, 256);
return $this->_normalize($random->add($min));
}

View File

@ -104,11 +104,11 @@ if (!class_exists('Crypt_RC4')) {
/**
* Include Crypt_Random
*/
// the class_exists() will only be called if the crypt_random function hasn't been defined and
// the class_exists() will only be called if the crypt_random_string function hasn't been defined and
// will trigger a call to __autoload() if you're wanting to auto-load classes
// call function_exists() a second time to stop the require_once from being called outside
// of the auto loader
if (!function_exists('crypt_random') && !class_exists('Crypt_Random') && !function_exists('crypt_random')) {
if (!function_exists('crypt_random_string') && !class_exists('Crypt_Random') && !function_exists('crypt_random_string')) {
require_once('Crypt/Random.php');
}
@ -542,10 +542,7 @@ class Net_SSH1 {
$session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie));
$session_key = '';
for ($i = 0; $i < 32; $i++) {
$session_key.= chr(crypt_random(0, 255));
}
$session_key = crypt_random_string(32);
$double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0));
if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) {
@ -1032,11 +1029,7 @@ class Net_SSH1 {
$length = strlen($data) + 4;
$padding_length = 8 - ($length & 7);
$padding = '';
for ($i = 0; $i < $padding_length; $i++) {
$padding.= chr(crypt_random(0, 255));
}
$padding = crypt_random_string(8 - ($length & 7));
$data = $padding . $data;
$data.= pack('N', $this->_crc($data));
@ -1213,8 +1206,11 @@ class Net_SSH1 {
$temp = chr(0) . chr(2);
$modulus = $key[1]->toBytes();
$length = strlen($modulus) - strlen($m) - 3;
for ($i = 0; $i < $length; $i++) {
$temp.= chr(crypt_random(1, 255));
$temp = '';
while (strlen($temp) != $length) {
$block = crypt_random_string($length - strlen($temp));
$block = str_replace("\x00", '', $block);
$temp.= $block;
}
$temp.= chr(0) . $m;

View File

@ -80,11 +80,11 @@ if (!class_exists('Math_BigInteger')) {
/**
* Include Crypt_Random
*/
// the class_exists() will only be called if the crypt_random function hasn't been defined and
// the class_exists() will only be called if the crypt_random_string function hasn't been defined and
// will trigger a call to __autoload() if you're wanting to auto-load classes
// call function_exists() a second time to stop the require_once from being called outside
// of the auto loader
if (!function_exists('crypt_random') && !class_exists('Crypt_Random') && !function_exists('crypt_random')) {
if (!function_exists('crypt_random_string') && !class_exists('Crypt_Random') && !function_exists('crypt_random_string')) {
require_once('Crypt/Random.php');
}
@ -967,10 +967,7 @@ class Net_SSH2 {
$compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms);
}
$client_cookie = '';
for ($i = 0; $i < 16; $i++) {
$client_cookie.= chr(crypt_random(0, 255));
}
$client_cookie = crypt_random_string(16);
$response = $kexinit_payload_server;
$this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
@ -1148,7 +1145,6 @@ class Net_SSH2 {
$g = new Math_BigInteger(2);
$x = new Math_BigInteger();
$x->setRandomGenerator('crypt_random');
$x = $x->random(new Math_BigInteger(1), $q);
$e = $g->modPow($x, $p);
@ -2400,11 +2396,7 @@ class Net_SSH2 {
$packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen($data) - 5;
$padding = '';
for ($i = 0; $i < $padding_length; $i++) {
$padding.= chr(crypt_random(0, 255));
}
$padding = crypt_random_string($padding_length);
// we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
$packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);