data types and standard library

Randomness

Random values appear in password reset links, email verification tokens, API keys, CSRF tokens, invitation codes, temporary filenames, test data, lotteries, and games.

The most important distinction is security. If a value protects an account, session, payment, private file, or permission, use PHP's cryptographically secure functions: random_bytes() and random_int().

Generate secure tokens

Use random_bytes() when you need unpredictable bytes, then encode them into a string that can travel through URLs, emails, or databases.

PHP example
<?php

declare(strict_types=1);

$token = bin2hex(random_bytes(32));

echo 'Token length: ' . strlen($token) . PHP_EOL;
echo 'Looks like hex: ' . (ctype_xdigit($token) ? 'yes' : 'no') . PHP_EOL;

// Prints:
// Token length: 64
// Looks like hex: yes

The actual token changes every run. The reliable checks are its length and format.

Generate secure integers

Use random_int() when you need an integer inside a range.

PHP example
<?php

declare(strict_types=1);

$roll = random_int(1, 6);

echo 'Roll is valid: ' . ($roll >= 1 && $roll <= 6 ? 'yes' : 'no') . PHP_EOL;

// Prints:
// Roll is valid: yes

This is suitable for security-sensitive numeric choices too, such as backup code digits or short-lived verification codes.

Format numeric codes carefully

Verification codes often need leading zeroes. Generate a number, then format it as a fixed-width string.

PHP example
<?php

declare(strict_types=1);

function verificationCode(): string
{
    return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
}

$code = verificationCode();

echo 'Code length: ' . strlen($code) . PHP_EOL;
echo 'Only digits: ' . (ctype_digit($code) ? 'yes' : 'no') . PHP_EOL;

// Prints:
// Code length: 6
// Only digits: yes

Do not cast the final code back to an integer, because that would remove leading zeroes.

Avoid predictable functions for secrets

Older functions such as rand() and mt_rand() are not the right choice for tokens, passwords, sessions, or anything an attacker could benefit from guessing.

PHP example
<?php

declare(strict_types=1);

function passwordResetToken(): string
{
    return bin2hex(random_bytes(32));
}

$token = passwordResetToken();

echo strlen($token) . PHP_EOL;

// Prints:
// 64

The function name says what the random value is for. That helps reviewers spot when security-sensitive code is using the wrong tool.

Random filenames should not expose original names

Temporary and stored filenames often need randomness to avoid collisions and hide user-supplied names.

PHP example
<?php

declare(strict_types=1);

function randomStorageName(string $extension): string
{
    $extension = strtolower($extension);

    if (!preg_match('/^[a-z0-9]+$/', $extension)) {
        throw new InvalidArgumentException('Invalid extension.');
    }

    return bin2hex(random_bytes(16)) . '.' . $extension;
}

$filename = randomStorageName('pdf');

echo str_ends_with($filename, '.pdf') ? 'pdf name' : 'wrong extension';
echo PHP_EOL;

// Prints:
// pdf name

The original filename can be stored as metadata for display, but it should not control the storage path.

Test random code by properties

Random output changes, so tests should check promises that are always true.

PHP example
<?php

declare(strict_types=1);

function inviteCode(): string
{
    return strtoupper(bin2hex(random_bytes(4)));
}

$code = inviteCode();

$isValid = strlen($code) === 8 && ctype_xdigit($code) && strtoupper($code) === $code;

echo $isValid ? 'valid invite code' : 'invalid invite code';
echo PHP_EOL;

// Prints:
// valid invite code

For code that needs deterministic randomness in tests, inject a small generator service or use a dedicated randomizer abstraction. Do not weaken production randomness just to make tests easier.

What to remember

Use random_bytes() for secure tokens and random_int() for secure numbers. Check random output by length, format, and range. Keep generated secrets out of logs, store only hashes when appropriate, and avoid predictable generators for anything security-sensitive.

Practice

Task: Generate account recovery values

Write a small example that generates values for an account recovery flow.

Requirements

  • Use declare(strict_types=1);.
  • Generate a password reset token using random_bytes().
  • Encode the token as hexadecimal.
  • Generate a six-digit verification code using random_int().
  • Preserve leading zeroes in the verification code.
  • Print checks for token length, token format, code length, and code format.
  • Include the expected output as comments in the same PHP code block.

Do not print the real token as the main proof. Randomness should be checked by properties that stay true every run.

Show solution
PHP example
<?php

declare(strict_types=1);

function passwordResetToken(): string
{
    return bin2hex(random_bytes(32));
}

function verificationCode(): string
{
    return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
}

$token = passwordResetToken();
$code = verificationCode();

echo 'Token length: ' . strlen($token) . PHP_EOL;
echo 'Token is hex: ' . (ctype_xdigit($token) ? 'yes' : 'no') . PHP_EOL;
echo 'Code length: ' . strlen($code) . PHP_EOL;
echo 'Code is digits: ' . (ctype_digit($code) ? 'yes' : 'no') . PHP_EOL;

// Prints:
// Token length: 64
// Token is hex: yes
// Code length: 6
// Code is digits: yes

The real values change every run, so the example verifies stable properties instead of comparing exact strings. In a production recovery flow, the raw reset token should be shown to the user once and a hash should be stored for later comparison.