random_bytes

Generates a string of cryptographically secure random bytes of the requested length.

Signature
random_bytes(int $length): string
  • Added in PHP 7.0

Parameters

ParameterTypeDescription
$lengthintThe number of random bytes to return. Must be 1 or greater. This is a byte count, not a character count for any encoded form.

Return value

stringA string containing the requested number of cryptographically secure random bytes.

Errors

Throws Random\RandomException if no suitable source of randomness is available, and ValueError if length is less than 1.

Changelog

  • PHP 8.2.0A CSPRNG failure now throws Random\RandomException. Previously a plain Exception was thrown, so existing catch (Exception $e) blocks continue to work because Random\RandomException extends Exception.

random_bytes() returns raw binary drawn from the operating system's cryptographic random source. It is the correct starting point for every secret your application generates: session identifiers, CSRF tokens, password reset links, API keys, encryption keys and initialisation vectors.

When to reach for it

Any time you need an unguessable string. 16 bytes (128 bits) is plenty for a CSRF token; 32 bytes (256 bits) is the usual choice for a long-lived API key or password reset token. Do not build a token by concatenating uniqid(), time() or mt_rand() — all three are predictable, and hashing predictable input does not make it unpredictable.

What trips people up

The return value is binary. It contains null bytes, control characters and invalid UTF-8, so it will break JSON encoding, HTML output, and any database column that is not BINARY/BLOB. Encode it first with bin2hex() or base64_encode().

Remember what that encoding does to length: bin2hex(random_bytes(16)) is 32 characters, not 16. People asking for "a 32-character token" usually want random_bytes(16).

When comparing a submitted token against a stored one, use hash_equals() rather than === to avoid leaking information through timing.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Generate a CSRF token to embed in a form and store in the session
  • Create a single-use password reset token, storing only its SHA-256 hash
  • Produce an API key for a new integration account
  • Build a random salt or initialisation vector for an encryption routine

Frequently asked questions

How many bytes should a secure token be?

16 bytes (128 bits) is enough for CSRF tokens and short-lived links; 32 bytes (256 bits) is the common choice for API keys and password reset tokens. Going beyond 32 bytes adds length without adding meaningful security.

Why does random_bytes output look like garbage?

Because it returns raw binary, not printable text — every byte value from 0 to 255 is possible. Pass it through bin2hex() or base64_encode() before storing or displaying it.

What is the difference between random_bytes and openssl_random_pseudo_bytes?

random_bytes() is built into PHP core, always cryptographically secure, and throws on failure, while openssl_random_pseudo_bytes() requires the OpenSSL extension and reports weakness through a by-reference flag people routinely ignore. Use random_bytes().

More examples

Password reset token stored as a hash

32 raw bytes become a 64-character hex token. Only its SHA-256 hash is stored, so a leaked database does not hand out working reset links, and hash_equals() compares in constant time — the output shows true for the real token and false for a guess.

Example · php
<?php
$token  = bin2hex(random_bytes(32));   // goes in the emailed link
$stored = hash('sha256', $token);      // goes in the database

echo 'token length: ', strlen($token), "\n";
echo 'stored hash:  ', substr($stored, 0, 24), "...\n";

$submitted = $token;
echo 'match: ', var_export(hash_equals($stored, hash('sha256', $submitted)), true), "\n";
echo 'wrong: ', var_export(hash_equals($stored, hash('sha256', 'guess')), true), "\n";

Building a UUID v4

A UUID v4 is just 16 random bytes with six bits overwritten to mark the version and variant. The printed version nibble is always 4, which is how a reader can tell a random UUID from a time-based one.

Example · php
<?php
$b = random_bytes(16);
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40); // version 4
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80); // RFC variant

$hex  = bin2hex($b);
$uuid = sprintf('%s-%s-%s-%s-%s', substr($hex, 0, 8), substr($hex, 8, 4), substr($hex, 12, 4), substr($hex, 16, 4), substr($hex, 20, 12));

echo "uuid: $uuid\n";
echo 'version nibble: ', $uuid[14], "\n";

Discussion

  • Be the first to comment on this lesson.