Laravel Insecure Random Numbers: How to Replace rand() and mt_rand() with Cryptographic Alternatives

Using rand() or mt_rand() for tokens, passwords, or security decisions creates predictable values that attackers can guess or reproduce.

Medium severity Application Security Updated 2026-05-01 Markdown

Computers are deterministic machines — they produce the same output given the same input. True randomness requires external entropy, so programming languages implement pseudo-random number generators (PRNGs), which produce sequences that appear random but are determined by an initial seed value. PHP's rand() and mt_rand() use PRNGs seeded from predictable sources like the current time. An attacker who can observe a few outputs from these functions can mathematically reconstruct the internal state and predict every future output.

The attack against predictable random values is called PRNG state reconstruction. If a password reset token is generated with md5(mt_rand()), an attacker who requests a password reset can observe when the request was made (from the email timestamp), use the timestamp as the seed range, and generate every possible mt_rand() output for a small time window. The number of possibilities is small enough to brute-force against the application's reset endpoint within minutes. The same applies to session identifiers, OTP codes, and API keys generated with predictable randomness.

Real-world exploits of this class have been documented. Researchers found that several PHP applications generated password reset tokens using md5(microtime()), making them trivially predictable — the seed space for a 1-second window is only 1,000,000 possible values. PHP 7 introduced random_bytes() and random_int() as official cryptographically secure alternatives, drawing entropy from the operating system's CSPRNG (/dev/urandom on Linux). Laravel's Str::random() uses random_bytes() internally, making it safe for security-sensitive usage.

The Problem

PHP's rand() and mt_rand() functions use predictable pseudo-random number generators (PRNGs). An attacker who observes a few outputs can predict future values — and in some cases, reconstruct past values. If you use these functions to generate password reset tokens, API keys, session identifiers, CSRF tokens, or any security-sensitive value, attackers can predict them and gain unauthorized access. PHP provides cryptographically secure alternatives that should always be used instead.

How to Fix

  1. 1

    Find insecure random usage in your code

    Search for rand() and mt_rand() in security-sensitive contexts:

    grep -rn 'rand(\|mt_rand(\|array_rand(\|shuffle(' app/ --include='*.php'

    Look specifically for usage in: - Token generation - Password/PIN creation - OTP codes - Nonce or salt generation - Random filename generation for uploads - Lottery/selection logic with financial impact

  2. 2

    Replace with cryptographically secure alternatives

    Use these replacements:

    // Random integers
    // BEFORE: rand(100000, 999999)
    // AFTER:
    random_int(100000, 999999);
    // Random strings/tokens
    // BEFORE: md5(mt_rand())
    // AFTER:
    Str::random(40);          // Laravel helper (uses random_bytes)
    bin2hex(random_bytes(20)); // Raw PHP
    // Random bytes
    // BEFORE: mt_rand() based
    // AFTER:
    random_bytes(32);
    // Shuffling arrays securely
    // BEFORE: shuffle($array)
    // AFTER: Use a Fisher-Yates shuffle with random_int()
  3. 3

    Use Laravel helpers for common patterns

    Laravel provides secure random generation out of the box:

    // Random string
    use Illuminate\Support\Str;
    Str::random(40);     // 40-char random string
    Str::uuid();         // UUID v4
    Str::orderedUuid();  // Time-ordered UUID
    // Token generation
    $token = hash('sha256', Str::random(60));
    // Unique filenames
    $filename = Str::uuid() . '.' . $file->extension();
    All of these use random_bytes() internally.

How to Verify

Search for remaining insecure usage:

grep -rn 'mt_rand(\|\brand(' app/ --include='*.php' | grep -v '// safe\|// non-security'

Run php artisan stackshield:scan --check=SS040 to verify.

Prevention

Use Str::random(), random_int(), or random_bytes() for all security-sensitive randomness. rand() and mt_rand() are acceptable only for non-security uses like UI element positioning or test data generation. Add a code review rule to flag rand()/mt_rand() usage.

Frequently Asked Questions

Is mt_rand() safe for non-security purposes?

Yes. For non-security uses like randomizing display order, generating test data, or adding visual variation, mt_rand() is fine. The key distinction is: will predictable values cause a security issue? If not, mt_rand() is acceptable and faster.

Is Str::random() cryptographically secure?

Yes. Laravel's Str::random() uses random_bytes() internally, which is cryptographically secure. It is safe for generating tokens, passwords, and other security-sensitive values.

Is uniqid() safe to use for generating tokens?

No. uniqid() is based on microtime() and produces a value that is almost entirely predictable from the current timestamp. It is not suitable for tokens, session IDs, or any security-sensitive identifier. Use Str::random(40) or bin2hex(random_bytes(20)) instead, both of which draw from the operating system's CSPRNG.

How should I generate a secure 6-digit OTP or PIN code?

Use random_int(100000, 999999) for a 6-digit numeric OTP. random_int() uses a CSPRNG and is safe for security-sensitive purposes. For a zero-padded 6-digit code: str_pad(random_int(0, 999999), 6, "0", STR_PAD_LEFT). Never use rand(), mt_rand(), or time-based generation for OTPs — the predictability makes them trivially brute-forceable.

What about PHP's openssl_random_pseudo_bytes() function?

openssl_random_pseudo_bytes() is cryptographically secure when the second $strong output parameter returns true, but PHP 7+ added random_bytes() as the preferred, simpler alternative that is always cryptographically strong without requiring the $strong parameter check. Use random_bytes() in new code — it is simpler to use correctly and available in all PHP versions that Laravel supports.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue, and most teams do not find out until something breaks. The free scan checks your live app in 60 seconds. Then StackShield re-runs every check after each deploy, so a fix you ship today does not quietly regress next week.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup for the scan. Continuous monitoring on a 14-day trial, no card.