skip to content

In PHP, which functions generate a secure token, and why are rand() and mt_rand() the wrong choice for one?

level: middleimportance: must knowfreq 48%

answer

  1. random_bytes returns raw binary
  2. bin2hex to make a printable token
  3. random_int(min, max) is inclusive
  4. rand/mt_rand are predictable, not CSPRNG
  5. throws Random\RandomException on failure

basics

~20 s

Use random_bytes($len) for a token (then bin2hex/base64 to make it printable) and random_int($min, $max) for a secure number; both draw from the OS CSPRNG and throw Random\RandomException on failure. rand() and mt_rand() are fast but predictable, so an attacker can reproduce their output.

solid answer

~50 s

For anything an attacker must not guess — a session ID, a password-reset token, an API key, a CSRF token — use PHP's **cryptographically secure** generators: **`random_bytes(int $length): string`** returns `$length` **raw binary** bytes from the OS CSPRNG, and **`random_int(int $min, int $max): int`** returns a uniform integer in the inclusive range. Because `random_bytes` output is binary, wrap it for storage or URLs: `bin2hex(random_bytes(32))` gives a 64-character hex token, or `sodium_bin2base64(...)` for base64. Both functions **throw `Random\RandomException`** (a subclass of `Exception`; before PHP 8.2 a plain `Exception`) if the system source fails, so they never silently return weak output. By contrast **`rand()` and `mt_rand()` are fast pseudo-random generators, not CSPRNGs** — their sequence is determined by an internal seed, so observing a few outputs lets an attacker predict the rest. They are fine for shuffling a deck or picking a sample, never for secrets. The `Random\Randomizer` class (PHP 8.2) with the default `Random\Engine\Secure` engine is the object-oriented CSPRNG API for the same purpose.

go deeper

for a junior

Use random_bytes/random_int for secrets and know rand/mt_rand are not secure.

for a middle

Explain that random_bytes returns binary needing bin2hex/base64, random_int is inclusive, and why PRNG output is predictable.

for a senior

Choose entropy sizes, use the Randomizer Secure engine for unbiased shuffles, and let RandomException propagate rather than falling back.

for a principal

Set standards for token entropy and generator choice across services and reason about where reproducible engines are acceptable.

## The two secure primitives | Function | Returns | Use for | |---|---|---| | `random_bytes(int $length)` | `$length` **raw binary** bytes | tokens, keys, salts, nonces | | `random_int(int $min, int $max)` | a uniform int in `[$min, $max]` (both inclusive) | secure numeric codes, unbiased index selection | Both read from the operating system's cryptographically secure source (`getrandom`/`/dev/urandom` on Linux, the equivalent on other platforms). "Cryptographically secure" means that seeing past outputs gives no practical advantage in predicting future ones — the property you are buying for a secret. The *definition* of a CSPRNG is a security concept owned elsewhere; here the point is which PHP calls have it. ## Making a usable token `random_bytes` returns binary, which is not safe to put in a URL, cookie, or database text column as-is. Encode it: ```php $token = bin2hex(random_bytes(32)); // 32 bytes -> 64 hex chars, 256 bits of entropy ``` - `bin2hex()` doubles the length and yields `[0-9a-f]`. - `sodium_bin2base64($bytes, SODIUM_BASE64_VARIANT_URLSAFE)` is more compact and URL-safe. - Choose the **byte count** for the entropy you need: 16 bytes (128 bits) is a strong minimum for tokens, 32 bytes (256 bits) is common. For a numeric one-time code, `random_int(0, 999999)` gives an unbiased 6-digit value; do **not** build it as `rand() % 1000000`, which is both predictable and modulo-biased. ## Why rand()/mt_rand() are disqualified `rand()` and `mt_rand()` are **pseudo-random**: an internal state is seeded once, and each call advances it deterministically. Mersenne Twister (`mt_rand`) is a well-known algorithm; from a handful of outputs an attacker can recover the internal state and reproduce every subsequent value. That is fine when you only need statistical spread (sampling, jitter, a game) but fatal for a secret, because "unpredictable to an attacker" is the whole requirement. - `mt_srand()`/`srand()` only make it *worse* to reach for — seeding a PRNG with something guessable (time, PID) makes the output trivially reproducible. There is no seed you can give `mt_rand` that makes it a CSPRNG. - Since PHP 7, `random_int` and `random_bytes` exist precisely so you never need `mt_rand` for security. ## The object API: Randomizer PHP 8.2 added `Random\Randomizer`. Constructed with no argument it uses `Random\Engine\Secure` — the CSPRNG — so: ```php $r = new Random\Randomizer(); // default engine is Secure (CSPRNG) $n = $r->getInt(1, 100); $b = $r->getBytes(32); $shuffled = $r->shuffleArray($items); ``` You can also pass a *reproducible* engine (`Mt19937`, `PcgOneseq128XslRr64`, `Xoshiro256StarStar`) for tests or simulations — but those are **not** CryptoSafe, so use `Secure` (the default) for anything security-relevant. `Randomizer` is the modern way to get unbiased `shuffleArray`/`pickArrayKeys` on a secure engine, which raw `random_int` loops make awkward. ## Failure handling `random_bytes` and `random_int` **throw** on a source failure rather than degrading. Since PHP 8.2 the exception is `Random\RandomException` (previously a plain `Exception`). You normally let it propagate — a system with no secure randomness cannot safely issue tokens — rather than catching it and falling back to `mt_rand`, which would defeat the point.

  • How do you turn `random_bytes` into a URL-safe token?
    `random_bytes` returns raw binary, so encode it: `bin2hex(random_bytes(32))` for a 64-char hex string, or `sodium_bin2base64($bytes, SODIUM_BASE64_VARIANT_URLSAFE)` for a shorter URL-safe form. Pick the byte count for the entropy you need — 16 bytes (128 bits) minimum, 32 bytes (256 bits) common.
  • Is `random_int(0, 9) . random_int(0, 9) ...` or `mt_rand() % 10` better for a numeric code?
    Use `random_int`. It is a CSPRNG and returns an unbiased value across the inclusive range. `mt_rand()` is predictable, and `% 10` also introduces modulo bias. For a 6-digit code, `random_int(0, 999999)` with zero-padding is correct.
  • When is `mt_rand()` acceptable?
    When you need statistical randomness with no adversary — shuffling for a game, sampling data, adding jitter to a retry. It is fast and fine there. The moment the value must be unguessable by an attacker (tokens, keys, IDs), switch to `random_bytes`/`random_int` or a `Randomizer` on the `Secure` engine.

saying these in an interview costs you the question

  • mt_rand is fine for tokens if you seed it well
  • random_bytes returns a printable hex string directly
  • random_int's upper bound is exclusive
  • rand() is cryptographically secure on modern PHP
  • catch the exception and fall back to mt_rand
  • a good statistical distribution makes a generator secure