Testing Randomness of PHP Random Number Functions
Random number generation is a process that yields numbers that cannot be reasonably predicted. The sequence of numbers should not be predictable, and it plays a significant role in applications that rely on the unpredictability of the random number sequence.
"True" Random Number generation processes can include anything from a simple coin flip, a dice roll, to cosmic radiation measurements, atmospheric pressure, lava lamps, and other physical means that depend on several naturally occurring physical aspects, which makes it quite difficult to predict on a computer.
Most modern computer operating systems attempt to provide a close "true" random number generator making use of measurements such as spinning hard disk latency to simulate a close alternative to "true" random number generators. Their implementation details make them more secure as well. For example, operating systems regularly reseed the the Random Number Generator to prevent potentially leaked internal state affecting the Random Number Generation in events of hibernation, or other events that memory could have been accessed bypassing kernel memory protection.
In real-life computer applications, operating systems and programming language runtime provide Random Number Generators (RNGs) that use various algorithms to generate random numbers, and make use of available physical randomness to increase the randomness of the generator. One of the fundamental drawbacks of these Pseudo Random Number Generators is that they depend on an initial "seed" value, and knowing the initial seed value and the algorithm is enough to predict the entire stream of random numbers, thereby making the use of a proper random number generator vital for any application.
Random Number Generators in PHP
PHP provides several ways to generate random values: rand
, mt_rand
, random_int
, random_bytes
, openssl_random_pseudo_bytes
, etc., Some of them are Pseudorandom number generators, but they carry various properties that make them different from the other, and must be favored over the other.
Random number generators are used everywhere from simple computer games to PIN numbers to sensitive information encrypted with a randomly generated encryption key. Using appropriate random number generators is paramount in most of the applications to ensure that a malicious actor cannot determine the random number generated within the system.
All Pseudorandom generators inherently depend on a "seed" value that determines all the entire sequence of random numbers generated using the given algorithm. Computer games such as Minecraft build the entirety of the game world based on a single seed value. In order to build an identical game world, all it takes is just the initial seed.
Random Number Generators available in PHP vary a lot on how they are implemented, and how secure they are to be used in applications that rely on the randomness of the generator to be secure.
As of PHP 8.2, PHP provides following random number generators:
Function/Class | Availability | Notes |
---|---|---|
rand |
PHP 4 and later | Insecure, unsuitable for any security-related applications. Aliased to mt_rand since PHP 7.1 |
mt_rand |
PHP 4 and later | Uses Mersenne Twister. Received some bug fixes and improvements in PHP 7.1, but still inherently insecure. |
lcg_value |
PHP 4 and later | Returns a random float value in the inclusive range of 0 and 1. Not cryptographically secure. |
random_int |
PHP 7.0 and later | Recommended, as it is cryptographically secure |
random_bytes |
PHP 7.0 and later | Recommended, as it is cryptographically secure. If the system cannot generate cryptographically secure random numbers, it fails hard without falling back to insecure algorithms. |
openssl_random_pseudo_bytes |
OpenSSL and PHP 5.3 and later | Can be used to fetch cryptographically secure random numbers, but it is not guaranteed as the function may fallback to insecure algorithms. |
Random\Engine\Mt19937 |
PHP 8.2 and later | An OOP interface for mt_rand . |
Random\Engine\PcgOneseq128XslRr64 |
PHP 8.2 and later | Permuted congruential generator implementation. |
Random\Engine\Xoshiro256StarStar |
PHP 8.2 and later | Xoshiro PRNG implementation. |
Measuring Randomness in PHP PRNGs
One of the easiest ways to measure the randomness of Random Number Generators is to visualize the yielded values to observe patterns.
Using PHP's GD extension, it is possible to plot an image by placing a single pixel on random X and Y coordinates. On a predetermined number of attempts, an image with mostly even pixel distribution indicates a random number generator that is close to a "true" RNG. RNGs that produce values purely based on a seed value (such as Mersenne Twister) should produce identical outputs if the seed remains the same.
// Name the testing function, which is used as the output image file name.
$test_name = 'random_int';
$size = 250;
$random_function = static function(int $max) {
return random_int(0, $max);
};
// Create an empty square image with a given $size.
$im = imagecreatetruecolor($size, $size);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
// Fill the image in white
imagefill($im, 0, 0, $white);
$iterations = $size ** 2;
for ($i = 0; $i < $iterations; $i++) {
$x = $random_function($size);
$y = $random_function($size);
imagesetpixel($im, $x, $y, $black);
}
imagepng($im, $test_name . '.png', 0);
imagedestroy($im);
random_int
/ random_bytes
random_int
function is a CSPRNG (Cryptographically Secure Pseudo Random Number Generator) available since PHP 7.0. It throws an Exception if it could not yield a senescence of numbers with sufficient randomness, and does not fallback to any insecure RNG source.
Further, it is not possible to alter the seed values for this RNG, and is the recommended way to obtain random numbers and bytes in PHP, even for cryptographic operations.
$random_function = static function(int $max) {
return random_int(0, $max);
};
Using the snippet above to visualize the randomness, random_int
function produces an image similar to this:
New in PHP 8.2 In PHP 8.2, it is also possible to use
random_int
andrandom_bytes
PRNGs with scoped and OOP API usingRandom\Engine\Secure
engine. There are more detailed examples available in PHP 8.2: New Random Extension - Usage examples
mt_rand
Function and Random\Engine\Mt19937
Engine
Mersenne Twister is a Pseudo Random Number Generator available in PHP. Its seed value is configurable, but PHP seeds it with a sufficiently random seed at start.
$random_function = static function(int $max) {
return mt_rand(0, $max);
};
One major difference between Mersenne Twister RNG and a hybrid/hardware approach is that the entire sequence of the random numbers depends on the seed. PHP's mt_srand
is used to seed the RNG. Mersenne Twister yields an identical random number sequence for the same seed.
This deterministic nature is useful when an application/game needs to generate values based on a single seed (such as Minecraft). However, Mersenne Twister a bad choice for applications that need to generate random numbers for cryptographic purposes because the internal state of the RNG can be derived by observing the sequences of random numbers the RNG yields.
To visualize this, the three following are generated using mt_rand()
function. Two of them use $seed = 42
, and the last one uses $seed = 43
:
mt_srand($seed);
$random_function = static function(int $max) {
return mt_rand(0, $max);
};
To better visualize the similarities and differences, the following images were generated using imagefilledrectangle()
function to draw filled rectangles instead of individual pixels. The color of the rectangle was also picked randomly.
$seed = 42 |
$seed = 42 |
$seed = 43 |
---|---|---|
As long as the seed value is the same, the generated random number series remain the same. | As long as the seed value is the same, the generated random number series remain the same. | Changing the seed value even by one byte yields completely different series of random numbers. |
rand
Function
Since PHP 7.1, PHP's rand
function uses MT under the hood, the results are identical to the mt_rand
function. However, prior to PHP 7.0, the rand
function yielded a more predictable and repetitive stream of random numbers that made it quite far from a true random number generator.
$random_function = static function(int $max) {
return rand(0, $max);
};
Using rand
in PHP 7.1 and later is necessarily bad because it uses mt_rand
anyway. However, to make sure statically analyzers do not flag the use of rand
, consider moving to random_int
.
lcg_value
Function
lcg_value()
is a Combined linear congruential generator that returns a pseudo random number between 0 and 1. This function is also not considered a cryptographically secure pseudo random number generator.
$random_function = static function(int $max) {
return abs((int) (lcg_value() * $max));
};
Random\Engine\PcgOneseq128XslRr64
and Random\Engine\Xoshiro256StarStar
In PHP 8.2, the majority of the RNG functions are moved to a new extension called random
. It brings a new \Random\Randomizer
class, along with four RNG engines as PHP classes.
Two new PRNGs added with the new random
extension are PcgOneseq128XslRr64
, Permuted congruential generator implementation, and Xoshiro256StarStar
, a Xoshiro PRNG implementation. Both of them are similar to Mersenne Twister, in that they all only rely on the initial seed value, and can be used as a deterministic stream of numbers as long as the seed value remains the same.
PRNG | $seed = 42 |
$seed = 42 |
$seed = 43 |
---|---|---|---|
Visualizing | As long as the seed value is the same, the generated random number series remain the same. | As long as the seed value is the same, the generated random number series remain the same. | Changing the seed value even by one byte yields completely different series of random numbers. |
PcgOneseq128XslRr64 |
|||
Xoshiro256StarStar |
PHP offers several Random Number Generators, and the choice of the correct Random Number Generator is important, especially when it comes to applications on which the unpredictableness of the random number stream is important. Some of the RNGs, such as rand()
prior to PHP 7.1 are inherently insecure and predictable.
PHP 8.2 offers Xoshiro256StarStar
and PcgOneseq128XslRr64
as two additional RNGs in addition to existing Mersenne Twister (with an OOP API with Mt19937
).
Not all RNGs are suitable for Cryptographically Secure Pseudo Random Number Generators (CSPRNG). openssl_random_pseudo_bytes
function from the OpenSSL extension was a better choice prior to PHP 7.0, but even that was not ideal because it used to return false
if it was not possible to random numbers with sufficient entropy. This was later fixed in PHP 7.4 to throw an Exception on RNG failures.
RNGs such as Mersenne Twister, Xoshiro256StarStar
, and PcgOneseq128XslRr64
have use cases, but because they rely on a single seed value, using them for any application that require a CSPRNG is not recommended.
For all Random Number generation purposes, using
random_int
andrandom_bytes
functions is recommended because it fails with Exceptions (instead of silently defaulting insecure algorithms), and is supported in all PHP versions since PHP 7.0.
Applications that do not use the RNG for cryptographic use can use more faster implementations such as Xoshiro256StarStar
.