Juguemos un poco de póker informático, solo tú, yo y un servidor en el que ambos confiamos. El servidor usa un generador de números pseudoaleatorios que se inicializa con una semilla de 32 bits justo antes de jugar. Así que hay alrededor de cuatro mil millones de mazos posibles.
Tengo cinco cartas en mi mano, aparentemente no estamos jugando Texas Hold 'Em. Supongamos que las cartas se reparten una para mí, una para ti, una para mí, una para ti, y así sucesivamente. Así que tengo la primera, tercera, quinta, séptima y novena cartas en el mazo.
Anteriormente ejecuté el generador de números pseudoaleatorios cuatro mil millones de veces, una vez con cada semilla, y escribí la primera tarjeta generada para cada uno en una base de datos. Supongamos que mi primera carta es la reina de espadas. Eso solo aparece como la primera carta en una de cada 52 de esas barajas posibles, por lo que hemos reducido las posibles barajas de cuatro mil millones a alrededor de 80 millones más o menos.
Supongamos que mi segunda carta es el tres de corazones. Ahora corro mi RNG 80 millones más de veces usando los 80 millones de semillas que producen la reina de espadas como primer número. Esto me lleva un par de segundos. Escribo todas las barajas que producen los tres corazones como la tercera carta, la segunda carta en mi mano. De nuevo, eso es solo alrededor del 2% de las cubiertas, por lo que ahora tenemos 2 millones de cubiertas.
Supongamos que la tercera carta en mi mano es la 7 de los clubes. Tengo una base de datos de 2 millones de semillas que reparten mis dos cartas; Ejecuto mi RNG otros 2 millones de veces para encontrar el 2% de esas barajas que producen el 7 de clubes como la tercera carta, y solo tenemos 40,000 barajas.
Ya ves cómo va esto. Ejecuté mi RNG 40000 más veces para encontrar todas las semillas que producen mi cuarta carta, y eso nos lleva a 800 mazos, y luego lo ejecuto 800 veces más para obtener las ~ 20 semillas que producen mi quinta carta, y ahora solo genera esas veinte barajas de cartas y sé que tienes una de las veinte manos posibles. Además, tengo una muy buena idea de lo que voy a dibujar a continuación.
¿Ahora ves por qué es importante la aleatoriedad verdadera? La forma en que lo describe, cree que la distribución es importante, pero la distribución no es lo que hace que un proceso sea aleatorio. La imprevisibilidad es lo que hace que un proceso sea aleatorio.
ACTUALIZAR
Según los comentarios (ahora eliminados debido a su naturaleza poco constructiva), al menos el 0.3% de las personas que han leído esto están confundidos en cuanto a mi punto. Cuando las personas argumentan en contra de puntos que no he hecho, o peor, argumentan por puntos que sí hice suponiendo que no los hice, entonces sé que necesito explicar más clara y cuidadosamente.
Parece haber una confusión particular en torno a la distribución de palabras, por lo que quiero mencionar los usos con cuidado.
Las preguntas a la mano son:
- ¿Cómo difieren los números pseudoaleatorios y los números verdaderamente aleatorios?
- ¿Por qué es importante la diferencia?
- ¿Las diferencias tienen algo que ver con la distribución de la salida del PRNG?
Comencemos considerando la manera perfecta de generar una baraja de cartas al azar con la que jugar al póker. Luego veremos cómo otras técnicas para generar mazos son diferentes, y si es posible aprovechar esa diferencia.
Comencemos suponiendo que tenemos una caja mágica etiquetada TRNG
. Como su entrada le damos un número entero n mayor o igual a uno, y como su salida nos da un número verdaderamente aleatorio entre uno yn, inclusive. La salida de la caja es completamente impredecible (cuando se le da un número distinto de uno) y cualquier número entre uno yn es tan probable como otro; es decir que la distribución es uniforme . (Hay otras comprobaciones estadísticas más aleatorias de aleatoriedad que podríamos realizar; estoy ignorando este punto ya que no está relacionado con mi argumento. TRNG es perfectamente estadísticamente aleatorio por suposición).
Comenzamos con una baraja de cartas sin barajar. Pedimos a la caja de un número entre uno y 52 - es decir, TRNG(52)
. Independientemente del número que devuelva, contamos esas cartas de nuestro mazo ordenado y eliminamos esa carta. Se convierte en la primera carta del mazo barajado. Luego pedimos TRNG(51)
y hacemos lo mismo para seleccionar la segunda tarjeta, y así sucesivamente.
Otra forma de verlo es: ¡hay 52! = 52 x 51 x 50 ... x 2 x 1 posibles mazos, que es aproximadamente 2 226 . Hemos elegido uno de ellos al azar al azar.
Ahora repartimos las cartas. Cuando miro mis cartas, no tengo idea de qué cartas tienes. (Aparte del hecho obvio de que no tienes ninguna de las cartas que yo tengo.) Podrían ser cualquier carta, con la misma probabilidad.
Así que déjenme asegurarme de explicar esto claramente. Tenemos una distribución uniforme de cada salida individual de TRNG(n)
; cada uno escoge un número entre 1 yn con probabilidad 1 / n. ¡Además, el resultado de este proceso es que hemos elegido uno de 52! posibles mazos con una probabilidad de 1/52 !, por lo que la distribución sobre el conjunto de mazos posibles también es uniforme.
Todo bien.
Ahora supongamos que tenemos una caja menos mágica, etiquetada PRNG
. Antes de poder usarlo, debe sembrarse con un número sin signo de 32 bits.
Aparte: ¿Por qué 32 ? ¿No podría sembrarse con un número de 64 o 256 o 10000 bits? Seguro. Pero (1) en la práctica, la mayoría de los PRNG estándar se siembran con un número de 32 bits, y (2) si tiene 10000 bits de aleatoriedad para crear la semilla, ¿por qué está utilizando un PRNG? ¡Ya tienes una fuente de 10000 bits de aleatoriedad!
De todos modos, volvamos a cómo funciona el PRNG: después de sembrarlo, puede usarlo de la misma manera que lo hace TRNG
. Es decir, le pasa un número, n, y le devuelve un número entre 1 yn, inclusive. Además, la distribución de esa salida es más o menos uniforme . Es decir, cuando pedimos PRNG
un número entre 1 y 6, obtenemos 1, 2, 3, 4, 5 o 6 cada uno aproximadamente una sexta parte del tiempo, sin importar cuál sea la semilla.
Quiero enfatizar este punto varias veces porque parece ser el que confunde a ciertos comentaristas. La distribución del PRNG es uniforme en al menos dos formas. Primero, supongamos que elegimos cualquier semilla en particular. Esperaríamos que la secuencia PRNG(6), PRNG(6), PRNG(6)...
un millón de veces produjera una distribución uniforme de números entre 1 y 6. Y segundo, si elegimos un millón de semillas diferentes y solicitamos PRNG(6)
una vez para cada semilla, nuevamente esperaríamos una distribución uniforme de números del 1 al 6. La uniformidad del PRNG en cualquiera de estas operaciones no es relevante para el ataque que estoy describiendo .
Se dice que este proceso es pseudoaleatorio porque el comportamiento del cuadro es en realidad completamente determinista; elige entre uno de los 2 32 posibles comportamientos basados en la semilla. Es decir, una vez que se siembra, PRNG(6), PRNG(6), PRNG(6), ...
produce una secuencia de números con una distribución uniforme, pero esa secuencia está completamente determinada por la semilla. Para una secuencia de llamadas dada, por ejemplo, PRNG (52), PRNG (51) ... y así sucesivamente, solo hay 2 32 secuencias posibles. La semilla esencialmente elige cuál obtenemos.
Para generar un mazo, el servidor ahora genera una semilla. (¿Cómo? Volveremos a ese punto). Luego llaman PRNG(52)
, PRNG(51)
y así sucesivamente para generar el mazo, como antes.
Este sistema es susceptible al ataque que describí. Para atacar el servidor, primero, con anticipación, sembramos nuestra propia copia del cuadro con 0 y pedimos PRNG(52)
y escribimos eso. Luego volvemos a sembrar con 1, pedimos PRNG(52)
y escribimos eso, hasta 2 32 -1.
Ahora, el servidor de póker que usa PRNG para generar mazos tiene que generar una semilla de alguna manera. No importa cómo lo hagan. Podrían llamar TRNG(2^32)
para obtener una semilla verdaderamente aleatoria. O podrían tomar el tiempo actual como una semilla, que no es al azar en absoluto; Sé qué hora es tanto como tú. El punto de mi ataque es que no importa, porque tengo mi base de datos . Cuando veo mi primera carta, puedo eliminar el 98% de las posibles semillas. Cuando veo mi segunda carta, puedo eliminar un 98% más, y así sucesivamente, hasta que finalmente pueda llegar a un puñado de posibles semillas y saber con alta probabilidad lo que hay en su mano.
Ahora, nuevamente, quiero enfatizar que la suposición aquí es que si llamamos PRNG(6)
un millón de veces obtendríamos cada número aproximadamente una sexta parte del tiempo . Esa distribución es (más o menos) uniforme , y si lo único que le importa es la uniformidad de esa distribución , está bien. El punto de la pregunta era: ¿hay otras cosas aparte de esa distribución PRNG(6)
que nos interesan? Y la respuesta es sí . También nos preocupamos por la imprevisibilidad .
Otra forma de ver el problema es que, aunque la distribución de un millón de llamadas PRNG(6)
podría estar bien, porque el PRNG está eligiendo entre solo 2 32 posibles comportamientos, no puede generar todos los mazos posibles. Solo puede generar 2 32 de los 2 226 mazos posibles; Una pequeña fracción. Por lo tanto, la distribución en el conjunto de todos los mazos es muy mala. Pero nuevamente, el ataque fundamental aquí se basa en nuestra capacidad de predecir con éxito el comportamiento pasado y futuro de PRNG
una pequeña muestra de su producción.
Permítanme decir esto una tercera o cuatro veces para asegurarse de que esto se asimile. Hay tres distribuciones aquí. Primero, la distribución del proceso que produce la semilla aleatoria de 32 bits. Eso puede ser perfectamente aleatorio, impredecible y uniforme, y el ataque seguirá funcionando . En segundo lugar, la distribución de un millón de llamadas a PRNG(6)
. Eso puede ser perfectamente uniforme y el ataque seguirá funcionando. Tercero, la distribución de mazos elegidos por el proceso pseudoaleatorio que he descrito. Esa distribución es extremadamente pobre; solo se puede elegir una pequeña fracción de los posibles mazos IRL. El ataque depende de la previsibilidad del comportamiento del PRNG en función del conocimiento parcial de su salida .
A UN LADO: Este ataque requiere que el atacante sepa o pueda adivinar cuál es el algoritmo exacto utilizado por el PRNG. Si eso es realista o no es una pregunta abierta. Sin embargo, al diseñar un sistema de seguridad, debe diseñarlo para que sea seguro contra ataques, incluso si el atacante conoce todos los algoritmos del programa . Dicho de otra manera: la parte de un sistema de seguridad que debe permanecer en secreto para que el sistema sea seguro se llama "clave". Si su sistema depende de su seguridad en los algoritmos que usa como secretos, entonces su clave contiene esos algoritmos . ¡Esa es una posición extremadamente débil para estar!
Hacia adelante.
Ahora supongamos que tenemos una tercera caja mágica etiquetada CPRNG
. Es una versión de fuerza criptográfica de PRNG
. Se necesita una semilla de 256 bits en lugar de una semilla de 32 bits. Comparte con PRNG
la propiedad que la semilla elige entre uno de los 2 256 posibles comportamientos. Y al igual que nuestras otras máquinas, tiene la propiedad de que una gran cantidad de llamadas CPRNG(n)
producen una distribución uniforme de resultados entre 1 yn: cada una ocurre 1 / n de las veces. ¿Podemos ejecutar nuestro ataque contra él?
Nuestro ataque original requiere que almacenemos 2 32 asignaciones de semillas a PRNG(52)
. Pero 2 256 es un número mucho mayor; es completamente inviable ejecutar CPRNG(52)
eso muchas veces y almacenar los resultados.
Pero supongamos que hay alguna otra forma de tomar el valor CPRNG(52)
y de eso deducir un hecho acerca de la semilla. Hasta ahora hemos sido bastante tontos, forzando a todas las combinaciones posibles. ¿Podemos mirar dentro de la caja mágica, descubrir cómo funciona y deducir datos sobre la semilla en función de la salida?
No. Los detalles son demasiado complicados de explicar, pero los CPRNG están inteligentemente diseñados para que no sea factible deducir ningún hecho útil sobre la semilla desde el primer resultado CPRNG(52)
o desde cualquier subconjunto del resultado, sin importar cuán grande sea .
Bien, ahora supongamos que el servidor está usando CPRNG
para generar mazos. Necesita una semilla de 256 bits. ¿Cómo elige esa semilla? Si elige cualquier valor que un atacante pueda predecir , de repente el ataque vuelve a ser viable . Si podemos determinar que de las 2 256 semillas posibles, es probable que solo cuatro mil millones de ellas sean elegidas por el servidor, entonces estamos de vuelta en el negocio . Podemos montar este ataque nuevamente, solo prestando atención a la pequeña cantidad de semillas que posiblemente se pueden generar.
Por lo tanto, el servidor debe trabajar para garantizar que el número de 256 bits se distribuya de manera uniforme , es decir, cada semilla posible se elige con una probabilidad de 1/2 256 . Básicamente, el servidor debería llamar TRNG(2^256)-1
para generar la semilla CPRNG
.
¿Qué sucede si puedo hackear el servidor y mirarlo para ver qué semilla se eligió? En ese caso, el atacante conoce el pasado y el futuro completos del CPRNG . ¡El autor del servidor debe protegerse contra este ataque! (Por supuesto, si puedo montar con éxito este ataque, entonces probablemente también pueda transferir el dinero directamente a mi cuenta bancaria, así que tal vez eso no sea tan interesante. El punto es: la semilla debe ser un secreto difícil de adivinar, y un un número verdaderamente aleatorio de 256 bits es bastante difícil de adivinar).
Volviendo a mi punto anterior sobre la defensa en profundidad: la semilla de 256 bits es la clave de este sistema de seguridad. La idea de un CPRNG es que el sistema es seguro siempre que la clave sea segura ; incluso si se conoce cualquier otro hecho sobre el algoritmo, siempre que pueda mantener la clave secreta, las cartas del oponente son impredecibles.
OK, entonces la semilla debe ser secreta y distribuida uniformemente porque si no es así, podemos lanzar un ataque. Suponemos que la distribución de las salidas de CPRNG(n)
es uniforme. ¿Qué pasa con la distribución sobre el conjunto de todas las cubiertas posibles?
Podría decir: el CPRNG emite 2 256 secuencias posibles, pero solo hay 2 226 mazos posibles. Por lo tanto, hay más secuencias posibles que mazos, así que estamos bien; ahora es posible (con alta probabilidad) cada mazo de IRL posible en este sistema. Y ese es un buen argumento, excepto ...
2 226 es solo una aproximación de 52 !. Divídelo. 2 256/52 ! no puede ser un número entero porque, por un lado, ¡52! es divisible por 3 pero no hay potencia de dos Como este no es un número entero ahora, tenemos la situación de que todas las cubiertas son posibles , pero algunas cubiertas son más probables que otras .
Si eso no está claro, considere la situación con números más pequeños. Supongamos que tenemos tres cartas, A, B y C. Supongamos que usamos un PRNG con una semilla de 8 bits, por lo que hay 256 semillas posibles. Hay 256 salidas posibles PRNG(3)
dependiendo de la semilla; no hay forma de que un tercio de ellos sea A, un tercio de ellos sea B y un tercio de ellos sea C porque 256 no es divisible de manera equitativa entre 3. Tiene que haber un pequeño sesgo hacia uno de ellos.
Del mismo modo, 52 no se divide de manera equitativa en 2 256 , por lo que debe haber algún sesgo hacia algunas cartas como la primera carta elegida y un sesgo lejos de otras.
En nuestro sistema original con una semilla de 32 bits hubo un sesgo masivo y la gran mayoría de los mazos posibles nunca se produjeron. En este sistema, se pueden producir todas las cubiertas, pero la distribución de las cubiertas sigue siendo defectuosa . Algunas cubiertas son muy ligeramente más probables que otras.
Ahora la pregunta es: ¿tenemos un ataque basado en este defecto? y la respuesta está en la práctica, probablemente no . Los CPRNG están diseñados para que, si la semilla es verdaderamente aleatoria , no sea factible computarizar diferenciar entre CPRNG
y TRNG
.
OK, así que resumámoslo.
¿Cómo difieren los números pseudoaleatorios y los números verdaderamente aleatorios?
Difieren en el nivel de previsibilidad que exhiben.
- Los números verdaderamente aleatorios no son predecibles.
- Todos los números pseudoaleatorios son predecibles si la semilla se puede determinar o adivinar.
¿Por qué es importante la diferencia?
Porque hay aplicaciones donde la seguridad del sistema depende de la imprevisibilidad .
- Si se usa un TRNG para elegir cada tarjeta, entonces el sistema es inatacable.
- Si se utiliza un CPRNG para elegir cada tarjeta, entonces el sistema es seguro si la semilla es impredecible y desconocida.
- Si se utiliza un PRNG ordinario con un pequeño espacio inicial, entonces el sistema no es seguro independientemente de si la semilla es impredecible o desconocida; un espacio de semillas lo suficientemente pequeño es susceptible a los ataques de fuerza bruta del tipo que he descrito.
¿La diferencia tiene algo que ver con la distribución de la salida del PRNG?
La uniformidad de la distribución o la falta del mismo para llamadas individuales a RNG(n)
no es relevante para los ataques que he descrito.
Como hemos visto, tanto a PRNG
como CPRNG
producen distribuciones pobres de la probabilidad de elegir cualquier mazo individual de todos los mazos posibles. El PRNG
es considerablemente peor, pero ambos tienen problemas.
Una pregunta más:
Si TRNG es mucho mejor que CPRNG, que a su vez es mucho mejor que PRNG, ¿por qué alguien usa CPRNG o PRNG?
Dos razones.
Primero: gastos. TRNG es caro . Generar números verdaderamente aleatorios es difícil. Los CPRNG dan buenos resultados para muchas llamadas arbitrarias con solo una llamada a TRNG para la semilla. La desventaja es que debes mantener esa semilla en secreto .
Segundo: a veces queremos previsibilidad y lo único que nos importa es una buena distribución. Si está generando datos "aleatorios" como entradas de programa para un conjunto de pruebas, y muestra un error, entonces sería bueno que ejecutar el conjunto de pruebas nuevamente produzca el error.
Espero que ahora esté mucho más claro.
Finalmente, si disfrutaste esto, entonces podrías disfrutar de una lectura adicional sobre el tema de la aleatoriedad y las permutaciones: