In PHP, which functions generate a secure token, and why are rand() and mt_rand() the wrong choice for one?
answer
- random_bytes returns raw binary
- bin2hex to make a printable token
- random_int(min, max) is inclusive
- rand/mt_rand are predictable, not CSPRNG
- throws Random\RandomException on failure
basics
~20 sUse 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 sFor 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
Use random_bytes/random_int for secrets and know rand/mt_rand are not secure.
Explain that random_bytes returns binary needing bin2hex/base64, random_int is inclusive, and why PRNG output is predictable.
Choose entropy sizes, use the Randomizer Secure engine for unbiased shuffles, and let RandomException propagate rather than falling back.
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