Safeguard
Application Security•

Which PHP functions are safe for random numbers and crypto?

Use random_bytes and random_int for secrets, password_hash for passwords, sodium for encryption and hash_equals for comparisons. Avoid rand, mt_rand and uniqid.

Safeguard Security Team
4 min read

For secrets in PHP, use random_bytes and random_int, which draw from the operating system's cryptographic generator. Use password_hash for passwords, a vetted library such as the sodium extension for encryption, and hash_equals to compare secrets. Do not use rand, mt_rand or uniqid for anything an attacker must not guess.

What makes random_bytes safe?

The PHP manual says random_bytes returns cryptographically secure bytes suitable for all applications, including long-term secrets such as encryption keys. On Linux it uses getrandom() and falls back to /dev/urandom. On Windows it uses the CNG API. If no source is available it throws an exception rather than returning weak bytes, so a failure is loud.

The output is raw binary, which may contain unprintable characters or invalid UTF-8. Encode it with bin2hex or base64_encode before putting it in a URL, cookie or database text column. Use random_int when you need an unbiased integer in a range, for example picking a one-time code digit.

Which functions should you not use?

The manual's own warnings are direct. For mt_rand: it "does not generate cryptographically secure values, and must not be used for cryptographic purposes, or purposes that require returned values to be unguessable." For uniqid, which returns an identifier built from the current time with microsecond precision, the manual carries the same caution. rand belongs in the same group.

A constructed example: a password reset link is built from md5(uniqid()). The input is a timestamp. An attacker who requests a reset for their own account, then for the victim's, knows roughly what time window to search. Hashing a predictable input does not make it unpredictable.

How should passwords be stored?

Use password_hash with password_verify. The manual notes that PASSWORD_DEFAULT is currently bcrypt and may change, so size the column at 255 bytes. Bcrypt truncates at 72 bytes. Argon2i and Argon2id are available when PHP is built with Argon2 support, with tunable memory, time and thread costs.

Do not pass a salt. The function generates one from the operating system's source, and an explicit salt has been ignored since PHP 8.0. Do not build your own from rand, mt_rand or uniqid. The manual suggests choosing a cost so one hash takes under about 350 ms for interactive logins, benchmarked on your own hardware, and the standard pattern is to call password_needs_rehash after a successful login and store a new hash if the parameters moved.

What about encryption?

Do not write your own scheme. The sodium extension, bundled with PHP since 7.2, provides authenticated encryption through sodium_crypto_secretbox, with keys and nonces you generate from random_bytes or the matching keygen helper. Authenticated means a modified ciphertext fails to decrypt instead of producing altered plaintext. If you use OpenSSL functions directly, you take on mode selection, IV handling and authentication yourself, and mistakes there are common.

Never reuse a nonce with the same key. Store keys outside the codebase, and rotate them on a schedule.

How do you compare secrets?

A normal string comparison stops at the first difference, so it takes slightly different time depending on where the mismatch occurs. hash_equals, per the manual, checks equality without leaking information about the known string through execution time. Pass the known value first and the user-supplied value second.

What to do

  1. Replace rand, mt_rand, uniqid and lcg_value anywhere they produce tokens, codes or keys. Add a static analysis rule to catch regressions.
  2. Generate tokens with random_bytes of at least 16 bytes, then hex or base64 encode.
  3. Store passwords with password_hash, and rehash on login when needed.
  4. Encrypt with sodium, using fresh nonces and keys from the secure generator.
  5. Compare tokens, signatures and API keys with hash_equals.
  6. Handle the exception from random_bytes by failing the request, not by falling back.

Quick check

Is mt_rand fine if I hash its output?

No. Hashing does not add unpredictability. If the input is guessable, so is the hash. Use random_bytes.

Why does random_bytes throw an exception?

It throws if no secure source is available, so your code cannot silently continue with weak values. Let the request fail.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.