Por que alguém usaria o gerador de número aleatório "padrão" de System.Random em vez de sempre usar o gerador de número aleatório criptograficamente seguro de System.Security.Cryptography.RandomNumberGenerator (ou suas subclasses porque RandomNumberGenerator é abstrato)?
Nate Lawson nos diz em sua apresentação do Google Tech Talk " Crypto Strikes Back " no minuto 13:11 para não usar os geradores de número aleatório "padrão" de Python, Java e C # e, em vez disso, usar a versão criptograficamente segura.
Eu sei a diferença entre as duas versões de geradores de números aleatórios (consulte a pergunta 101337 ).
Mas que razão existe para nem sempre usar o gerador de números aleatórios seguro? Por que usar System.Random? Desempenho, talvez?
c#
.net
cryptography
random
Lernkurve
fonte
fonte
using R = System.Security.Cryptography.RandomNumberGenerator; R.Create();
Respostas:
Velocidade e intenção. Se você está gerando um número aleatório e não precisa de segurança, por que usar uma função de criptografia lenta? Você não precisa de segurança, então por que fazer outra pessoa pensar que o número pode ser usado para algo seguro quando não será?
fonte
Random
. Deixe-me adivinhar: você criou uma nova instância daRandom
classe para cada número, que, como é propagada por um temporizador aproximado, será propagada com o mesmo valor por um intervalo de cerca de 1-16 ms.Random
que faz com que ele retorne todos os 0's quando o mesmo objeto é usado por vários threads.Além da velocidade e da interface mais útil (
NextDouble()
etc), também é possível fazer uma sequência aleatória repetível usando um valor de semente fixo. Isso é bastante útil, entre outros, durante o teste.Random gen1 = new Random(); // auto seeded by the clock Random gen2 = new Random(0); // Next(10) always yields 7,8,7,5,2,....
fonte
Em primeiro lugar, a apresentação que você vinculou fala apenas sobre números aleatórios para fins de segurança. Portanto, ele não afirma
Random
ser ruim para fins não relacionados à segurança.Mas eu afirmo que sim. A implementação do .net 4
Random
apresenta falhas de várias maneiras. Eu recomendo usá-lo apenas se você não se importar com a qualidade de seus números aleatórios. Eu recomendo usar melhores implementações de terceiros.Falha 1: a semeadura
O construtor padrão é propagado com a hora atual. Assim, todas as instâncias
Random
criadas com o construtor padrão em um curto período de tempo (ca. 10ms) retornam a mesma sequência. Isso é documentado e "por design". Isso é particularmente irritante se você quiser multi-thread seu código, já que você não pode simplesmente criar uma instância deRandom
no início de cada execução de thread.A solução alternativa é ser extremamente cuidadoso ao usar o construtor padrão e propagar manualmente quando necessário.
Outro problema aqui é que o espaço da semente é bastante pequeno (31 bits). Portanto, se você gerar 50k instâncias de
Random
com sementes perfeitamente aleatórias, provavelmente obterá uma sequência de números aleatórios duas vezes (devido ao paradoxo do aniversário ). Portanto, a semeadura manual também não é fácil de acertar.Falha 2: A distribuição de números aleatórios retornados por
Next(int maxValue)
é tendenciosaExistem parâmetros para os quais
Next(int maxValue)
claramente não são uniformes. Por exemplo, se você calcularr.Next(1431655765) % 2
, obterá0
cerca de 2/3 das amostras. (Amostra de código no final da resposta.)Falha 3: o
NextBytes()
método é ineficiente.O custo por byte de
NextBytes()
é quase tão grande quanto o custo para gerar uma amostra inteira inteira comNext()
. A partir disso, suspeito que eles realmente criam uma amostra por byte.Uma implementação melhor usando 3 bytes de cada amostra seria acelerada
NextBytes()
em quase um fator 3.Graças a esta falha,
Random.NextBytes()
é apenas cerca de 25% mais rápido do queSystem.Security.Cryptography.RNGCryptoServiceProvider.GetBytes
na minha máquina (Win7, Core i3 2600MHz).Tenho certeza de que se alguém inspecionasse o código de byte de origem / descompilado, encontraria ainda mais falhas do que eu encontrei com minha análise de caixa preta.
Amostras de código
r.Next(0x55555555) % 2
é fortemente tendencioso:Random r = new Random(); const int mod = 2; int[] hist = new int[mod]; for(int i = 0; i < 10000000; i++) { int num = r.Next(0x55555555); int num2 = num % 2; hist[num2]++; } for(int i=0;i<mod;i++) Console.WriteLine(hist[i]);
Atuação:
byte[] bytes=new byte[8*1024]; var cr=new System.Security.Cryptography.RNGCryptoServiceProvider(); Random r=new Random(); // Random.NextBytes for(int i=0;i<100000;i++) { r.NextBytes(bytes); } //One sample per byte for(int i=0;i<100000;i++) { for(int j=0;j<bytes.Length;j++) bytes[j]=(byte)r.Next(); } //One sample per 3 bytes for(int i=0;i<100000;i++) { for(int j=0;j+2<bytes.Length;j+=3) { int num=r.Next(); bytes[j+2]=(byte)(num>>16); bytes[j+1]=(byte)(num>>8); bytes[j]=(byte)num; } //Yes I know I'm not handling the last few bytes, but that won't have a noticeable impact on performance } //Crypto for(int i=0;i<100000;i++) { cr.GetBytes(bytes); }
fonte
Random
usa para transformar um inteiro de 31 bits em um número com o limite superior especificado. Esqueci os detalhes, mas é algo parecidorandomValue * max / 2^{31}
.Next()
, demonstrada por você aqui, é um bug bastante espetacular - e ainda está presente hoje, 6 anos após você ter escrito suas descobertas. (Eu digo "bug" em vez de apenas "falha", porque os documentos afirmam que "Números pseudo-aleatórios são escolhidos com probabilidade igual de um conjunto finito de números" . Não é assim, e seu código aqui prova isso.)System.Random tem muito mais desempenho, pois não gera números aleatórios criptograficamente seguros.
Um teste simples em minha máquina preenchendo um buffer de 4 bytes com dados aleatórios 1.000.000 vezes leva 49 ms para Random, mas 2.845 ms para RNGCryptoServiceProvider. Observe que, se você aumentar o tamanho do buffer que está enchendo, a diferença diminui, pois a sobrecarga para RNGCryptoServiceProvider é menos relevante.
fonte
Random
eRNGCryptoServiceProvider
não tenham mudado nos últimos 8 anos (o que, pelo que sei, podem ter mudado), já vi benchmarks completamente quebrados o suficiente usados no Stack Overflow para não confiar nos resultados de um benchmark cujo código não está publicamente disponível.As razões mais óbvias já foram mencionadas, então aqui está uma mais obscura: PRNGs criptográficos normalmente precisam ser continuamente propagados novamente com entropia "real". Assim, se você usar um CPRNG com muita frequência, poderá esgotar o pool de entropia do sistema, o que (dependendo da implementação do CPRNG) o enfraquecerá (permitindo que um invasor o preveja) ou bloqueará enquanto tenta preencher seu pool de entropia (tornando-se assim um vetor de ataque para um ataque DoS).
De qualquer forma, seu aplicativo agora se tornou um vetor de ataque para outros aplicativos totalmente não relacionados que - ao contrário do seu - na verdade dependem vitalmente das propriedades criptográficas do CPRNG.
Este é um problema real do mundo real, BTW, que foi observado em servidores headless (que naturalmente têm pools de entropia bastante pequenos porque não possuem fontes de entropia como entrada de mouse e teclado) executando Linux, onde os aplicativos usam incorretamente o
/dev/random
kernel CPRNG para todos os tipos de números aleatórios, enquanto o comportamento correto seria ler um pequeno valor de semente/dev/urandom
e usá-lo para semear seu próprio PRNG.fonte
Se estiver programando um jogo de cartas ou loteria online, você deve ter certeza de que a sequência é quase impossível de adivinhar. No entanto, se você estiver mostrando aos usuários, digamos, uma citação do dia, o desempenho é mais importante do que a segurança.
fonte
Isso foi discutido longamente, mas, em última análise, a questão do desempenho é uma consideração secundária ao selecionar um RNG. Há uma vasta gama de RNGs por aí, e o Lehmer LCG enlatado no qual a maioria dos RNGs de sistema consiste não é o melhor nem necessariamente o mais rápido. Em sistemas antigos e lentos, era um excelente compromisso. Esse compromisso raramente é realmente relevante nos dias de hoje. A coisa persiste nos sistemas atuais principalmente porque A) a coisa já está construída, e não há nenhuma razão real para 'reinventar a roda' neste caso, e B) para o que a grande maioria das pessoas a usará, é 'bom o bastante'.
Em última análise, a seleção de um RNG se resume à relação Risco / Recompensa. Em algumas aplicações, por exemplo em um videogame, não existe nenhum risco. Um Lehmer RNG é mais do que adequado e é pequeno, conciso, rápido, bem compreendido e "pronto para uso".
Se o aplicativo for, por exemplo, um jogo de pôquer on-line ou loteria onde há prêmios reais envolvidos e dinheiro real entra em jogo em algum ponto da equação, o Lehmer 'na caixa' não é mais adequado. Em uma versão de 32 bits, ele tem apenas 2 ^ 32 estados válidos possíveis antes de começar a circular, na melhor das hipóteses . Hoje em dia, essa é uma porta aberta para um ataque de força bruta. Em um caso como esse, o desenvolvedor vai querer ir para algo como um RNG de Período Muito Longo de algumas espécies e provavelmente semeá-lo de um provedor criptograficamente forte. Isso dá um bom compromisso entre velocidade e segurança. Nesse caso, a pessoa sairá procurando por algo como o Mersenne Twister ou um gerador recursivo múltiplo de algum tipo.
Se o aplicativo é algo como comunicar grandes quantidades de informações financeiras em uma rede, agora existe um risco enorme e supera qualquer recompensa possível. Ainda há carros blindados porque às vezes homens fortemente armados são a única segurança adequada e, acredite, se uma brigada de pessoal de operações especiais com tanques, caças e helicópteros fosse financeiramente viável, esse seria o método escolhido. Em um caso como este, usar um RNG criptograficamente forte faz sentido, porque qualquer nível de segurança que você pode obter, não é o tanto que você deseja. Portanto, você pegará o máximo que puder encontrar e o custo é uma questão secundária muito, muito remota, seja em tempo ou dinheiro. E se isso significa que cada sequência aleatória leva 3 segundos para ser gerada em um computador muito poderoso, você vai esperar os 3 segundos,
fonte
Observe que a classe System.Random em C # está codificada incorretamente, portanto, deve ser evitada.
https://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug#tabs
fonte
Nem todo mundo precisa de números aleatórios criptograficamente seguros e podem se beneficiar mais de uma impressão simples mais rápida. Talvez o mais importante seja que você pode controlar a sequência de números System.Random.
Em uma simulação utilizando números aleatórios que você pode querer recriar, você executa novamente a simulação com a mesma semente. Pode ser útil para rastrear bugs quando você deseja regenerar um dado cenário com falha também - executando seu programa com a mesma sequência exata de números aleatórios que travou o programa.
fonte
Se eu não precisar da segurança, ou seja, só quero um valor relativamente indeterminado, não um criptograficamente forte, o Random tem uma interface muito mais fácil de usar.
fonte
Diferentes necessidades exigem diferentes RNGs. Para criptografia, você deseja que seus números aleatórios sejam o mais aleatórios possível. Para simulações de Monte Carlo, você deseja que preencham o espaço uniformemente e sejam capazes de iniciar o RNG a partir de um estado conhecido.
fonte
Random
não é um gerador de números aleatórios, é um gerador de sequência pseudo-aleatória determinística, que leva seu nome por razões históricas.A razão para usar
System.Random
é se você deseja essas propriedades, ou seja, uma sequência determinística, que é garantida para produzir a mesma sequência de resultados quando inicializada com a mesma semente.Se você quiser melhorar a "aleatoriedade" sem sacrificar a interface, pode herdar da
System.Random
substituição de vários métodos.Por que você quer uma sequência determinística
Uma razão para ter uma sequência determinística em vez da verdadeira aleatoriedade é porque ela pode ser repetida.
Por exemplo, se você estiver executando uma simulação numérica, pode inicializar a sequência com um número aleatório (verdadeiro) e registrar o número usado .
Então, se você deseja repetir exatamente a mesma simulação, por exemplo, para fins de depuração, você pode fazer isso inicializando a sequência com o valor registrado .
Por que você iria querer esta sequência particular, não muito boa,
A única razão que consigo pensar seria a compatibilidade com versões anteriores do código existente que usa essa classe.
Resumindo, se você quiser melhorar a sequência sem alterar o resto do código, vá em frente.
fonte
Eu escrevi um jogo (Crystal Sliders no iPhone: Aqui ) que colocaria uma série "aleatória" de joias (imagens) no mapa e você giraria o mapa como queria e as selecionaria e elas iriam embora. - Semelhante ao Bejeweled. Eu estava usando Random (), e foi semeado com o número de tiques de 100 ns desde que o telefone foi inicializado, uma semente bastante aleatória.
Achei incrível que ele gerasse jogos quase idênticos entre si - das 90 ou mais joias, de 2 cores, eu obteria duas EXATAMENTE iguais, exceto por 1 a 3 joias! Se você lançar 90 moedas e obter o mesmo padrão, exceto por 1-3, isso é MUITO improvável! Tenho várias capturas de tela que mostram o mesmo. Fiquei chocado com o quão ruim System.Random () era! Eu presumi que DEVERIA ter escrito algo terrivelmente errado em meu código e estava usando-o errado. Eu estava errado, era o gerador.
Como um experimento - e uma solução final, voltei ao gerador de números aleatórios que uso desde 1985 ou mais - que é VASTMENTE melhor. É mais rápido, tem um período de 1,3 * 10 ^ 154 (2 ^ 521) antes de se repetir. O algoritmo original foi propagado com um número de 16 bits, mas eu mudei para um número de 32 bits e melhorei a propagação inicial.
O original está aqui:
ftp://ftp.grnet.gr/pub/lang/algorithms/c/jpl-c/random.c
Com o passar dos anos, fiz todos os testes de números aleatórios em que pude pensar e superei todos eles. Não espero que tenha algum valor criptográfico, mas retorna um número tão rápido quanto "return * p ++;" até esgotar os 521 bits e, em seguida, executar um processo rápido sobre os bits para criar novos bits aleatórios.
Criei um wrapper C # - chamei-o de JPLRandom (), implementou a mesma interface que Random () e alterei todos os lugares onde o chamei no código.
A diferença era MUITO melhor - OMG, fiquei pasmo - não deveria haver nenhuma maneira que eu pudesse dizer apenas olhando para as telas de 90 ou mais joias em um padrão, mas fiz um lançamento de emergência do meu jogo seguindo isso.
E eu nunca usaria System.Random () para nada novamente. Estou CHOCADO que a versão deles tenha se surpreendido com algo que agora tem 30 anos!
Jogos -Traderhut
fonte
Random
muita frequência. Ele deve ser criado apenas uma vez, chamandoNext
essa instância várias vezes.Random
é ruim, mas não tão ruim assim. Você pode postar um programa de amostra junto com um par de sementes que exiba esse problema?