PHP: How do you generate a random string?
You need a random string again and again. For a file name, for a cache key or for a token. Depending on what you use it for, though, quite different things matter.
Ask yourself the important question up front: is anyone allowed to guess the string? If it is only a unique file name, the answer does not matter. If it is a password reset link or a session token, the security of your application depends on it.
The simple case
The shortest route to a secure random string goes through random_bytes and bin2hex:
<?php
echo bin2hex(random_bytes(16));
?>
Output:
71649e32bddbae7ad9149822227aa85e
random_bytes() returns the requested number of bytes from the operating
system’s cryptographically secure source. bin2hex() converts those into
readable hexadecimal characters. 16 bytes become 32 characters, because every
byte needs two of them.
The result consists only of 0-9 and a-f. For most purposes that is
perfectly fine and you do not need to read any further.
Your own alphabet
If you want to decide which characters may occur, draw the characters individually with random_int:
<?php
function randomString(int $length, string $alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'): string {
$max = strlen($alphabet) - 1;
$out = '';
for ($i = 0; $i < $length; $i++) {
$out .= $alphabet[random_int(0, $max)];
}
return $out;
}
echo randomString(12); // e.g. 7t3jyvhkma33
?>
random_int() is the secure variant of rand() and returns a number in the
given range — without the skew we are about to come to.
Why not rand() or mt_rand()?
Because their output can be reproduced. Neither function generates real random numbers; they generate a sequence that depends entirely on a starting value (the seed). Set the same seed and you get the same sequence:
1st run with seed 42: 637542, 126067, 286876, 846414, 890026
2nd run with seed 42: 637542, 126067, 286876, 846414, 890026
identical: true
For an attacker that means: whoever knows the seed, or can derive it from observed values, knows all the following values too. For test data that same property is actually useful, because it lets you reproduce a bug. For tokens it is fatal.
The trap: modulo bias
The following line is very common for mapping a random byte onto an alphabet:
<?php
// Careful, this variant is skewed!
$char = $alphabet[ord(random_bytes(1)) % strlen($alphabet)];
?>
The problem has nothing to do with the random source and everything to do with
the remainder of the division. A byte has 256 possible values. With a 10
character alphabet, 256 / 10 = 25 remainder 6. So the first six characters of
the alphabet get one chance more than the remaining four.
That is not grey theory, it can be measured. 600,000 draws, alphabet
0123456789:
0: 10.229 %
1: 10.119 %
2: 10.167 %
3: 10.159 %
4: 10.096 %
5: 10.126 %
6: 9.810 %
7: 9.778 %
8: 9.755 %
9: 9.760 %
The step sits exactly between 5 and 6, precisely where the arithmetic
predicts it. For comparison, the same number of draws with random_int(0, 9):
0: 10.085 %
1: 9.997 %
...
8: 10.008 %
9: 9.986 %
There is no such block here, the deviations scatter randomly. 9.76 % instead
of 10 % sounds like very little. With a token it means an attacker can order
their guesses sensibly. random_int() takes that worry off your hands, because
internally it re-draws until the value falls into the desired range without
skew.
uniqid() is not random
A common misunderstanding. uniqid() does not generate random values, it
formats the current time. Three calls in a row:
6a91e83188307
6a91e8318830b
6a91e8318830c
The values differ only in their last characters and count upwards. As an identifier within one request that is usable, as a token it is not.
It looks similar with str_shuffle. The function shuffles the characters you pass in, so every character occurs exactly once afterwards. For a random string drawn from an alphabet it is therefore the wrong choice, quite apart from the question of the random source.
Summary
- For tokens, links and keys:
random_bytes()orrandom_int(). - For everything else
bin2hex(random_bytes(16))does just as well. rand()andmt_rand()are reproducible and unsuitable for security purposes.- Never map bytes onto an alphabet with
%, it skews the distribution. uniqid()is a clock andstr_shuffle()is a permutation. Neither is a random generator.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).