Números Pseudoaleatorios · Tema 18

Generadores criptográficos frente a simulación

Seguridad frente a velocidad: por qué random sirve para simular y está prohibido para claves, y por qué secrets es al revés.

01 · Punto de partida

El generador correcto para cada trabajo

Un simulador quiere millones de valores por segundo y repetirlos con una semilla. Un sistema de claves quiere lo opuesto: que nadie pueda predecir ni repetir el próximo token aunque haya visto mil anteriores. Usar el generador equivocado sale caro: sorteo predecible o simulación de 10 horas que no se puede depurar.

Los Temas 12 a 17 te dieron velocidad y calidad estadística. Aquí agregamos la tercera dimensión: la imprevisibilidad ante un adversario. Esa es la frontera entre un PRNG de simulación y un CSPRNG.

  • ¿Por qué MT/PCG/Philox, aun siendo modernos, no sirven para claves?
  • ¿Qué garantiza un CSPRNG que un PRNG no promete?
  • ¿Por qué el CSPRNG es más lento y no se siembra a mano?
  • ¿Cuándo usar random, numpy y cuándo secrets?

02 · Definición

PRNG frente a CSPRNG

◈

PRNG simulación

MT, PCG, Philox, xoshiro: semilla manual, secuencia repetible, gigas por segundo. Predecible con observación suficiente.

⇄

CSPRNG

ChaCha20, AES-CTR_DRBG, getrandom / BCrypt: entropía del SO, re-siembra automática, pasa el next-bit test.

▣

Next-bit test

Si conociendo todos los bits hasta n ningún atacante eficiente predice el bit n+1 mejor que 50 %, es criptográficamente seguro.

Comparación directa.
AspectoSimulación (random / numpy)Cripto (secrets / os.urandom)
SemillaManual, p. ej. seed(42)Entropía del SO, no la elegís
ReproducibleSí (ideal depurar)No (ideal no predecir)
VelocidadMuy rápido, vectorizableMás lento, llamada al sistema
Si se filtra el estadoSe predice todo lo futuroResiste: re-seed y backtracking limitado

03 · Cómo funciona un CSPRNG

Entropía + cifrado + re-siembra

El SO junta ruido real (tiempos de interrupciones, sensores, jitter) en un pozo de entropía y lo estira con un cifrado (ChaCha20, AES) en modo DRBG. Cada llamada mezcla estado nuevo: aunque roben el estado actual, el pasado y el futuro tras re-seed siguen protegidos.

Entropía
Ruido físico del sistema (/dev/urandom, getrandom(), BCryptGenRandom). No es semilla elegida: es azar del entorno.
DRBG
Generador determinista que estira poca entropía en muchos bytes seguros (Hash_DRBG, HMAC_DRBG, CTR_DRBG según NIST 800-90A).
Reseed
Re-inyección periódica de entropía fresca: limita el daño si el estado se compromete.

Bien usado

secrets para lo secreto

Claves, tokens, passwords, sorteos con premio: secrets.token_hex(), secrets.randbelow(). Sin semilla manual.

Mal usado

random para lo secreto

Con 624 salidas se clona MT; con pocas se clona un LCG. Un token con random es un cartel de “adiviname”.

04 · Ejemplos

Qué va de cada lado

¿Hay adversario?→No: simular→Sí: secrets
  1. 1
    Simulación Monte Carlo.

    Mil millones de réplicas reproducibles con default_rng(123). Velocidad y spawn mandan (Temas 37–38).

  2. 2
    Token de sesión / reset de clave.

    secrets.token_urlsafe(32). Cada llamado es único aunque el atacante vea mil tokens previos.

  3. 3
    Sorteo con premio / loot pagado.

    Hay incentivo para predecir: va del lado cripto (secrets.randbelow(n)), auditado y sin semilla fija en producción.

05 · Implementación en Python

secrets frente a random

La librería estándar lo deja claro: random es para modelado y juegos sin adversario; secrets es para claves y tokens. Fijate que secrets ni siquiera acepta semilla.

Python en tu navegador. Ejecutá dos veces el bloque random: repite. El bloque secrets nunca repite.

import random

random.seed(42)
print([round(random.random(), 6) for _ in range(3)])
random.seed(42)
print([round(random.random(), 6) for _ in range(3)])

Misma semilla, misma secuencia: perfecto para depurar, fatal para un token.

Tokens que no se repiten

import secrets

print(secrets.token_hex(8))
print(secrets.token_hex(8))
print(secrets.randbelow(100), secrets.randbelow(100))
print(secrets.choice(["oro", "plata", "bronce"]))

06 · Exploración

Laboratorio: velocidad frente a imprevisibilidad

