Me parece ver muchas respuestas en las que alguien sugiere usar <random>
para generar números aleatorios, generalmente junto con un código como este:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
Por lo general, esto reemplaza algún tipo de "abominación impía" como:
srand(time(NULL));
rand()%6;
Podríamos criticar la forma antigua argumentando que time(NULL)
proporciona una entropía baja, time(NULL)
es predecible y el resultado final no es uniforme.
Pero todo eso es cierto en la nueva forma: simplemente tiene un barniz más brillante.
rd()
devuelve un singleunsigned int
. Esto tiene al menos 16 bits y probablemente 32. Eso no es suficiente para generar los 19937 bits de estado de MT.Usar
std::mt19937 gen(rd());gen()
(sembrar con 32 bits y mirar la primera salida) no da una buena distribución de salida. 7 y 13 nunca pueden ser la primera salida. Dos semillas producen 0. Doce semillas producen 1226181350. ( Enlace )std::random_device
puede implementarse, y a veces se implementa, como un PRNG simple con una semilla fija. Por lo tanto, podría producir la misma secuencia en cada ejecución. ( Enlace ) Esto es incluso peor quetime(NULL)
.
Peor aún, es muy fácil copiar y pegar los fragmentos de código anteriores, a pesar de los problemas que contienen. Algunas soluciones para esto requieren la adquisición de bibliotecas grandes que pueden no ser adecuadas para todos.
A la luz de esto, mi pregunta es ¿Cómo se puede sembrar de manera sucinta, portátil y completa el PRNG mt19937 en C ++?
Dados los problemas anteriores, una buena respuesta:
- Debe sembrar completamente el mt19937 / mt19937_64.
- No se puede confiar únicamente en
std::random_device
otime(NULL)
como fuente de entropía. - No debería confiar en Boost u otras bibliotecas.
- Debe caber en una pequeña cantidad de líneas para que se vea bien pegado en una respuesta.
Pensamientos
Mi pensamiento actual es que las salidas de
std::random_device
se pueden combinar (tal vez a través de XOR) contime(NULL)
valores derivados de la aleatorización del espacio de direcciones y una constante codificada (que podría establecerse durante la distribución) para obtener un mejor esfuerzo en la entropía.std::random_device::entropy()
no da una buena indicación de lo questd::random_device
podría o no hacer.
std::random_device
,time(NULL)
y direcciones de funciones, y luego XOR juntos para producir una especie de fuente de entropía de mejor esfuerzo.std::random_device
correctamente en las plataformas en las que planea ejecutar su programa y proporcionar una función auxiliar que crea un generador inicial (seed11::make_seeded<std::mt19937>()
)Respuestas:
Yo diría que el mayor defecto
std::random_device
es que se permite una alternativa determinista si no hay ningún CSPRNG disponible. Esto por sí solo es una buena razón para no sembrar un PRNG usandostd::random_device
, ya que los bytes producidos pueden ser deterministas. Desafortunadamente, no proporciona una API para averiguar cuándo sucede esto o para solicitar fallas en lugar de números aleatorios de baja calidad.Es decir, no existe una solución completamente portátil : sin embargo, existe un enfoque mínimo y decente. Puede usar una envoltura mínima alrededor de un CSPRNG (definido a
sysrandom
continuación) para sembrar el PRNG.Ventanas
Puede confiar en
CryptGenRandom
un CSPRNG. Por ejemplo, puede utilizar el siguiente código:Tipo Unix
En muchos sistemas similares a Unix, debe usar / dev / urandom cuando sea posible (aunque no se garantiza que exista en sistemas compatibles con POSIX).
Otro
Si no hay CSPRNG disponible, puede optar por confiar en él
std::random_device
. Sin embargo, evitaría esto si es posible, ya que varios compiladores (en particular, MinGW) lo implementan como un PRNG (de hecho, producen la misma secuencia cada vez para alertar a los humanos de que no es correctamente aleatorio).Siembra
Ahora que tenemos nuestras piezas con una sobrecarga mínima, podemos generar los bits deseados de entropía aleatoria para sembrar nuestro PRNG. El ejemplo usa 32 bits (obviamente insuficientes) para inicializar el PRNG, y debe aumentar este valor (que depende de su CSPRNG).
Comparación con Boost
Podemos ver paralelismos con boost :: random_device (un verdadero CSPRNG) después de un vistazo rápido al código fuente . Boost se usa
MS_DEF_PROV
en Windows, que es el tipo de proveedor paraPROV_RSA_FULL
. Lo único que faltaría sería verificar el contexto criptográfico, lo que se puede hacerCRYPT_VERIFYCONTEXT
. En * Nix, Boost usa/dev/urandom
. Es decir, esta solución es portátil, está bien probada y es fácil de usar.Especialización Linux
Si está dispuesto a sacrificar la concisión por la seguridad,
getrandom
es una excelente opción en Linux 3.17 y superior, y en Solaris reciente.getrandom
se comporta de manera idéntica a/dev/urandom
, excepto que se bloquea si el kernel no ha inicializado su CSPRNG aún después de arrancar. El siguiente fragmento detecta si Linuxgetrandom
está disponible y, si no, recurre a/dev/urandom
.OpenBSD
Hay una advertencia final: el OpenBSD moderno no tiene
/dev/urandom
. Deberías usar getentropy en su lugar.otros pensamientos
Si necesita bytes aleatorios criptográficamente seguros, probablemente debería reemplazar el fstream con abrir / leer / cerrar sin búfer de POSIX. Esto se debe a que ambos
basic_filebuf
yFILE
contienen un búfer interno, que se asignará a través de un asignador estándar (y, por lo tanto, no se borrará de la memoria).Esto se puede hacer fácilmente cambiando
sysrandom
a:Gracias
Un agradecimiento especial a Ben Voigt por señalar el
FILE
uso de lecturas almacenadas en búfer y, por lo tanto, no debe usarse.También me gustaría agradecer a Peter Cordes por mencionar
getrandom
y la falta de OpenBSD/dev/urandom
.fuente
/dev/random
que sería la mejor opción para sembrar un RNG, pero aparentemente/dev/urandom
todavía se considera computacionalmente seguro incluso cuando/dev/random
se bloquearía debido a la baja entropía disponible, por lo queurandom
es la opción recomendada para todo, excepto tal vez los pads únicos. Consulte también unix.stackexchange.com/questions/324209/… . Sinurandom
embargo, tenga cuidado con las semillas predecibles desde muy temprano después del arranque.getrandom(2)
llamada al sistema de Linux es como abrir y leer/dev/urandom
, excepto que se bloqueará si las fuentes de aleatoriedad del kernel aún no se han inicializado. Creo que esto te salva del problema de aleatoriedad de baja calidad de arranque temprano sin bloquear en otros casos como lo/dev/random
hace./dev/urandom
generalmente funciona. La discusión de la lista de correo de Python sobre esto es algo a lo que generalmente me suscribo: bugs.python.org/issue27266En cierto sentido, esto no se puede hacer de forma portátil. Es decir, se puede concebir una plataforma totalmente determinista válida que ejecute C ++ (digamos, un simulador que acelere el reloj de la máquina de forma determinista y con E / S "determinizadas") en la que no hay una fuente de aleatoriedad para sembrar un PRNG.
fuente
std::random_device
estuviera en esa categoría, pero aparentemente no lo es si algunas implementaciones reales usan un PRNG de semilla fija. Eso va mucho más allá del argumento de einpoklum.Puede usar un
std::seed_seq
y llenarlo al menos hasta el tamaño de estado requerido para el generador usando el método de Alexander Huszagh para obtener la entropía:Si hubiera una forma adecuada de completar o crear una SeedSequence a partir de un UniformRandomBitGenerator en la biblioteca estándar, usar
std::random_device
para sembrar correctamente sería mucho más simple.fuente
La implementación en la que estoy trabajando aprovecha la
state_size
propiedad delmt19937
PRNG para decidir cuántas semillas proporcionar en la inicialización:Creo que hay margen de mejora porque
std::random_device::result_type
podría diferirstd::mt19937::result_type
en tamaño y rango, por lo que realmente debería tenerse en cuenta.Una nota sobre std :: random_device .
Según la
C++11(/14/17)
(s) norma (s):Esto significa que la implementación solo puede generar valores deterministas si alguna limitación le impide generar valores no deterministas .
El
MinGW
compilador enWindows
famoso no proporciona valores no deterministas de sustd::random_device
, a pesar de que están fácilmente disponibles en el sistema operativo. Así que considero que esto es un error y no es probable que ocurra con frecuencia en todas las implementaciones y plataformas.fuente
std::random_device
y, por lo tanto, es vulnerable a los problemas que surgen de él.std::random_device
? Sé que el estándar permite unPRNG
retroceso, pero creo que es solo para cubrirse, ya que es difícil exigir que cada dispositivo que lo useC++
tenga una fuente aleatoria no determinista. Y si no es así, ¿qué podrías hacer al respecto de todos modos?std::random_device
. Creo que ese es el espíritu del estándar. Por eso he buscado y solo puedo encontrarMinGW
que está roto en este sentido. Nadie parece informar de este problema con cualquier otra cosa que haya encontrado. Entonces, en mi biblioteca, simplemente lo he marcadoMinGW
como no admitido. Si hubiera un problema más amplio, lo volvería a pensar. Simplemente no veo la evidencia de eso en este momento.std::random_device
a todos al ponerlo a disposición en una forma que no ofrece las capacidades de aleatoriedad de la plataforma. Las implementaciones de baja calidad frustran el propósito de la API existente. En mi opinión, sería mejor si simplemente no lo implementaran hasta que lo tengan funcionando. (O mejor, si la API proporciona una forma de solicitar fallas si la aleatoriedad de alta calidad no está disponible, por lo que MinGW podría evitar causar riesgos de seguridad al mismo tiempo que ofrece diferentes semillas para juegos o lo que sea)No hay nada de malo en sembrar usando el tiempo, asumiendo que no lo necesita para estar seguro (y no dijo que esto fuera necesario). La idea es que puede usar hash para corregir la no aleatoriedad. Encontré que esto funciona adecuadamente en todos los casos, incluidas y en particular para las simulaciones de Monte Carlo pesadas.
Una característica interesante de este enfoque es que se generaliza a la inicialización de otros conjuntos de semillas no realmente aleatorios. Por ejemplo, si desea que cada subproceso tenga su propio RNG (para la seguridad de subprocesos), puede inicializar en función del ID de subproceso con hash.
El siguiente es un SSCCE , destilado de mi base de código (por simplicidad; algunas estructuras de soporte OO elididas):
fuente
1
y2
y observe que la secuencia de flotadores generados por ellos tarda un tiempo en divergir realmente).Aquí está mi propia puñalada a la pregunta:
La idea aquí es usar XOR para combinar muchas fuentes potenciales de entropía (tiempo rápido, tiempo lento,
std::random-device
ubicaciones de variables estáticas, ubicaciones de montones, ubicaciones de funciones, ubicaciones de bibliotecas, valores específicos del programa) para hacer el mejor esfuerzo para inicializar el mt19937. Siempre que al menos una vez la fuente sea "buena", el resultado será al menos así de "bueno".Esta respuesta no es tan corta como sería preferible y puede contener uno o más errores de lógica. Así que lo considero un trabajo en progreso. Por favor comente si tiene comentarios.
fuente
&i ^ &myseed
debería tener considerablemente menos entropía que cualquiera de los dos, ya que ambos son objetos con una duración de almacenamiento estático en la misma unidad de traducción y, por lo tanto, es probable que estén bastante juntos. ¿Y no parece que realmente uses el valor especial de la inicialización demyseed
?^
es un combinador de hash horrible; si dos valores tienen mucha entropía, pero poca comparados entre sí, los elimina.+
suele ser mejor (ya que x + x solo quema 1 bit de entropía en x, mientras que x ^ x los quema todos). La función no es segura, sospecho (rd()
)+
me refiero a sin firmar (+
en firmado es UB-bait). Si bien estos son casos UB algo ridículos, dijiste portátiles. También considere obtener la dirección de una función como un valor integral si es posible (¿no se sabe si lo es?)/dev/urandom
o/dev/random
).Están disponibles en sistemas modernos similares a UNIX, como Linux, Solaris y OpenBSD.
fuente
Una plataforma determinada puede tener una fuente de entropía, como
/dev/random
. Nanosegundos desde la Época constd::chrono::high_resolution_clock::now()
es probablemente la mejor semilla en la Biblioteca Estándar.Anteriormente he usado algo como
(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )
para obtener más bits de entropía para aplicaciones que no son críticas para la seguridad.fuente
/dev/urandom
, especialmente en un caso como este./dev/random
bloques, y a menudo sin buenas razones para hacerlo ([inserte una explicación larga sobre cuántos sistemas operativos diferentes estiman la aleatoriedad de los bytes producidos por / dev / random])./dev/urandom
no existía, y la alternativa al bloqueo fue el determinismo. Una caja podría tener/dev/hwrng
o/dev/hw_random
también, que debería ser incluso mejor./dev/random
", y eso parece haber provocado una guerra santa sobre/dev/random
versus/dev/urandom
en Linux que no tenía la intención cuando di ese ejemplo ..