Math.random() vs crypto.getRandomValues(): por qué importa la diferencia
Si alguna vez has escrito "Math.random()" en JavaScript para generar un número al azar, has usado un generador pseudoaleatorio — no uno verdaderamente aleatorio. La diferencia entre esto y crypto.getRandomValues() parece un tecnicismo, pero tiene consecuencias reales en seguridad, imparcialidad y confiabilidad que vale la pena entender, sobre todo si construyes algo que otras personas van a usar.
Qué es realmente un generador pseudoaleatorio (PRNG)
Math.random() no genera aleatoriedad "de la nada". Internamente usa un algoritmo determinista (en la mayoría de motores de JavaScript modernos, una variante de xorshift128+) que parte de un estado interno — una semilla — y produce una secuencia de números que parece aleatoria estadísticamente, pero que es completamente reproducible si conoces el algoritmo y el estado inicial.
Esto no es un defecto de implementación: es exactamente para lo que se diseñó. Un PRNG es rápido, eficiente, y perfecto para casos donde la "aleatoriedad visual" es suficiente — animaciones, efectos visuales, muestreo no crítico de datos, juegos simples sin apuestas reales. El problema aparece cuando alguien lo usa donde la imparcialidad o la impredecibilidad son requisitos de seguridad, no solo de apariencia.
Qué cambia con crypto.getRandomValues()
La API Web Crypto, específicamente crypto.getRandomValues(), no depende de una fórmula matemática con estado interno predecible. Obtiene su entropía de fuentes del sistema operativo — generadores de números aleatorios del hardware, ruido de temporización de eventos del sistema, y otras fuentes físicas que son, en la práctica, imposibles de reproducir o predecir desde fuera.
Es la misma tecnología subyacente que usan los navegadores para generar claves criptográficas, tokens de sesión seguros, y otros valores donde una predicción exitosa por parte de un atacante tendría consecuencias reales. Por eso es el estándar recomendado para contraseñas, tokens, sorteos, y cualquier caso donde "alguien podría intentar predecir o manipular el resultado" sea una amenaza real.
Cuándo usar cada uno en la práctica
Usa Math.random() cuando la velocidad importa más que la imparcialidad criptográfica y las consecuencias de un resultado "no perfectamente aleatorio" son bajas: animaciones, posiciones aleatorias de elementos decorativos, shuffle de una playlist personal, prototipos rápidos.
Usa crypto.getRandomValues() (o crypto.randomUUID() para identificadores) cuando el resultado tiene consecuencias reales: contraseñas, tokens de sesión o de API, sorteos con premios, cualquier decisión donde un participante podría beneficiarse de predecir o manipular el resultado, o simplemente cuando quieras poder decir con certeza técnica que tu proceso es imparcial.
Todos los generadores de este sitio usan crypto.getRandomValues() o crypto.randomUUID() precisamente por esta razón — no porque Math.random() esté "mal", sino porque para sorteos, contraseñas y decisiones que la gente toma en serio, la barra de imparcialidad debe ser más alta.
Preguntas frecuentes
- ¿Math.random() es inseguro?
- No es "inseguro" para su propósito original (aleatoriedad visual, no crítica), pero es inapropiado para contraseñas, tokens de seguridad o sorteos donde la predictibilidad importa.
- ¿crypto.getRandomValues() es más lento?
- Es marginalmente más lento que Math.random(), pero la diferencia es imperceptible para el usuario en casi cualquier caso de uso — la seguridad adicional vale ese costo mínimo.
- ¿Puedo "sembrar" (seed) crypto.getRandomValues() para obtener resultados reproducibles?
- No, y esa es justamente la idea: a diferencia de un PRNG con semilla, no puedes reproducir la secuencia, lo que es una característica de seguridad, no una limitación.
- ¿Node.js tiene un equivalente?
- Sí, el módulo crypto de Node.js ofrece randomBytes() y randomUUID() con las mismas garantías criptográficas que la Web Crypto API del navegador.