Accident in PHP7 - Am I Lucky?

Original author: Nicola Pietroluongo
  • Transfer
Cryptographic randomization in PHP

In this article, we will analyze the problems related to the generation of random numbers used in cryptography. PHP5 does not provide a simple mechanism for generating cryptographic random numbers, while PHP7 solves this problem by introducing CSPRNG functions.

What is CSPRNG?


Citing Wikipedia , a cryptographically secure pseudorandom number generator (CSPRNG) is a pseudorandom number generator with certain properties that allow it to be used in cryptography.

CSPRNG is mainly used for the following purposes:
  • Key generation (including public / private key generation)
  • Create random passwords for user accounts
  • Encryption systems

The main aspect of maintaining a high level of security is the high quality of randomness.

CSPRNG in PHP7


PHP7 introduces two new features that can be used for CSPRNG: random_bytesand random_int.

The function random_bytesreturns a string and takes as input parameters intthe length (in bytes) of the returned value:

$bytes = random_bytes(10);
var_dump(bin2hex($bytes));
//possible ouput: string(20) "7dfab0af960d359388e6"  

random_int returns an integer in the given range:

var_dump(random_int(1, 100));
//possible output: 27

Behind the scenes


The sources of randomness of the above functions differ depending on the environment:
  • Windows will always be used CryptGenRandom();
  • On other platforms - subject to availability, it will be enabled arc4random_buf()(true in the case of BSD-derived systems or systems c libbsd).
  • If the above is not available, Linux will use the system one getrandom(2).
  • If all of this fails, PHP will try to use it as a final attempt /dev/urandom.
  • If it is not possible to use these sources, an error will be thrown.

Simple test


A good random number generation system is determined by the “quality” of the generations. To test it, a set of statistical tests is often used, allowing, without delving into the complicated topic of statistics, to compare the known reference behavior with the result of the generator and help in assessing its quality.

One of the easiest tests is dice. The expected probability of a six falling out with one bone is one to six, while if I throw three dice 100 times, the expected loss of 1, 2 and 3 sixes is approximately the following:
  • 0 sixes = 57.9 times
  • 1 six = 34.7 times
  • 2 sixes = 6.9 times
  • 3 sixes = 0.5 times

Here is the code for playing a roll of dice 1,000,000 times:

$times = 1000000;
$result = [];
for ($i=0; $i < $times; $i++) {
    $dieRoll = array(6 => 0); //initializes just the six counting to zero
    $dieRoll[roll()] += 1; //first die
    $dieRoll[roll()] += 1; //second die
    $dieRoll[roll()] += 1; //third die
    $result[$dieRoll[6]] += 1; //counts the sixes
}
function roll() {
    return random_int(1,6);
}
var_dump($result);

Running code in PHP7 using random_intand simple randwill produce the following results:
SixesExpected Resultrandom_intrand
0579000579430578179
1347000346927347620
2690006898569586
3500046584615

For a better comparison rand, random_intwe plot the results using the formula: результат PHP- ожидаемый результат/ sqrt(ожидаемый результат).

The graph will look like this (the closer to zero, the better):
test random graph

Even despite the poor result with three sixes and the simplicity of the test, we see a clear superiority random_intover rand.

What about PHP5?


By default, PHP5 does not provide any strong pseudo random number generators. But in fact there are several options, such as openssl_random_pseudo_bytes(), mcrypt_create_iv()or direct use /dev/randomor /dev/urandomc fread(). There are also libraries like RandomLib or libsodium .

If you want to start using a good random number generator and at the same time are not yet ready to switch to PHP7, you can use the library random_compatfrom Paragon Initiative Enterprises. It allows you to use random_bytes(), and random_int()in PHP 5.x projects.

The library can be installed through Composer :

composer require paragonie/random_compat

require 'vendor/autoload.php';
$string = random_bytes(32);
var_dump(bin2hex($string));
// string(64) "8757a27ce421b3b9363b7825104f8bc8cf27c4c3036573e5f0d4a91ad2aaec6f"
$int = random_int(0,255);
var_dump($int);
// int(81)

Compared to PHP7, it random_compatuses slightly different priorities:
  1. fread()/dev/urandom if available
  2. mcrypt_create_iv($bytes, MCRYPT_CREATE_IV)
  3. COM('CAPICOM.Utilities.1')->GetRandom()
  4. openssl_random_pseudo_bytes()


You can read more information about why this order is used in the documentation .

An example of generating a password using a library:

$passwordChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$passwordLength = 8;
$max = strlen($passwordChar) - 1;
$password = '';
for ($i = 0; $i < $passwordLength; ++$i) {
    $password .= $passwordChar[random_int(0, $max)];
}
echo $password;
//possible output: 7rgG8GHu

Brief summary


You should always use cryptographically robust pseudo random number generators, and random_compatis a good solution for this.

If you need a reliable source of random data, then look towards random_intand random_bytes.

Related Links



Also popular now: