Eu argumentaria que a maior falha com std::random_device
é que é permitido um fallback determinístico se nenhum CSPRNG estiver disponível. Isso por si só é uma boa razão para não propagar um PRNG usando std::random_device
, uma vez que os bytes produzidos podem ser determinísticos. Infelizmente, não fornece uma API para descobrir quando isso acontece ou para solicitar falha em vez de números aleatórios de baixa qualidade.
Ou seja, não existe uma solução totalmente portátil : no entanto, existe uma abordagem decente e mínima. Você pode usar um envoltório mínimo em torno de um CSPRNG (definido como sysrandom
abaixo) para propagar o PRNG.
janelas
Você pode confiar em CryptGenRandom
um CSPRNG. Por exemplo, você pode usar o seguinte 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;
}
Unix-like
Em muitos sistemas semelhantes ao Unix, você deve usar / dev / urandom quando possível (embora não seja garantido que exista em sistemas compatíveis com 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;
}
De outros
Se nenhum CSPRNG estiver disponível, você pode optar por confiar em std::random_device
. No entanto, eu evitaria isso se possível, uma vez que vários compiladores (mais notavelmente, MinGW) o implementam como um PRNG (na verdade, produzindo a mesma sequência todas as vezes para alertar os humanos de que não é propriamente aleatório).
Semeando
Agora que temos nossas peças com overhead mínimo, podemos gerar os bits desejados de entropia aleatória para semear nosso PRNG. O exemplo usa 32 bits (obviamente insuficientes) para propagar o PRNG e você deve aumentar esse valor (que depende do seu CSPRNG).
std::uint_least32_t seed;
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);
Comparação para Boost
Podemos ver paralelos com boost :: random_device (um verdadeiro CSPRNG) após uma rápida olhada no código-fonte . O Boost usa MS_DEF_PROV
no Windows, que é o tipo de provedor para PROV_RSA_FULL
. Falta apenas verificar o contexto criptográfico, o que pode ser feito com CRYPT_VERIFYCONTEXT
. Em * Nix, Boost usa /dev/urandom
. Ou seja, esta solução é portátil, bem testada e fácil de usar.
Especialização Linux
Se você está disposto a sacrificar a sucinta pela segurança, getrandom
é uma excelente escolha no Linux 3.17 e superior e no Solaris recente. getrandom
se comporta de forma idêntica /dev/urandom
, exceto que bloqueia se o kernel ainda não inicializou seu CSPRNG após a inicialização. O snippet a seguir detecta se o Linux getrandom
está disponível e, caso não esteja, retorna ao /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
Há uma advertência final: o OpenBSD moderno não tem /dev/urandom
. Você deve usar getentropy em vez disso.
#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
outros pensamentos
Se você precisa de bytes aleatórios criptograficamente seguros, você provavelmente deve substituir o fstream pelo open / read / close sem buffer do POSIX. Isso ocorre porque basic_filebuf
e FILE
contém um buffer interno, que será alocado por meio de um alocador padrão (e, portanto, não apagado da memória).
Isso pode ser feito facilmente mudando sysrandom
para:
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;
}
obrigado
Agradecimentos especiais a Ben Voigt por apontar o FILE
uso de leituras em buffer e, portanto, não deve ser usado.
Eu também gostaria de agradecer a Peter Cordes por mencionar getrandom
, e a falta do OpenBSD /dev/urandom
.