diff --git a/composer.json b/composer.json index b37957b..9710467 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ }, "extra": { "branch-alias": { - "dev-master": "2.4-dev", - "dev-develop": "2.5-dev" + "dev-master": "2.1-dev", + "dev-develop": "2.2-dev" } }, "autoload-dev": { diff --git a/src/Rand.php b/src/Rand.php index e127726..ac6fa16 100644 --- a/src/Rand.php +++ b/src/Rand.php @@ -9,11 +9,21 @@ namespace Zend\Math; +use RandomLib; + /** * Pseudorandom number generator (PRNG) */ abstract class Rand { + + /** + * Alternative random byte generator using RandomLib + * + * @var RandomLib\Generator + */ + protected static $generator = null; + /** * Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback * @@ -27,34 +37,61 @@ public static function getBytes($length, $strong = false) if ($length <= 0) { return false; } - if (extension_loaded('openssl')) { - $rand = openssl_random_pseudo_bytes($length, $secure); - if ($secure === true) { - return $rand; + $bytes = ''; + if (function_exists('openssl_random_pseudo_bytes') + && (version_compare(PHP_VERSION, '5.3.4') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $bytes = openssl_random_pseudo_bytes($length, $usable); + if (true === $usable) { + return $bytes; } } - if (extension_loaded('mcrypt')) { - // PHP bug #55169 - // @see https://bugs.php.net/bug.php?id=55169 - if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || - version_compare(PHP_VERSION, '5.3.7') >= 0) { - $rand = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); - if ($rand !== false && strlen($rand) === $length) { - return $rand; - } + if (function_exists('mcrypt_create_iv') + && (version_compare(PHP_VERSION, '5.3.7') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($bytes !== false && strlen($bytes) === $length) { + return $bytes; } } - if ($strong) { - throw new Exception\RuntimeException( + $checkAlternatives = (file_exists('/dev/urandom') && is_readable('/dev/urandom')) + || class_exists('\\COM', false); + if (true === $strong && false === $checkAlternatives) { + throw new Exception\RuntimeException ( 'This PHP environment doesn\'t support secure random number generation. ' . - 'Please consider to install the OpenSSL and/or Mcrypt extensions' + 'Please consider installing the OpenSSL and/or Mcrypt extensions' ); } - $rand = ''; - for ($i = 0; $i < $length; $i++) { - $rand .= chr(mt_rand(0, 255)); + $generator = self::getAlternativeGenerator(); + return $generator->generate($length); + } + + /** + * Retrieve a fallback/alternative RNG generator + * + * @return RandomLib\Generator + */ + public static function getAlternativeGenerator() + { + if (!is_null(self::$generator)) { + return self::$generator; + } + if (!class_exists('RandomLib\\Factory')) { + throw new Exception\RuntimeException( + 'The RandomLib fallback pseudorandom number generator (PRNG) ' + . ' must be installed in the absence of the OpenSSL and ' + . 'Mcrypt extensions' + ); } - return $rand; + $factory = new RandomLib\Factory; + $factory->registerSource( + 'HashTiming', + 'Zend\Math\Source\HashTiming' + ); + self::$generator = $factory->getMediumStrengthGenerator(); + return self::$generator; } /** diff --git a/src/Source/HashTiming.php b/src/Source/HashTiming.php new file mode 100644 index 0000000..2dd0e82 --- /dev/null +++ b/src/Source/HashTiming.php @@ -0,0 +1,114 @@ + + * + * Copyright (c) 2012, George Argyros + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * + * The function is providing, at least at the systems tested :), + * $len bytes of entropy under any PHP installation or operating system. + * The execution time should be at most 10-20 ms in any system. + * + * Modified by Padraic Brady as part of Zend Framework to use 25% of the + * original version's iterations. + */ +class HashTiming implements RandomLib\Source +{ + + /** + * Return an instance of Strength indicating the strength of the source + * + * @return Strength An instance of one of the strength classes + */ + public static function getStrength() + { + return new Strength(Strength::VERYLOW); + } + + /** + * Generate a random string of the specified size + * + * @param int $size The size of the requested random string + * + * @return string A string of the requested size + */ + public function generate($size) + { + $result = ''; + $entropy = ''; + $msec_per_round = 400; + $bits_per_round = 2; + $total = $size; + $bytes = 0; + $hash_length = 20; + $rounds = 0; + while (strlen($result) < $size) { + $bytes = ($total > $hash_length)? $hash_length : $total; + $total -= $bytes; + for ($i=1; $i < 3; $i++) { + $t1 = microtime(true); + $seed = mt_rand(); + for ($j=1; $j < 50; $j++) { + $seed = sha1($seed); + } + $t2 = microtime(true); + $entropy .= $t1 . $t2; + } + $div = (int) (($t2 - $t1) * 1000000); + if ($div <= 0) { + $div = 400; + } + $rounds = (int) ($msec_per_round * 50 / $div); + $iter = $bytes * (int) (ceil(8 / $bits_per_round)); + for ($i = 0; $i < $iter; $i ++) { + $t1 = microtime(); + $seed = sha1(mt_rand()); + for ($j = 0; $j < $rounds; $j++) { + $seed = sha1($seed); + } + $t2 = microtime(); + $entropy .= $t1 . $t2; + } + $result .= sha1($entropy, true); + } + return substr($result, 0, $size); + } + +} diff --git a/test/RandTest.php b/test/RandTest.php index dcff169..41fa883 100644 --- a/test/RandTest.php +++ b/test/RandTest.php @@ -10,7 +10,9 @@ namespace ZendTest\Math; +use Zend\Math; use Zend\Math\Rand; +use RandomLib; /** * @category Zend @@ -127,4 +129,27 @@ public function testGetStringBase64() $this->assertTrue(preg_match('#^[0-9a-zA-Z+/]+$#', $rand) === 1); } } + + public function testHashTimingSourceStrengthIsVeryLow() + { + $this->assertEquals(1, (string) Math\Source\HashTiming::getStrength()); + } + + public function testHashTimingSourceStrengthIsRandomWithCorrectLength() + { + $source = new Math\Source\HashTiming; + $rand = $source->generate(32); + $this->assertTrue(32 === strlen($rand)); + $rand2 = $source->generate(32); + $this->assertNotEquals($rand, $rand2); + } + + public function testAltGeneratorIsRandomWithCorrectLength() + { + $source = Math\Rand::getAlternativeGenerator(); + $rand = $source->generate(32); + $this->assertTrue(32 === strlen($rand)); + $rand2 = $source->generate(32); + $this->assertNotEquals($rand, $rand2); + } }