Números aleatorios en PHP: rand, mt_rand, random_int y random_bytes seguros

PHP tiene cuatro funciones para generar números aleatorios que sirven para propósitos muy distintos: rand() y mt_rand() para uso general (no criptográfico), y random_int() y random_bytes() para contextos de seguridad. Elegir la función incorrecta puede comprometer la seguridad de tu aplicación.

rand() y mt_rand(): uso general

<?php
// rand(): genera un entero aleatorio en el rango [min, max]
echo rand();        // número aleatorio entre 0 y PHP_INT_MAX
echo rand(1, 100);  // número entre 1 y 100

// mt_rand(): usa el algoritmo Mersenne Twister, más rápido y con mejor distribución
echo mt_rand();
echo mt_rand(1, 6); // simular un dado

// Desde PHP 7.1, rand() usa internamente el mismo algoritmo que mt_rand()
// La diferencia es mínima; mt_rand() sigue siendo marginalmente más rápido
?>

Ni rand() ni mt_rand() son adecuadas para criptografía. Su secuencia es predecible si se conoce la semilla, que PHP puede reutilizar entre llamadas del mismo proceso.

random_int(): enteros seguros criptográficamente

random_int(int $min, int $max) está disponible desde PHP 7.0 y usa la fuente de entropía del sistema operativo (/dev/urandom en Linux, CryptGenRandom en Windows):

<?php
// Entero criptográficamente seguro en el rango [1, 100]
$numero = random_int(1, 100);

// Simular un dado de 6 caras de forma segura
$dado = random_int(1, 6);

// Si no hay suficiente entropía, lanza una Exception
try {
    $seguro = random_int(PHP_INT_MIN, PHP_INT_MAX);
} catch (Exception $e) {
    // Muy raro en sistemas modernos; gestionar igualmente
    error_log('Error de entropía: ' . $e->getMessage());
}
?>

random_bytes(): bytes seguros criptográficamente

random_bytes(int $length) genera una cadena de N bytes aleatorios criptográficamente seguros:

<?php
// Generar 16 bytes aleatorios (128 bits de entropía)
$bytes = random_bytes(16);
echo bin2hex($bytes);       // representación hexadecimal (32 chars)
echo base64_encode($bytes); // representación base64 (24 chars, con padding)
?>

Ejemplo real: generar contraseñas seguras

<?php
function generarPassword(int $longitud = 16, string $caracteres = ''): string
{
    if ($caracteres === '') {
        $caracteres = 'abcdefghijklmnopqrstuvwxyz'
                    . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                    . '0123456789'
                    . '!@#$%^&*()-_=+[]{}|;:,.<>?';
    }

    $max      = strlen($caracteres) - 1;
    $password = '';
    for ($i = 0; $i < $longitud; $i++) {
        $password .= $caracteres[random_int(0, $max)];
    }
    return $password;
}

echo generarPassword(20);
// Por ejemplo: "aK3!mZ9xP#2qN7wR8tYv"
?>

Ejemplo real: tokens CSRF

<?php
// Generar token CSRF para formularios
function generarTokenCsrf(): string
{
    $token = bin2hex(random_bytes(32)); // 64 chars hex
    $_SESSION['csrf_token'] = $token;
    return $token;
}

function validarTokenCsrf(string $tokenRecibido): bool
{
    $tokenEsperado = $_SESSION['csrf_token'] ?? '';
    // hash_equals evita timing attacks
    return hash_equals($tokenEsperado, $tokenRecibido);
}

// En el formulario
$token = generarTokenCsrf();
echo '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($token) . '">';

// Al procesar el formulario
if (!validarTokenCsrf($_POST['csrf_token'] ?? '')) {
    http_response_code(403);
    exit('Token CSRF inválido');
}
?>

Ejemplo real: sorteos y selección aleatoria

<?php
// Seleccionar N ganadores de una lista sin repetición
function seleccionarGanadores(array $participantes, int $n): array
{
    if ($n > count($participantes)) {
        throw new InvalidArgumentException('No hay suficientes participantes');
    }

    $ganadores = [];
    $indices   = range(0, count($participantes) - 1);

    for ($i = 0; $i < $n; $i++) {
        $pos        = random_int(0, count($indices) - 1);
        $ganadores[] = $participantes[$indices[$pos]];
        array_splice($indices, $pos, 1); // eliminar el índice ya seleccionado
    }

    return $ganadores;
}

$participantes = ['Ana', 'Luis', 'María', 'Carlos', 'Elena', 'Pedro'];
$ganadores     = seleccionarGanadores($participantes, 2);
echo implode(' y ', $ganadores) . ' han ganado el sorteo';
?>

Ejemplo real: códigos de recuperación de cuenta

<?php
// Generar 8 códigos de recuperación legibles (estilo GitHub)
function generarCodigosRecuperacion(int $cantidad = 8): array
{
    $codigos = [];
    for ($i = 0; $i < $cantidad; $i++) {
        // Formato: XXXX-XXXX (solo hex en minúsculas, fácil de leer y escribir)
        $parte1 = substr(bin2hex(random_bytes(2)), 0, 4);
        $parte2 = substr(bin2hex(random_bytes(2)), 0, 4);
        $codigos[] = "$parte1-$parte2";
    }
    return $codigos;
}

$codigos = generarCodigosRecuperacion();
// ['a3f1-b82c', '7d4e-1c9a', ...]
?>

Cuándo usar cada función

Función

Criptográfica

Úsala para...

rand() / mt_rand()

No

Juegos, tests, datos de ejemplo, simulaciones no críticas

random_int()

Sí

Passwords, tokens, sorteos, códigos de verificación

random_bytes()

Sí

CSRF tokens, claves de sesión, IVs para cifrado

La documentación de random_int() y la de random_bytes() detallan las fuentes de entropía utilizadas en cada sistema operativo y las excepciones que pueden lanzar.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP