security #17359 do not ship with a custom rng implementation (xabbuh, fabpot)
This PR was merged into the 2.3 branch. Discussion ---------- do not ship with a custom rng implementation Commits -------b91441c
removed obsolete tests, fixed composer.jsonfcd3160
do not ship with a custom rng implementation
This commit is contained in:
commit
54d7f2dfb9
|
@ -18,6 +18,7 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.3",
|
"php": ">=5.3.3",
|
||||||
"doctrine/common": "~2.4",
|
"doctrine/common": "~2.4",
|
||||||
|
"paragonie/random_compat": "~1.0",
|
||||||
"twig/twig": "~1.23|~2.0",
|
"twig/twig": "~1.23|~2.0",
|
||||||
"psr/log": "~1.0"
|
"psr/log": "~1.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Security\Core\Util;
|
namespace Symfony\Component\Security\Core\Util;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A secure random number generator implementation.
|
* A secure random number generator implementation.
|
||||||
*
|
*
|
||||||
|
@ -21,98 +19,11 @@ use Psr\Log\LoggerInterface;
|
||||||
*/
|
*/
|
||||||
final class SecureRandom implements SecureRandomInterface
|
final class SecureRandom implements SecureRandomInterface
|
||||||
{
|
{
|
||||||
private $logger;
|
|
||||||
private $useOpenSsl;
|
|
||||||
private $seed;
|
|
||||||
private $seedUpdated;
|
|
||||||
private $seedLastUpdatedAt;
|
|
||||||
private $seedFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* Be aware that a guessable seed will severely compromise the PRNG
|
|
||||||
* algorithm that is employed.
|
|
||||||
*
|
|
||||||
* @param string $seedFile
|
|
||||||
* @param LoggerInterface $logger
|
|
||||||
*/
|
|
||||||
public function __construct($seedFile = null, LoggerInterface $logger = null)
|
|
||||||
{
|
|
||||||
$this->seedFile = $seedFile;
|
|
||||||
$this->logger = $logger;
|
|
||||||
|
|
||||||
$isUnsupportedPhp = '\\' === DIRECTORY_SEPARATOR && PHP_VERSION_ID < 50304;
|
|
||||||
|
|
||||||
// determine whether to use OpenSSL
|
|
||||||
if (!function_exists('random_bytes') && ($isUnsupportedPhp || !function_exists('openssl_random_pseudo_bytes'))) {
|
|
||||||
if (null !== $this->logger) {
|
|
||||||
$this->logger->notice('It is recommended that you install the "paragonie/random_compat" library or enable the "openssl" extension for random number generation.');
|
|
||||||
}
|
|
||||||
$this->useOpenSsl = false;
|
|
||||||
} else {
|
|
||||||
$this->useOpenSsl = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function nextBytes($nbBytes)
|
public function nextBytes($nbBytes)
|
||||||
{
|
{
|
||||||
if (function_exists('random_bytes')) {
|
return random_bytes($nbBytes);
|
||||||
return random_bytes($nbBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// try OpenSSL
|
|
||||||
if ($this->useOpenSsl) {
|
|
||||||
$bytes = openssl_random_pseudo_bytes($nbBytes, $strong);
|
|
||||||
|
|
||||||
if (false !== $bytes && true === $strong) {
|
|
||||||
return $bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $this->logger) {
|
|
||||||
$this->logger->info('OpenSSL did not produce a secure random number.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize seed
|
|
||||||
if (null === $this->seed) {
|
|
||||||
if (null === $this->seedFile) {
|
|
||||||
throw new \RuntimeException('You need to specify a file path to store the seed.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_file($this->seedFile)) {
|
|
||||||
list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed();
|
|
||||||
} else {
|
|
||||||
$this->seed = uniqid(mt_rand(), true);
|
|
||||||
$this->updateSeed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$bytes = '';
|
|
||||||
while (strlen($bytes) < $nbBytes) {
|
|
||||||
static $incr = 1;
|
|
||||||
$bytes .= hash('sha512', $incr++.$this->seed.uniqid(mt_rand(), true).$nbBytes, true);
|
|
||||||
$this->seed = base64_encode(hash('sha512', $this->seed.$bytes.$nbBytes, true));
|
|
||||||
$this->updateSeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
return substr($bytes, 0, $nbBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function readSeed()
|
|
||||||
{
|
|
||||||
return json_decode(file_get_contents($this->seedFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateSeed()
|
|
||||||
{
|
|
||||||
if (!$this->seedUpdated && $this->seedLastUpdatedAt < time() - mt_rand(1, 10)) {
|
|
||||||
file_put_contents($this->seedFile, json_encode(array($this->seed, microtime(true))));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->seedUpdated = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,201 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Symfony\Component\Security\Tests\Core\Util;
|
|
||||||
|
|
||||||
use Symfony\Component\Security\Core\Util\SecureRandom;
|
|
||||||
|
|
||||||
class SecureRandomTest extends \PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* T1: Monobit test.
|
|
||||||
*
|
|
||||||
* @dataProvider getSecureRandoms
|
|
||||||
*/
|
|
||||||
public function testMonobit($secureRandom)
|
|
||||||
{
|
|
||||||
$nbOnBits = substr_count($this->getBitSequence($secureRandom, 20000), '1');
|
|
||||||
$this->assertTrue($nbOnBits > 9654 && $nbOnBits < 10346, 'Monobit test failed, number of turned on bits: '.$nbOnBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* T2: Chi-square test with 15 degrees of freedom (chi-Quadrat-Anpassungstest).
|
|
||||||
*
|
|
||||||
* @dataProvider getSecureRandoms
|
|
||||||
*/
|
|
||||||
public function testPoker($secureRandom)
|
|
||||||
{
|
|
||||||
$b = $this->getBitSequence($secureRandom, 20000);
|
|
||||||
$c = array();
|
|
||||||
for ($i = 0; $i <= 15; ++$i) {
|
|
||||||
$c[$i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($j = 1; $j <= 5000; ++$j) {
|
|
||||||
$k = 4 * $j - 1;
|
|
||||||
++$c[8 * $b[$k - 3] + 4 * $b[$k - 2] + 2 * $b[$k - 1] + $b[$k]];
|
|
||||||
}
|
|
||||||
|
|
||||||
$f = 0;
|
|
||||||
for ($i = 0; $i <= 15; ++$i) {
|
|
||||||
$f += $c[$i] * $c[$i];
|
|
||||||
}
|
|
||||||
|
|
||||||
$Y = 16 / 5000 * $f - 5000;
|
|
||||||
|
|
||||||
$this->assertTrue($Y > 1.03 && $Y < 57.4, 'Poker test failed, Y = '.$Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run test.
|
|
||||||
*
|
|
||||||
* @dataProvider getSecureRandoms
|
|
||||||
*/
|
|
||||||
public function testRun($secureRandom)
|
|
||||||
{
|
|
||||||
$b = $this->getBitSequence($secureRandom, 20000);
|
|
||||||
|
|
||||||
$runs = array();
|
|
||||||
for ($i = 1; $i <= 6; ++$i) {
|
|
||||||
$runs[$i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$addRun = function ($run) use (&$runs) {
|
|
||||||
if ($run > 6) {
|
|
||||||
$run = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
++$runs[$run];
|
|
||||||
};
|
|
||||||
|
|
||||||
$currentRun = 0;
|
|
||||||
$lastBit = null;
|
|
||||||
for ($i = 0; $i < 20000; ++$i) {
|
|
||||||
if ($lastBit === $b[$i]) {
|
|
||||||
++$currentRun;
|
|
||||||
} else {
|
|
||||||
if ($currentRun > 0) {
|
|
||||||
$addRun($currentRun);
|
|
||||||
}
|
|
||||||
|
|
||||||
$lastBit = $b[$i];
|
|
||||||
$currentRun = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($currentRun > 0) {
|
|
||||||
$addRun($currentRun);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertTrue($runs[1] > 2267 && $runs[1] < 2733, 'Runs of length 1 outside of defined interval: '.$runs[1]);
|
|
||||||
$this->assertTrue($runs[2] > 1079 && $runs[2] < 1421, 'Runs of length 2 outside of defined interval: '.$runs[2]);
|
|
||||||
$this->assertTrue($runs[3] > 502 && $runs[3] < 748, 'Runs of length 3 outside of defined interval: '.$runs[3]);
|
|
||||||
$this->assertTrue($runs[4] > 233 && $runs[4] < 402, 'Runs of length 4 outside of defined interval: '.$runs[4]);
|
|
||||||
$this->assertTrue($runs[5] > 90 && $runs[5] < 223, 'Runs of length 5 outside of defined interval: '.$runs[5]);
|
|
||||||
$this->assertTrue($runs[6] > 90 && $runs[6] < 233, 'Runs of length 6 outside of defined interval: '.$runs[6]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Long-run test.
|
|
||||||
*
|
|
||||||
* @dataProvider getSecureRandoms
|
|
||||||
*/
|
|
||||||
public function testLongRun($secureRandom)
|
|
||||||
{
|
|
||||||
$b = $this->getBitSequence($secureRandom, 20000);
|
|
||||||
|
|
||||||
$longestRun = $currentRun = 0;
|
|
||||||
$lastBit = null;
|
|
||||||
for ($i = 0; $i < 20000; ++$i) {
|
|
||||||
if ($lastBit === $b[$i]) {
|
|
||||||
++$currentRun;
|
|
||||||
} else {
|
|
||||||
if ($currentRun > $longestRun) {
|
|
||||||
$longestRun = $currentRun;
|
|
||||||
}
|
|
||||||
$lastBit = $b[$i];
|
|
||||||
$currentRun = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($currentRun > $longestRun) {
|
|
||||||
$longestRun = $currentRun;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertTrue($longestRun < 34, 'Failed longest run test: '.$longestRun);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serial Correlation (Autokorrelationstest).
|
|
||||||
*
|
|
||||||
* @dataProvider getSecureRandoms
|
|
||||||
*/
|
|
||||||
public function testSerialCorrelation($secureRandom)
|
|
||||||
{
|
|
||||||
$shift = rand(1, 5000);
|
|
||||||
$b = $this->getBitSequence($secureRandom, 20000);
|
|
||||||
|
|
||||||
$Z = 0;
|
|
||||||
for ($i = 0; $i < 5000; ++$i) {
|
|
||||||
$Z += $b[$i] === $b[$i + $shift] ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertTrue($Z > 2326 && $Z < 2674, 'Failed serial correlation test: '.$Z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSecureRandoms()
|
|
||||||
{
|
|
||||||
$secureRandoms = array();
|
|
||||||
|
|
||||||
// only add if openssl is indeed present
|
|
||||||
$secureRandom = new SecureRandom();
|
|
||||||
if ($this->hasOpenSsl($secureRandom)) {
|
|
||||||
$secureRandoms[] = array($secureRandom);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no-openssl with custom seed provider
|
|
||||||
$secureRandom = new SecureRandom(sys_get_temp_dir().'/_sf2.seed');
|
|
||||||
$this->disableOpenSsl($secureRandom);
|
|
||||||
$secureRandoms[] = array($secureRandom);
|
|
||||||
|
|
||||||
return $secureRandoms;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function disableOpenSsl($secureRandom)
|
|
||||||
{
|
|
||||||
$ref = new \ReflectionProperty($secureRandom, 'useOpenSsl');
|
|
||||||
$ref->setAccessible(true);
|
|
||||||
$ref->setValue($secureRandom, false);
|
|
||||||
$ref->setAccessible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function hasOpenSsl($secureRandom)
|
|
||||||
{
|
|
||||||
$ref = new \ReflectionProperty($secureRandom, 'useOpenSsl');
|
|
||||||
$ref->setAccessible(true);
|
|
||||||
|
|
||||||
$ret = $ref->getValue($secureRandom);
|
|
||||||
|
|
||||||
$ref->setAccessible(false);
|
|
||||||
|
|
||||||
return $ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getBitSequence($secureRandom, $length)
|
|
||||||
{
|
|
||||||
$bitSequence = '';
|
|
||||||
for ($i = 0; $i < $length; $i += 40) {
|
|
||||||
$value = unpack('H*', $secureRandom->nextBytes(5));
|
|
||||||
$value = str_pad(base_convert($value[1], 16, 2), 40, '0', STR_PAD_LEFT);
|
|
||||||
$bitSequence .= $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return substr($bitSequence, 0, $length);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,6 +17,7 @@
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.3",
|
"php": ">=5.3.3",
|
||||||
|
"paragonie/random_compat": "~1.0",
|
||||||
"symfony/event-dispatcher": "~2.2",
|
"symfony/event-dispatcher": "~2.2",
|
||||||
"symfony/http-foundation": "~2.1",
|
"symfony/http-foundation": "~2.1",
|
||||||
"symfony/http-kernel": "~2.1"
|
"symfony/http-kernel": "~2.1"
|
||||||
|
@ -43,8 +44,7 @@
|
||||||
"symfony/validator": "",
|
"symfony/validator": "",
|
||||||
"symfony/routing": "",
|
"symfony/routing": "",
|
||||||
"doctrine/dbal": "to use the built-in ACL implementation",
|
"doctrine/dbal": "to use the built-in ACL implementation",
|
||||||
"ircmaxell/password-compat": "",
|
"ircmaxell/password-compat": ""
|
||||||
"paragonie/random_compat": ""
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": { "Symfony\\Component\\Security\\": "" },
|
"psr-0": { "Symfony\\Component\\Security\\": "" },
|
||||||
|
|
Reference in New Issue