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 usando std::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:
bool acquire_context(HCRYPTPROV *ctx)
{
if (!CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, 0)) {
return CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET);
}
return true;
}
size_t sysrandom(void* dst, size_t dstlen)
{
HCRYPTPROV ctx;
if (!acquire_context(&ctx)) {
throw std::runtime_error("Unable to initialize Win32 crypt library.");
}
BYTE* buffer = reinterpret_cast<BYTE*>(dst);
if(!CryptGenRandom(ctx, dstlen, buffer)) {
throw std::runtime_error("Unable to generate random bytes.");
}
if (!CryptReleaseContext(ctx, 0)) {
throw std::runtime_error("Unable to release Win32 crypt library.");
}
return dstlen;
}
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).
size_t sysrandom(void* dst, size_t dstlen)
{
char* buffer = reinterpret_cast<char*>(dst);
std::ifstream stream("/dev/urandom", std::ios_base::binary | std::ios_base::in);
stream.read(buffer, dstlen);
return dstlen;
}
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).
std::uint_least32_t seed;
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);
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 para PROV_RSA_FULL
. Lo único que faltaría sería verificar el contexto criptográfico, lo que se puede hacer CRYPT_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 Linux getrandom
está disponible y, si no, recurre a /dev/urandom
.
#if defined(__linux__) || defined(linux) || defined(__linux)
# // Check the kernel version. `getrandom` is only Linux 3.17 and above.
# include <linux/version.h>
# if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
# define HAVE_GETRANDOM
# endif
#endif
// also requires glibc 2.25 for the libc wrapper
#if defined(HAVE_GETRANDOM)
# include <sys/syscall.h>
# include <linux/random.h>
size_t sysrandom(void* dst, size_t dstlen)
{
int bytes = syscall(SYS_getrandom, dst, dstlen, 0);
if (bytes != dstlen) {
throw std::runtime_error("Unable to read N bytes from CSPRNG.");
}
return dstlen;
}
#elif defined(_WIN32)
// Windows sysrandom here.
#else
// POSIX sysrandom here.
#endif
OpenBSD
Hay una advertencia final: el OpenBSD moderno no tiene /dev/urandom
. Deberías usar getentropy en su lugar.
#if defined(__OpenBSD__)
# define HAVE_GETENTROPY
#endif
#if defined(HAVE_GETENTROPY)
# include <unistd.h>
size_t sysrandom(void* dst, size_t dstlen)
{
int bytes = getentropy(dst, dstlen);
if (bytes != dstlen) {
throw std::runtime_error("Unable to read N bytes from CSPRNG.");
}
return dstlen;
}
#endif
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
y FILE
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:
size_t sysrandom(void* dst, size_t dstlen)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
throw std::runtime_error("Unable to open /dev/urandom.");
}
if (read(fd, dst, dstlen) != dstlen) {
close(fd);
throw std::runtime_error("Unable to read N bytes from CSPRNG.");
}
close(fd);
return dstlen;
}
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
.
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>()
)