Parece que vejo muitas respostas em que alguém sugere o uso <random>
para gerar números aleatórios, geralmente junto com códigos como este:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
Normalmente, isso substitui algum tipo de "abominação profana", como:
srand(time(NULL));
rand()%6;
Podemos criticar a maneira antiga argumentando que time(NULL)
fornece baixa entropia, time(NULL)
é previsível e o resultado final não é uniforme.
Mas tudo isso é verdade no novo jeito: ele apenas tem um verniz mais brilhante.
rd()
retorna um únicounsigned int
. Isso tem pelo menos 16 bits e provavelmente 32. Isso não é suficiente para propagar os 19937 bits de estado do MT.Usando
std::mt19937 gen(rd());gen()
(semeando com 32 bits e olhando para a primeira saída) não dá uma boa distribuição de saída. 7 e 13 nunca podem ser a primeira saída. Duas sementes produzem 0. Doze sementes produzem 1226181350. ( Link )std::random_device
pode ser, e às vezes é, implementado como um PRNG simples com uma semente fixa. Portanto, pode produzir a mesma sequência em cada execução. ( Link ) Isso é ainda pior do quetime(NULL)
.
Pior ainda, é muito fácil copiar e colar os trechos de código anteriores, apesar dos problemas que eles contêm. Algumas soluções para isso requerem a aquisição de bibliotecas maiores que podem não ser adequadas para todos.
Diante disso, minha pergunta é: Como alguém pode semear de forma sucinta, portátil e completa o PRNG mt19937 em C ++?
Dadas as questões acima, uma boa resposta:
- Deve semear totalmente o mt19937 / mt19937_64.
- Não pode contar apenas com
std::random_device
outime(NULL)
como fonte de entropia. - Não deve contar com Boost ou outras bibliotecas.
- Deve caber em um pequeno número de linhas, de forma que fique bem copiado e colado em uma resposta.
Pensamentos
Meu pensamento atual é que as saídas de
std::random_device
podem ser combinadas (talvez via XOR) comtime(NULL)
valores derivados da randomização do espaço de endereço e uma constante codificada (que pode ser definida durante a distribuição) para obter uma melhor chance de entropia.std::random_device::entropy()
não dá uma boa indicação do questd::random_device
pode ou não fazer.
std::random_device
,time(NULL)
e endereços de função, e então XOR juntos para produzir uma espécie de fonte de entropia de melhor esforço.std::random_device
adequadamente nas plataformas em que você está planejando executar seu programa e fornecer uma função auxiliar que cria um gerador propagado (seed11::make_seeded<std::mt19937>()
)Respostas:
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 usandostd::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: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).
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).
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 paraPROV_RSA_FULL
. Falta apenas verificar o contexto criptográfico, o que pode ser feito comCRYPT_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 Linuxgetrandom
está disponível e, caso não esteja, retorna ao/dev/urandom
.OpenBSD
Há uma advertência final: o OpenBSD moderno não tem
/dev/urandom
. Você deve usar getentropy em vez disso.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
eFILE
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: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
.fonte
/dev/random
seria a melhor escolha para semear um RNG, mas aparentemente/dev/urandom
ainda é considerado computacionalmente seguro mesmo quando/dev/random
bloquearia por causa da baixa entropia disponível, entãourandom
é a escolha recomendada para tudo, exceto talvez blocos únicos. Consulte também unix.stackexchange.com/questions/324209/… . Nourandom
entanto, tome cuidado com as sementes previsíveis logo após a inicialização.getrandom(2)
chamada de sistema do Linux é como abrir e ler/dev/urandom
, exceto que irá bloquear se as fontes de aleatoriedade do kernel ainda não foram inicializadas. Acho que isso o salva do problema de aleatoriedade de baixa qualidade da inicialização inicial sem bloquear em outros casos, como/dev/random
faz./dev/urandom
geralmente funcionam. A discussão da lista de e-mails do Python sobre isso é algo que geralmente eu assino: bugs.python.org/issue27266Em certo sentido, isso não pode ser feito de forma portátil. Ou seja, pode-se conceber uma plataforma totalmente determinística válida rodando C ++ (digamos, um simulador que acelera o relógio da máquina deterministicamente, e com E / S "determinada") na qual não há fonte de aleatoriedade para semear um PRNG.
fonte
std::random_device
estivesse nessa categoria, mas aparentemente não é se algumas implementações reais usam um PRNG de semente fixa! Isso vai muito além do argumento de einpoklum.Você pode usar um
std::seed_seq
e preenchê-lo até pelo menos o tamanho de estado necessário para o gerador usando o método de Alexander Huszagh para obter a entropia:Se houvesse uma maneira adequada de preencher ou criar um SeedSequence a partir de um UniformRandomBitGenerator na biblioteca padrão, o uso
std::random_device
para propagação correta seria muito mais simples.fonte
A implementação na qual estou trabalhando aproveita a
state_size
propriedade domt19937
PRNG para decidir quantas sementes fornecer na inicialização:Eu acho que há espaço para melhorias porque
std::random_device::result_type
podem ser diferentesstd::mt19937::result_type
em tamanho e alcance, então isso realmente deve ser levado em consideração.Uma nota sobre std :: random_device .
De acordo com o
C++11(/14/17)
(s) padrão (ões):Isso significa que a implementação só pode gerar valores determinísticos se for impedida de gerar valores não determinísticos por alguma limitação.
O
MinGW
compiladorWindows
notoriamente não fornece valores não determinísticos de seustd::random_device
, apesar de estarem facilmente disponíveis no sistema operacional. Portanto, considero isso um bug e provavelmente não uma ocorrência comum em implementações e plataformas.fonte
std::random_device
e, portanto, é vulnerável a problemas decorrentes dele.std::random_device
? Eu sei que o padrão permite umPRNG
retrocesso, mas sinto que é apenas para se proteger, pois é difícil exigir que cada dispositivo que usaC++
tenha uma fonte aleatória não determinística. E se não o fizerem, o que você poderia fazer a respeito?std::random_device
. Eu acredito que esse é o espírito do padrão. Então, eu procurei e só consigo encontrarMinGW
que está quebrado nesse aspecto. Ninguém parece estar relatando esse problema com qualquer outra coisa que eu encontrei. Portanto, em minha biblioteca, simplesmente marqueiMinGW
como não compatível. Se houvesse um problema mais amplo, eu o repensaria. Eu simplesmente não vejo a evidência disso agora.std::random_device
para todos ao torná-lo disponível em um formato que não oferece os recursos de aleatoriedade da plataforma. Implementações de baixa qualidade anulam o propósito da API existente. Seria melhor IMO se eles simplesmente não implementassem até que eles estivessem funcionando. (Ou melhor, se a API fornecesse uma maneira de solicitar falha se a aleatoriedade de alta qualidade não estivesse disponível, então o MinGW poderia evitar causar riscos de segurança enquanto ainda fornecia sementes diferentes para jogos ou qualquer outra coisa.)Não há nada de errado em semear usando o tempo, supondo que você não precise dele para ser seguro (e você não disse que isso era necessário). A ideia é que você pode usar o hash para corrigir a não aleatoriedade. Descobri que isso funciona adequadamente em todos os casos, incluindo e em particular para simulações pesadas de Monte Carlo.
Um bom recurso dessa abordagem é que ela generaliza para a inicialização a partir de outros conjuntos de sementes não realmente aleatórios. Por exemplo, se você deseja que cada thread tenha seu próprio RNG (para threadsafety), você pode apenas inicializar com base no ID do thread em hash.
O seguinte é um SSCCE , destilado da minha base de código (para simplificar; algumas estruturas de suporte OO eliminadas):
fonte
1
e2
e observe que a sequência de flutuações gerada por eles leva um tempo para realmente divergir).Aqui está minha própria tentativa de resolver a questão:
A ideia aqui é usar XOR para combinar muitas fontes potenciais de entropia (tempo rápido, tempo lento,
std::random-device
locais de variáveis estáticas, locais de heap, locais de função, locais de biblioteca, valores específicos do programa) para fazer um melhor esforço na tentativa de inicializar o mt19937. Contanto que pelo menos uma vez a fonte seja "boa", o resultado será pelo menos "bom".Essa resposta não é tão curta quanto seria preferível e pode conter um ou mais erros de lógica. Portanto, estou considerando um trabalho em andamento. Por favor, comente se você tiver feedback.
fonte
&i ^ &myseed
deveria ter consideravelmente menos entropia do que qualquer um deles sozinho, uma vez que ambos são objetos com duração de armazenamento estático na mesma unidade de tradução e, portanto, provavelmente estão bem próximos. E você não parece realmente usar o valor especial da inicialização demyseed
?^
é um combinador de hash horrível; se dois valores tiverem muita entropia, mas pouco comparados um ao outro, ele a remove.+
geralmente é melhor (já que x + x queima apenas 1 bit de entropia em x, enquanto x ^ x queima todos eles). A função não é segura, suspeito (rd()
)+
isso quero dizer sem sinal (+
com sinalizado é UB-isca). Embora esses sejam casos UB um tanto ridículos, você disse portátil. Considere também obter o endereço de uma função como um valor integral, se possível (incerto se for?)/dev/urandom
ou/dev/random
).Eles estão disponíveis em sistemas modernos do tipo UNIX, como Linux, Solaris e OpenBSD.
fonte
Uma determinada plataforma pode ter uma fonte de entropia, como
/dev/random
. Nanossegundos desde a época comstd::chrono::high_resolution_clock::now()
é provavelmente a melhor semente na Biblioteca Padrão.Eu já usei algo como
(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )
obter mais bits de entropia para aplicativos que não são críticos para a segurança.fonte
/dev/urandom
, especialmente em um caso como este./dev/random
blocos, e muitas vezes sem boas razões para fazê-lo ([insira uma longa explicação sobre quantos sistemas operacionais diferentes estimam a aleatoriedade dos bytes produzidos por / dev / random])./dev/urandom
não existiam, e a alternativa ao bloqueio era o determinismo. Uma caixa pode ter/dev/hwrng
ou/dev/hw_random
também, o que deve ser ainda melhor./dev/random
" e isso parece ter desencadeado uma guerra santa sobre o/dev/random
contra/dev/urandom
no Linux que eu não pretendia quando dei aquele exemplo ..