Crea contraseñas realmente fuertes en PHP: Generador seguro con aleatoriedad criptográfica y sin sesgos

En el fondo todos sabemos que “123456”, “password”, el nombre del perro o el año de nacimiento no son precisamente contraseñas seguras… y sin embargo seguimos usándolas.

El dato que más asusta: según diferentes estudios de los últimos años, entre el 76% y el 81% de las brechas de seguridad involucran credenciales débiles o reutilizadas.

Por eso hoy vamos a construir juntos, paso a paso, un generador de contraseñas realmente robusto en PHP, utilizando aleatoriedad criptográfica y evitando los sesgos que tienen muchas implementaciones caseras.

¿Qué diferencia hay entre “aleatorio” y “criptográficamente seguro”?

Cuando usamos rand(), mt_rand() o incluso el famoso rand(1000000,9999999) para generar códigos o contraseñas, estamos trabajando con generadores pseudoaleatorios.

Son muy útiles para juegos, animaciones, simulaciones… pero no para seguridad.

La razón principal es que si un atacante llega a conocer:

  • el algoritmo
  • la semilla (seed) inicial
  • o unas pocas salidas consecutivas

… en muchos casos puede reconstruir toda la secuencia futura.

Para contraseñas (y cualquier secreto criptográfico) necesitamos algo mucho más fuerte: aleatoriedad criptográficamente segura (CSPRNG).

En PHP moderno (≥7.0) la forma recomendada y más sencilla es usar random_bytes().

Esta función aprovecha las fuentes de entropía del sistema operativo (interrupciones de hardware, eventos del kernel, etc.), por lo que el resultado es impredecible incluso para alguien que conozca todo el estado interno previo.

El código – Función generateSecurePassword()

<?php

/**
 * Genera una contraseña fuerte usando aleatoriedad criptográfica sin sesgos
 * 
 * @param int    $length        Longitud deseada (mínimo 8)
 * @param bool   $useNumbers    Incluir números
 * @param bool   $useLowercase  Incluir minúsculas
 * @param bool   $useUppercase  Incluir mayúsculas
 * @param bool   $useSymbols    Incluir símbolos
 * @param bool   $useSpaces     Incluir espacios (¡cuidado con esta opción!)
 * @param string $customSymbols Símbolos personalizados (si se envía, reemplaza los predeterminados)
 * 
 * @return string Contraseña generada
 */
function generateSecurePassword(
    int $length = 12,
    bool $useNumbers = true,
    bool $useLowercase = true,
    bool $useUppercase = true,
    bool $useSymbols = true,
    bool $useSpaces = false,
    string $customSymbols = ''
): string {
    // Protección básica contra longitudes peligrosamente cortas
    $length = max(8, $length);

    // Conjuntos de caracteres
    $numbers   = '0123456789';
    $lowercase = 'abcdefghijklmnopqrstuvwxyz';
    $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $symbols   = $customSymbols !== '' 
        ? $customSymbols 
        : '!@#$%^&*()-_=+[]{}|;:,.<>?/~`';

    $spaces = ' ';

    // Construimos el pool completo según las opciones elegidas
    $chars = '';
    if ($useNumbers)    $chars .= $numbers;
    if ($useLowercase)  $chars .= $lowercase;
    if ($useUppercase)  $chars .= $uppercase;
    if ($useSymbols)    $chars .= $symbols;
    if ($useSpaces)     $chars .= $spaces;

    // Caso extremo: el usuario desactivó TODO → fallback razonable
    if ($chars === '') {
        $chars = $lowercase . $uppercase . $numbers;
    }

    $charsLength = strlen($chars);

    // Generador de índice aleatorio sin sesgo (rejection sampling)
    $getUnbiasedIndex = function (int $max) use (&$random_bytes): int {
        $maxSafe = (floor(255 / $max)) * $max;

        do {
            $byte = random_bytes(1);
            $value = ord($byte);
        } while ($value >= $maxSafe);

        return $value % $max;
    };

    // Generamos la contraseña base
    $password = '';
    for ($i = 0; $i < $length; $i++) {
        $index = $getUnbiasedIndex($charsLength);
        $password .= $chars[$index];
    }

    // Garantizamos al menos un carácter de cada tipo solicitado
    $required = [];
    if ($useNumbers)    $required[] = $numbers[random_int(0, 9)];
    if ($useLowercase)  $required[] = $lowercase[random_int(0, 25)];
    if ($useUppercase)  $required[] = $uppercase[random_int(0, 25)];
    if ($useSymbols)    $required[] = $symbols[random_int(0, strlen($symbols) - 1)];
    if ($useSpaces)     $required[] = ' ';

    // Mezclamos las posiciones donde insertaremos los caracteres obligatorios
    $positions = range(0, $length - 1);
    shuffle($positions); // ← ¡shuffle usa Mersenne Twister, pero solo para posiciones!

    foreach ($required as $i => $char) {
        if (isset($positions[$i])) {
            $password[$positions[$i]] = $char;
        }
    }

    return $password;
}

Puntos clave que hacen diferente (y más seguro) este generador

  • Usa random_bytes() → fuente criptográficamente segura
  • Rejection sampling para evitar sesgo modular (muy importante cuando el pool no es potencia de 2)
  • Garantiza al menos un carácter de cada tipo sin sacrificar la aleatoriedad global
  • Permite mucha flexibilidad (longitud, conjuntos de caracteres, símbolos personalizados)
  • Fallback razonable si el usuario desactiva todas las opciones

Ejemplo de uso rápido

$pass = generateSecurePassword(
    length: 16,
    useNumbers: true,
    useLowercase: true,
    useUppercase: true,
    useSymbols: true,
    customSymbols: '!@#*_$%'
);

echo "Contraseña generada:  " . $pass;
// Ejemplo de salida:  kP$9mW#vL2qR*8nT

Recomendaciones finales rápidas

  • Longitudes ≥14–16 caracteres son el estándar recomendado en 2025–2026 para la mayoría de aplicaciones
  • Nunca guardes contraseñas en texto plano → usa siempre password_hash()
  • Considera ofrecer también la opción de passphrase (estilo 4–6 palabras) en interfaces de usuario
  • Si vas a generar muchas contraseñas en batch → piensa en random_bytes() en bloque (más eficiente)

La seguridad de las contraseñas no se trata solo de la longitud, sino de la calidad de la aleatoriedad. Con random_bytes() y zero sesgos, este generador es imbatible para apps cotidianas. Podrías extenderlo con passphrases o exclusión de caracteres ambiguos (como ‘O’ vs ‘0’) en versiones futuras.

Implementa esta función hoy mismo y eleva la seguridad de tu app. ¡Tus usuarios te lo agradecerán! ¿Estás listo para probarla? ¡Explora las profundidades de mejorar la generación de claves en tu aplicación! Sigamos codificando.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.