Testing Randomness of PHP Random Number Functions

Published On31 Aug 2022

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:

Visualizing `random_int` randomness
Visualization of the randomness of `random_int` function, showing equally distributed values returned from the RNG

New in PHP 8.2 In PHP 8.2, it is also possible to use random_int and random_bytes PRNGs with scoped and OOP API using Random\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);
};

Visualizing `mt_rand` randomness
Visualization of the randomness of `mt_rand` function, showing equally distributed values returned from the RNG

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.
mt_rand run with seed = 42, round 1 mt_rand run with seed = 42, round 2 mt_rand run with seed = 43, round 1

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);
};

Visualizing `rand` randomness
Visualizing `rand` function, with visible predictable patterns

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));
};

Visualizing `lcg_value` randomness
Visualizing `lcg_value` function, with seemingly random noise and no visible patterns

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 PcgOneseq128XslRr64 run with seed = 42, round 1 PcgOneseq128XslRr64 run with seed = 42, round 2 PcgOneseq128XslRr64 run with seed = 43
Xoshiro256StarStar PcgOneseq128XslRr64 run with seed = 42, round 1 PcgOneseq128XslRr64 run with seed = 42, round 2 PcgOneseq128XslRr64 run with seed = 43

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 and random_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.

Recent Articles on PHP.Watch

All ArticlesFeed 
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
AEGIS Encryption with PHP Sodium Extension

AEGIS Encryption with PHP Sodium Extension

The Sodium extension in PHP 8.4 now supports AEGIS-128L and AEGIS256 Authenticated Encryption ciphers. They are significantly faster than AES-GCM and CHACHA20-POLY1305. This article benchmarks them and explains how to securely encrypt and decrypt data using AEGIS-128L and AEGIS256 on PHP.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

Support PHP.Watch — If you find the articles, version information, Codex, and other PHP.Watch contributions useful, consider supporting through GitHub Sponsors. Your sponsorship helps dedicate more time to creating valuable content and improving the PHP community. Together, we can keep the momentum going — thank you for your support!

Thanks to the highest tier sponsor: @TomasVotruba for your generous support to keep PHP.Watch moving 💜