PHP random string generator
Generate one here, or copy the PHP below. Every example uses a cryptographically secure source and was run on PHP 8.5.
More options
The short answer
On PHP 8.3 or later, use Random\Randomizer::getBytesFromString($alphabet, $length). A Randomizer created without an engine argument uses Random\Engine\Secure, which reads the operating system CSPRNG, and getBytesFromString picks each character uniformly. A chi-square test over 620,000 characters from it on PHP 8.5 came out at 61 on 61 degrees of freedom, exactly what uniform output looks like.
On PHP 7.0 to 8.2, call random_int(0, strlen($alphabet) - 1) once per character. random_int is cryptographically secure and unbiased, and it throws an exception rather than quietly falling back to something weaker.
For a token with no particular alphabet, bin2hex(random_bytes(16)) gives 32 hex characters carrying 128 bits.
| Function | Source | Safe for secrets |
|---|---|---|
| random_int, random_bytes | Operating system CSPRNG | Yes |
| Random\Randomizer with no engine | Random\Engine\Secure | Yes |
| rand | mt_rand under another name, since PHP 7.1 | No |
| mt_rand | Mersenne Twister | No |
| str_shuffle, shuffle, array_rand | The same Mersenne Twister state as mt_rand | No |
| uniqid | The current time in microseconds | No |
getBytesFromString works on bytes. Keep the alphabet ASCII, because an alphabet containing a multibyte character such as é produces broken UTF-8.
Why str_shuffle is the wrong answer
The most copied PHP answer is substr(str_shuffle($chars), 0, $length). It has two independent problems, and either one would be enough to rule it out.
str_shuffle draws from the same Mersenne Twister state as mt_rand. On PHP 8.5, calling mt_srand(42) before str_shuffle produced the identical shuffle every time, and shuffle and array_rand behaved the same way. Mersenne Twister is not a cryptographic generator: its state can be reconstructed from its output, and tools such as php_mt_seed search the 32-bit seed space for the seed behind observed values.
str_shuffle also never repeats a character, because it only rearranges the characters it was given. A 24-character string cut from a shuffled 62-character alphabet carries 135.5 bits instead of the 142.9 it would carry with repetition allowed, and it can never be longer than the alphabet. Repeating the alphabet with str_repeat before shuffling removes the length limit and leaves the predictability.
Mistakes that pass code review
Each of these appears in real projects, because each one produces output that looks random.
- md5(uniqid()) or sha1(uniqid(mt_rand(), true)) as a token. uniqid is the current time written as 13 hexadecimal characters, and two calls made moments apart on PHP 8.5 shared their first 12. Hashing a guessable input produces a guessable output.
- rand(0, strlen($chars) - 1) in a loop. Since PHP 7.1, rand is an alias of mt_rand; seeding both with the same value produced the same number on PHP 8.5.
- base64_encode(random_bytes(32)) in a URL. Plus, slash and equals all need escaping. Use rtrim(strtr(base64_encode($bytes), "+/", "-_"), "=") for 43 URL-safe characters.
- new Random\Randomizer(new Random\Engine\Mt19937($seed)) copied from an example about reproducible tests. The engine argument decides the source, and Secure is the only engine meant for secrets.
- openssl_random_pseudo_bytes chosen because it sounds more serious. It is secure on current PHP, but random_bytes does the same job, exists without the OpenSSL extension, and throws on failure.
Laravel and WordPress
Laravel Str::random($length) fills its output from random_bytes, base64-encodes the bytes and removes plus, slash and equals, drawing more bytes until the string is long enough. That was checked in the Laravel 12.x source. It is secure, and the output is uniform over letters and digits. Str::password() picks characters with random_int and shuffles them with Arr::shuffle, which uses Random\Randomizer with its default Secure engine.
WordPress wp_generate_password() picks each character with wp_rand(), which calls random_int. Only if random_int throws does wp_rand fall back to an older generator built from md5, uniqid and mt_rand. On any supported PHP version random_int has a secure source, so that fallback should never run.
Doing this in your own code
PHP 8.3+ - Random\Randomizer
<?php
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
// No engine argument means Random\Engine\Secure, the OS CSPRNG.
$randomizer = new Random\Randomizer();
for ($i = 0; $i < 3; $i++) {
echo $randomizer->getBytesFromString($alphabet, 24), PHP_EOL;
} PHP 7.0+ - random_int
<?php
function randomString(int $length, string $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'): string
{
$max = strlen($alphabet) - 1;
$out = '';
for ($i = 0; $i < $length; $i++) {
// random_int is unbiased and throws if no secure source exists.
$out .= $alphabet[random_int(0, $max)];
}
return $out;
}
for ($i = 0; $i < 3; $i++) {
echo randomString(24), PHP_EOL;
} PHP 7.0+ - hex and URL-safe tokens
<?php
$bytes = random_bytes(32);
echo bin2hex($bytes), PHP_EOL; // 64 hex characters
echo rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='), PHP_EOL; // 43 URL-safe characters PHP 7.0+ - password with every character type
<?php
function password(int $length = 20): string
{
$classes = ['ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz', '0123456789', '!#$%*+-=?@^_'];
if ($length < count($classes)) {
throw new InvalidArgumentException('Length is shorter than the number of required classes.');
}
$alphabet = implode('', $classes);
$max = strlen($alphabet) - 1;
// Whole-string rejection keeps the result uniform over valid passwords.
do {
$candidate = '';
for ($i = 0; $i < $length; $i++) {
$candidate .= $alphabet[random_int(0, $max)];
}
$valid = true;
foreach ($classes as $class) {
if (strpbrk($candidate, $class) === false) {
$valid = false;
}
}
} while (!$valid);
return $candidate;
}
for ($i = 0; $i < 5; $i++) {
echo password(20), PHP_EOL;
} Questions
How do I generate a random string in PHP?
On PHP 8.3 and later: (new Random\Randomizer())->getBytesFromString($alphabet, 24). On PHP 7.0 to 8.2: loop 24 times and append $alphabet[random_int(0, strlen($alphabet) - 1)]. Both are secure and unbiased.
Is str_shuffle secure for generating passwords?
No. It uses the Mersenne Twister state shared with mt_rand, so seeding with mt_srand makes it repeat exactly, and it never repeats a character, which lowers the entropy and caps the length at the size of the alphabet.
What is the difference between rand, mt_rand and random_int?
Since PHP 7.1, rand is an alias of mt_rand, and both are Mersenne Twister, built for speed and repeatability rather than secrecy. random_int reads the operating system cryptographic generator and returns an unbiased integer in the range you give it. Only random_int is suitable for passwords, tokens and keys.
How do I generate a random token in PHP?
bin2hex(random_bytes(16)) gives 32 hex characters and 128 bits. For a shorter URL-safe token, rtrim(strtr(base64_encode(random_bytes(32)), "+/", "-_"), "=") gives 43 characters and 256 bits.
Is uniqid random?
No. uniqid is derived from the current time in microseconds and returns 13 hexadecimal characters, most of which are the same for calls made close together. With more_entropy set it appends a value from a non-cryptographic generator. It is a timestamp, not a secret.