Ever since the very first alpha release, PHP 7 has shipped with brand new CSPRNG functions; random_int()
and random_bytes()
. These functions make it easy to generate randomness in cryptographic contexts; a feat that was less than trivial in PHPast.
But on Sept 17th, 2015, PHP internals made a bold move and shipped release candidate 3 of PHP 7 with a breaking change to the CSPRNG behavior.
Before PHP 7 RC3
The primary change came in the form of how the CSPRNG functions respond to a broken source of randomness.
The CSPRNG function's source of random bytes on Linux is /dev/urandom
which is almost always the right answer. On Windows it uses CryptGenRandom
for its source of random bytes. There's more to it than that, but that's the TL;DR.
In some rare cases, these sources can be missing, broken or compromised causing the CSPRNG functions to have to deal with an error state. Before RC3, the functions would raise an E_WARNING
and return false
. This put a lot of burden on the user-land PHP dev to know how to handle these errors gracefully. In fact the only way to "gracefully" handle the error was to use the STFU operator and check the return value.
$random = @random_bytes(32);
if ($random === false) {
// Fall back to another CSPRNG or kill script
}
This issue becomes a real security issue when the values of the CSPRNG functions go unchecked as in this example from Anthony Ferrara:
$password = "";
for ($i = 0; $i < 10; $i++) {
$password .= chr(random_int(33, 124));
}
The example above would generate a string of 10 null bytes if random_int()
failed. Not good for security at all.
The push for change
This less-than-ideal way of dealing with errors was originally brought up by CodesInChaos in the original PR. PeeHaa from room 11 jokingly blamed me in his code comments for the craziness.
I agreed with them.
I submitted a PR to change the behavior so that a catchable exception would be thrown instead. There was lots of back-and-forth about the patch on GitHub, the PHP internals maling list and even an episode of the PHP Roundtable with me and the release managers of PHP 7. The main problem was that PHP was already in the RC release phase and this patch was going to be a breaking change.
Fast forward several weeks Anthony Ferrara submitted an RFC, subjecting the breaking change patch to a diplomatic voting phase. It passed 28 to 2.
After PHP 7 RC3
Now when a sufficient amount of random bytes cannot be gathered, an Exception
is thrown which halts the script execution. This prevents broken CSPRNG's from being used in production environments.
Handling CSPRNG errors in RC3 becomes much more graceful in user-land PHP.
try {
$random = random_bytes(32);
} catch (Exception $e) {
// Fall back to another CSPRNG or kill script
}
The random_int()
function got the same love.
try {
$random = random_int(0,999);
} catch (Exception $e) {
// Fall back to another CSPRNG or kill script
}
One more breaking change for random_int()
One other change that got squeezed in with the RC3 patch is that the min
and max
arguments for random_int()
can now be the same value.
// Raises E_WARNING pre-RC3
// Returns "42" post-RC3
$random = random_int(42,42);
Asking random_int()
for "a number between 42 and 42" might seem like a strange thing to do but this condition can easily happen when using random_int()
within a for
loop for example. In addition, mt_rand()
(not to be used in cryptographic contexts!) also allows the same behavior so this keeps the PHP API's consistent.
Yay for better security in PHP!
PHP is known for its insanely good support for backward compatibility and that is one of the things that make PHP great, but it's also a double-edged sword for myriad rasons. As far as I know, this is one of the few times in PHP history where PHP has broken backwards compatibility in lieu of security.
This whole process of getting the CSPRNG patch through has really shown how the good people of PHP internals are willing to get behind changes that improve security on the web.
I want to personally thank PHP internals for getting behind this security-related breaking change. You all make me smile. :)