Compará tres fuentes del navegador: LCG didáctico (predecible y con semilla), Math.random (rápido, no seguro) y crypto.getRandomValues (seguro). Medimos tiempo real para N valores y si la secuencia se repite con la misma semilla.

EXPERIMENTO 18

Predecir o no predecir

LCG · Math.random · crypto

Los resultados numéricos aparecen debajo.
Primer u—
Tiempo (ms)—
¿Repite con semilla?—
¿Apto claves?—

LCG con semilla 42: repetible al milímetro.

La forma de la curva no delata seguridad: lo que importa es si conociendo el pasado se puede predecir el futuro y de dónde sale la semilla.

Preguntas para explorar

  1. Generá dos veces con LCG y semilla 42: ¿cambia el primer valor? Repetí con crypto: ¿qué pasa?
  2. Subí N a 500 y compará tiempos: ¿cuál es más lento? ¿Lo usarías para mil millones de réplicas?
  3. Si publicás la semilla de un sorteo con LCG, ¿puede un participante calcular el ganador antes del sorteo?
Ver respuestas sugeridas
  1. LCG repite exacto; crypto cambia siempre porque ignora tu semilla y bebe entropía del sistema.
  2. Crypto suele ser más lento (llamada al sistema): para simulación masiva es un despilfarro; para un token el costo es irrelevante.
  3. Sí: con la fórmula y la semilla reconstruye todo. Los sorteos con incentivo van con CSPRNG y auditoría, nunca con semilla pública.

07 · Comprensión

Confusiones frecuentes

«random es seguro porque nadie sabe mi semilla»

Seguridad por oscuridad: con salidas observadas se reconstruye el estado (MT con 624 valores, LCG con 2–3). La semilla se filtra en logs, repos y backups.

«Uso crypto para la simulación, así es de máxima calidad»

Pagás lentitud y perdés reproducibilidad y streams paralelos. Calidad estadística ya la dan PCG/Philox; seguridad no la necesitás contra la naturaleza.

«Math.random es criptográfico»

No: en navegadores es un PRNG rápido (xorshift128+ y variantes según motor), sin garantía de imprevisibilidad. Para tokens en JS usá crypto.getRandomValues.

«Hago rand() % n y listo»

El módulo introduce sesgo si n no divide al rango (Tema 35). Para sorteo justo usá secrets.randbelow(n), que hace rechazo interno sin sesgo.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: token de sesión

Generá dos tokens URL-safe de 32 bytes y verificá que son distintos. ¿Por qué no fijarías semilla aquí?

import secrets

a = secrets.token_urlsafe(32)
b = secrets.token_urlsafe(32)
print(a)
print(b)
print(a != b)
Ver solución razonada

Debe dar True (colisión imposible en la práctica). Fijar semilla destruiría la gracia: cualquiera repetiría tus “secretos”. secrets ni ofrece seed() a propósito.

Ejercicio 2: el sorteo justo

Sorteá un ganador entre 1000 participantes sin sesgo módulo. Repetilo 3 veces.

import secrets

for _ in range(3):
    print(secrets.randbelow(1000))
Ver solución

Tres números distintos casi seguro, cada uno con probabilidad 1/1000. Con rand() % 1000 los valores bajos tendrían una milésima extra según el rango base.

Ejercicio 3: clonar un LCG con 3 datos

Observá 3 salidas de un LCG conocido (a=1103515245, c=12345, m=2**31) y predecí la cuarta resolviendo el estado. ¿Qué prueba esto sobre random para claves?

Ver una posible respuesta
a, c, m = 1103515245, 12345, 2**31
x = 42
obs = []
for _ in range(3):
    x = (a * x + c) % m
    obs.append(x / m)
print([round(v, 6) for v in obs])
# atacante que ve obs y conoce a, c, m recupera x y predice:
x_next = (a * round(obs[-1] * m) + c) % m
print("predicho:", round(x_next / m, 6))

Si un LCG de juguete se clona con 1 dato, MT se clona con 624: ningún PRNG de simulación resiste a un observador. Para secreto, CSPRNG o nada.

09 · Síntesis

Ideas para recordar

  • PRNG = velocidad + reproducibilidad; CSPRNG = imprevisibilidad + entropía.
  • MT/PCG/Philox se clonan observando salidas: prohibidos para claves y sorteos con incentivo.
  • En Python: random/NumPy para simular, secrets/os.urandom para lo secreto.
  • En JS: Math.random para demos, crypto.getRandomValues para tokens.
  • Evitar % n en sorteos: usar randbelow sin sesgo.

En el próximo tema volvemos al cauce de simulación: generación de números uniformes, de enteros del generador a U(0,1).