Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.

Commit

Permalink
Merge branch 'security/2.1.4-rng-redux' into security/2.1.4
Browse files Browse the repository at this point in the history
  • Loading branch information
weierophinney committed Mar 13, 2013
2 parents 11ce90d + ac4207a commit 96b690d
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 20 deletions.
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -79,6 +79,27 @@ New polyfill support was created which does the following:
The functionality works with both Composer and ZF2's autoloading support, using
either PSR-0 or classmaps. All typehinting is preserved.

#### Security fix: Better RNG support

The `Zend\Math\Rand` component generates random bytes using the OpenSSL
or Mcrypt extensions when available but will otherwise use PHP's
`mt_rand()` function as a fallback. All outputs from `mt_rand()` are
predictable for the same PHP process if an attacker can brute force
the seed - which can be done if the attacker has access to a random number
generated by `mt_rand` or the session ID (if generated without using additional
entropy).

Zend Framework have revised the `Zend\Math\Rand` component to replace the
current `mt_rand()` fallback for OpenSSL/Mcrypt with Anthony Ferrara's
[RandomLib](https://github.com/ircmaxell/RandomLib), incorporating an additional
entropy source based on [source code published by George
Argyros](https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP). The new
fallback collects entropy from numerous sources other than PHP's internal seed
mechanism and extracts random bytes from the resulting mixed entropy pool.

For more information on this security vector, please see
[ZF2013-02](http://framework.zend.com/security/ZF2013-02).

Please see [CHANGELOG.md](CHANGELOG.md).

### SYSTEM REQUIREMENTS
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Expand Up @@ -13,11 +13,14 @@
},
"require-dev": {
"doctrine/common": ">=2.1",
"ircmaxell/random-lib": "dev-master",
"ircmaxell/security-lib": "dev-master",
"phpunit/PHPUnit": "3.7.*"
},
"suggest": {
"doctrine/common": "Doctrine\\Common >=2.1 for annotation features",
"ext-intl": "ext/intl for i18n features",
"ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable",
"pecl-weakref": "Implementation of weak references for Zend\\Stdlib\\CallbackHandler",
"zendframework/zendpdf": "ZendPdf for creating PDF representations of barcodes",
"zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha for rendering ReCaptchas in Zend\\Captcha and/or Zend\\Form"
Expand Down
77 changes: 57 additions & 20 deletions library/Zend/Math/Rand.php
Expand Up @@ -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
*
Expand All @@ -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;
}

/**
Expand Down
114 changes: 114 additions & 0 deletions library/Zend/Math/Source/HashTiming.php
@@ -0,0 +1,114 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Math\Source;

use RandomLib;
use SecurityLib\Strength;

/**
* Author:
* George Argyros <argyros.george@gmail.com>
*
* 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 <organization> 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);
}

}
3 changes: 3 additions & 0 deletions library/Zend/Math/composer.json
Expand Up @@ -15,6 +15,9 @@
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable"
}
"extra": {
"branch-alias": {
"dev-master": "2.1-dev",
Expand Down
25 changes: 25 additions & 0 deletions tests/ZendTest/Math/RandTest.php
Expand Up @@ -10,7 +10,9 @@

namespace ZendTest\Math;

use Zend\Math;
use Zend\Math\Rand;
use RandomLib;

/**
* @category Zend
Expand Down Expand Up @@ -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);
}
}

0 comments on commit 96b690d

Please sign in to comment.