forked from schmittjoh/JMSSecurityExtraBundle
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added secure random number generator
- Loading branch information
1 parent
2e40b0a
commit 1595e93
Showing
16 changed files
with
563 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace JMS\SecurityExtraBundle\Security\Util; | ||
|
||
/** | ||
* NullSeedProvider implementation. | ||
* | ||
* Never use this for anything but unit testing. | ||
* | ||
* @author Johannes M. Schmitt <schmittjoh@gmail.com> | ||
*/ | ||
class NullSeedProvider implements SeedProviderInterface | ||
{ | ||
public function loadSeed() | ||
{ | ||
return array('', new \DateTime()); | ||
} | ||
|
||
public function updateSeed($seed) | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<?php | ||
|
||
namespace JMS\SecurityExtraBundle\Security\Util; | ||
|
||
use Doctrine\DBAL\Types\Type; | ||
use Doctrine\DBAL\Connection; | ||
use Symfony\Component\HttpKernel\Log\LoggerInterface; | ||
|
||
/** | ||
* A secure random number generator implementation. | ||
* | ||
* @author Johannes M. Schmitt <schmittjoh@gmail.com> | ||
*/ | ||
final class SecureRandom | ||
{ | ||
private $logger; | ||
private $useOpenSsl; | ||
private $con; | ||
private $seed; | ||
private $seedTableName; | ||
private $seedUpdated; | ||
private $seedLastUpdatedAt; | ||
private $seedProvider; | ||
|
||
public function __construct(LoggerInterface $logger) | ||
{ | ||
$this->logger = $logger; | ||
|
||
// determine whether to use OpenSSL | ||
if (0 === stripos(PHP_OS, 'win')) { | ||
$this->useOpenSsl = false; | ||
} else if (!function_exists('openssl_random_pseudo_bytes')) { | ||
$this->logger->notice('It is recommended that you enable the "openssl" extension for random number generation.'); | ||
$this->useOpenSsl = false; | ||
} else { | ||
$this->useOpenSsl = true; | ||
} | ||
} | ||
|
||
/** | ||
* Sets the Doctrine seed provider. | ||
* | ||
* @param Connection $con | ||
* @param string $tableName | ||
*/ | ||
public function setConnection(Connection $con, $tableName) | ||
{ | ||
$this->con = $con; | ||
$this->seedTableName = $tableName; | ||
} | ||
|
||
/** | ||
* Sets a custom seed provider implementation. | ||
* | ||
* Be aware that a guessable seed will severely compromise the PRNG | ||
* algorithm that is employed. | ||
* | ||
* @param SeedProviderInterface $provider | ||
*/ | ||
public function setSeedProvider(SeedProviderInterface $provider) | ||
{ | ||
$this->seedProvider = $provider; | ||
} | ||
|
||
/** | ||
* Generates the specified number of secure random bytes. | ||
* | ||
* @param integer $nbBytes | ||
* @return string | ||
*/ | ||
public function nextBytes($nbBytes) | ||
{ | ||
// try OpenSSL | ||
if ($this->useOpenSsl) { | ||
$strong = false; | ||
$bytes = openssl_random_pseudo_bytes($nbBytes, $strong); | ||
|
||
if (false !== $bytes && true === $strong) { | ||
return $bytes; | ||
} | ||
|
||
$this->logger->info('OpenSSL did not produce a secure random number.'); | ||
} | ||
|
||
// initialize seed | ||
if (null === $this->seed) { | ||
if (null !== $this->seedProvider) { | ||
list($this->seed, $this->seedLastUpdatedAt) = $this->seedProvider->loadSeed(); | ||
} else if (null !== $this->con) { | ||
$this->initializeSeedFromDatabase(); | ||
} else { | ||
throw new \RuntimeException('You need to either specify a database connection, or a custom seed provider.'); | ||
} | ||
} | ||
|
||
$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)); | ||
|
||
if (!$this->seedUpdated && $this->seedLastUpdatedAt->getTimestamp() < time() - mt_rand(1, 10)) { | ||
if (null !== $this->seedProvider) { | ||
$this->seedProvider->updateSeed($this->seed); | ||
} else if (null !== $this->con) { | ||
$this->saveSeedToDatabase(); | ||
} | ||
|
||
$this->seedUpdated = true; | ||
} | ||
} | ||
|
||
return substr($bytes, 0, $nbBytes); | ||
} | ||
|
||
private function saveSeedToDatabase() | ||
{ | ||
$this->con->executeQuery("UPDATE {$this->seedTableName} SET seed = :seed, updated_at = :updatedAt", array( | ||
':seed' => $this->seed, | ||
':updatedAt' => new \DateTime(), | ||
), array( | ||
':updatedAt' => Type::DATETIME, | ||
)); | ||
} | ||
|
||
private function initializeSeedFromDatabase() | ||
{ | ||
$stmt = $this->con->executeQuery("SELECT seed, updated_at FROM {$this->seedTableName}"); | ||
|
||
if (false === $this->seed = $stmt->fetchColumn(0)) { | ||
throw new \RuntimeException('You need to initialize the generator by running the console command "init:secure-random".'); | ||
} | ||
|
||
$this->seedLastUpdatedAt = new \DateTime($stmt->fetchColumn(1)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
namespace JMS\SecurityExtraBundle\Security\Util; | ||
|
||
use Doctrine\DBAL\Schema\Schema; | ||
|
||
/** | ||
* The DBAL schema that will be used if you choose the database-based | ||
* seed provider. | ||
* | ||
* @author Johannes M. Schmitt <schmittjoh@gmail.com> | ||
*/ | ||
final class SecureRandomSchema extends Schema | ||
{ | ||
public function __construct($tableName) | ||
{ | ||
parent::__construct(); | ||
|
||
$table = $this->createTable($tableName); | ||
$table->addColumn('seed', 'string', array( | ||
'length' => 88, | ||
'not_null' => true, | ||
)); | ||
$table->addColumn('updated_at', 'datetime', array( | ||
'not_null' => true, | ||
)); | ||
} | ||
|
||
public function addToSchema(Schema $schema) | ||
{ | ||
foreach ($this->getTables() as $table) { | ||
$schema->_addTable($table); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
namespace JMS\SecurityExtraBundle\Security\Util; | ||
|
||
/** | ||
* Seed Provider Interface. | ||
* | ||
* @author Johannes M. Schmitt <schmittjoh@gmail.com> | ||
*/ | ||
interface SeedProviderInterface | ||
{ | ||
/** | ||
* Loads the initial seed. | ||
* | ||
* Whatever is returned from this method, it should not be guessable. | ||
* | ||
* @return array of the format array(string, DateTime) where string is the | ||
* initial seed, and DateTime is the last time it was updated | ||
*/ | ||
function loadSeed(); | ||
|
||
/** | ||
* Updates the seed. | ||
* | ||
* @param string $seed | ||
*/ | ||
function updateSeed($seed); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
namespace JMS\SecurityExtraBundle\Security\Util; | ||
|
||
/** | ||
* String utility functions. | ||
* | ||
* @author Johannes M. Schmitt <schmittjoh@gmail.com> | ||
*/ | ||
final class String | ||
{ | ||
private final function __construct() {} | ||
|
||
/** | ||
* Whether two strings are equal. | ||
* | ||
* This function uses a constant-time algorithm to compare the strings. | ||
* | ||
* @param string $str1 | ||
* @param string $str2 | ||
* @return Boolean | ||
*/ | ||
public static function equals($str1, $str2) | ||
{ | ||
if (strlen($str1) !== $c = strlen($str2)) { | ||
return false; | ||
} | ||
|
||
$result = 0; | ||
for ($i=0; $i<$c; $i++) { | ||
$result |= ord($str1[$i]) ^ ord($str2[$i]); | ||
} | ||
|
||
return 0 === $result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.