Minha equipe entregou algum código do lado do servidor (em Java) que gera tokens aleatórios e eu tenho uma pergunta sobre o mesmo -
O objetivo desses tokens é bastante sensível - usado para identificação de sessão, links de redefinição de senha, etc. Portanto, eles precisam ser criptograficamente aleatórios para evitar que alguém os adivinhe ou os force brutalmente. O token é um "longo" e, portanto, tem 64 bits.
O código atualmente usa a java.util.Random
classe para gerar esses tokens. A documentação para java.util.Random
afirma claramente o seguinte:
Instâncias de java.util.Random não são criptograficamente seguras. Em vez disso, considere usar o SecureRandom para obter um gerador de números pseudo-aleatórios criptograficamente seguro para uso por aplicativos sensíveis à segurança.
No entanto, a maneira como o código está usando atualmente java.util.Random
é o seguinte: ele instancia a java.security.SecureRandom
classe e, em seguida, usa o SecureRandom.nextLong()
método para obter a semente que é usada para instanciar a java.util.Random
classe. Em seguida, ele usa o java.util.Random.nextLong()
método para gerar o token.
Então, minha pergunta agora - ainda é insegura, dado que o java.util.Random
está sendo semeado usando java.security.SecureRandom
? Preciso modificar o código para que ele use java.security.SecureRandom
exclusivamente para gerar os tokens?
Atualmente, a semente do código é a Random
única vez na inicialização
fonte
Random
única vez na inicialização ou semeia um novo para cada token? Felizmente, esta é uma pergunta estúpida, mas pensei em verificar.long
ou possíveisdouble
.Respostas:
A implementação padrão do Oracle JDK 7 usa o que é chamado de gerador linear de congratulações para produzir valores aleatórios
java.util.Random
.Retirado do
java.util.Random
código-fonte (JDK 7u2), de um comentário sobre o métodoprotected int next(int bits)
, que é o que gera os valores aleatórios:Previsibilidade de geradores congruentes lineares
Hugo Krawczyk escreveu um artigo muito bom sobre como esses LCGs podem ser previstos ("Como prever geradores congruenciais"). Se você tiver sorte e estiver interessado, ainda poderá encontrar uma versão gratuita e disponível para download na Web. E há muito mais pesquisas que mostram claramente que você nunca deve usar um LCG para fins críticos de segurança. Isso também significa que seus números aleatórios são previsíveis no momento, algo que você não deseja para IDs de sessão e similares.
Como quebrar um gerador linear de congruência
A suposição de que um invasor teria que esperar a repetição do LCG após um ciclo completo está errada. Mesmo com um ciclo ideal (o módulo m em sua relação de recorrência), é muito fácil prever valores futuros em muito menos tempo do que um ciclo completo. Afinal, são apenas algumas equações modulares que precisam ser resolvidas, o que se torna fácil assim que você observa valores de saída suficientes do LCG.
A segurança não melhora com uma semente "melhor". Simplesmente não importa se você semeia com um valor aleatório gerado
SecureRandom
ou produz o valor rolando um dado várias vezes.Um invasor simplesmente calcula a semente a partir dos valores de saída observados. Isso leva muito menos tempo que 2 ^ 48 no caso de
java.util.Random
. Os descrentes podem experimentar esse experimento , onde é mostrado que você pode preverRandom
resultados futuros observando apenas dois (!) Valores de resultado no tempo aproximadamente 2 ^ 16. Não leva nem um segundo em um computador moderno para prever a saída de seus números aleatórios agora.Conclusão
Substitua seu código atual. Use
SecureRandom
exclusivamente. Então, pelo menos, você terá uma pequena garantia de que o resultado será difícil de prever. Se você deseja as propriedades de um PRNG criptograficamente seguro (no seu caso, é isso que você deseja), precisará seguirSecureRandom
apenas. Ser esperto em mudar a maneira como deveria ser usado quase sempre resultará em algo menos seguro ...fonte
Random
está quebrado - deve ser usado apenas em diferentes cenários. Claro, você sempre pode usar o SecureRandom. Mas, em geral,SecureRandom
é visivelmente mais lento que puroRandom
. E há casos em que você está interessado apenas em boas propriedades estatísticas e excelente desempenho, mas não se importa com segurança: as simulações de Monte-Carlo são um bom exemplo. Fiz comentários sobre isso em uma resposta semelhante , talvez você ache útil.Um aleatório tem apenas 48 bits, enquanto o SecureRandom pode ter até 128 bits. Portanto, as chances de repetição no securerandom são muito pequenas.
Random usa o
system clock
como a semente / ou para gerar a semente. Portanto, eles podem ser reproduzidos facilmente se o atacante souber a hora em que a semente foi gerada. Mas o SecureRandom tiraRandom Data
do seuos
(eles podem ser um intervalo entre pressionamentos de tecla etc - a maioria dos coleciona esses dados, armazena-os em arquivos -/dev/random and /dev/urandom in case of linux/solaris
) e usa isso como a semente.Portanto, se o tamanho pequeno do token estiver correto (no caso de Random), você poderá continuar usando seu código sem nenhuma alteração, pois está usando o SecureRandom para gerar a semente. Mas se você quiser tokens maiores (que não podem ser sujeitos a
brute force attacks
), vá com SecureRandom -No caso de
2^48
tentativas aleatórias, são necessárias apenas tentativas, com as CPUs avançadas de hoje em dia, é possível quebrá-las em tempo prático. Mas, para seguranças2^128
, serão necessárias tentativas aleatórias , que levarão anos e anos para se igualar às máquinas avançadas de hoje.Veja este link para mais detalhes.
EDIT
Após ler os links fornecidos pelo @emboss, fica claro que a semente, por mais aleatória que seja, não deve ser usada com java.util.Random. É muito fácil calcular a semente observando a saída.
Escolha SecureRandom - use o PRNG nativo (conforme fornecido no link acima), pois ele usa valores aleatórios do
/dev/random
arquivo para cada chamada paranextBytes()
. Dessa maneira, um invasor observando a saída não poderá distinguir nada, a menos que esteja controlando o conteúdo do/dev/random
file (o que é muito improvável)O algoritmo sha1 prng calcula a semente apenas uma vez e, se sua VM estiver em execução por meses usando a mesma semente, pode ser quebrada por um invasor que está observando passivamente a saída.
OBSERVAÇÃO - Se você estiver ligando o
nextBytes()
mais rápido possível para o seu sistema operacional escrever bytes aleatórios (entropia) no/dev/random
, poderá encontrar problemas ao usar o NATIVE PRNG . Nesse caso, use uma instância SHA1 PRNG de SecureRandom e a cada poucos minutos (ou algum intervalo), propague essa instância com o valor denextBytes()
de uma instância NATIVE PRNG do SecureRandom. A execução paralela desses dois garantirá que você esteja propagando regularmente com valores aleatórios verdadeiros, além de não esgotar a entropia obtida pelo sistema operacional.fonte
Random
, o OP não deve estar usandoRandom
./proc/sys/kernel/random/entropy_avail
e verificar com alguma linha de despejos que não há é muito longa espera ao ler sobre/dev/random
Se você executar duas vezes
java.util.Random.nextLong()
com a mesma semente, produzirá o mesmo número. Por razões de segurança que você deseja manter,java.security.SecureRandom
porque é muito menos previsível.As 2 classes são semelhantes, acho que você só precisa mudar
Random
paraSecureRandom
uma ferramenta de refatoração e a maior parte do código existente funcionará.fonte
Se alterar o código existente for uma tarefa acessível, sugiro que você use a classe SecureRandom, conforme sugerido no Javadoc.
Mesmo se você encontrar a implementação da classe Random, use a classe SecureRandom internamente. você não deve considerar que:
Portanto, é uma escolha melhor seguir a sugestão de documentação e ir diretamente com o SecureRandom.
fonte
java.util.Random
implementação foi usadaSecureRandom
internamente, e que o código deles usaSecureRandom
para propagar o arquivoRandom
. Ainda assim, eu concordo com as duas respostas até agora; é melhor usarSecureRandom
para evitar uma solução explicitamente determinística.A implementação de referência atual de
java.util.Random.nextLong()
faz duas chamadas para o métodonext(int)
que expõe diretamente 32 bits da semente atual:Os 32 bits superiores do resultado de
nextLong()
são os bits da semente no momento. Como a largura da semente é de 48 bits (diz o javadoc), basta * iterar nos 16 bits restantes (ou seja, apenas 65.536 tentativas) para determinar a semente que produziu o segundo 32 bits.Depois que a semente é conhecida, todos os seguintes tokens podem ser facilmente calculados.
Usando a saída
nextLong()
diretamente, em parte o segredo do PNG, a ponto de todo o segredo poder ser calculado com muito pouco esforço. Perigoso!* É necessário algum esforço se o segundo 32 bits for negativo, mas é possível descobrir isso.
fonte
A semente não tem sentido. Um bom gerador aleatório difere no número principal escolhido. Todo gerador aleatório inicia a partir de um número e itera através de um 'toque'. O que significa que você passa de um número para o próximo, com o antigo valor interno. Mas depois de um tempo você alcança o começo novamente e começa tudo de novo. Então você executa ciclos. (o valor de retorno de um gerador aleatório não é o valor interno)
Se você usar um número primo para criar um toque, todos os números desse toque serão escolhidos antes de você completar um ciclo completo com todos os números possíveis. Se você pegar números não primos, nem todos os números serão escolhidos e você terá ciclos mais curtos.
Números primos mais altos significam ciclos mais longos antes de retornar ao primeiro elemento novamente. Portanto, o gerador aleatório seguro apenas possui um ciclo mais longo, antes de chegar ao início novamente, é por isso que é mais seguro. Você não pode prever a geração de números tão fácil quanto em ciclos mais curtos.
Com outras palavras: Você precisa substituir tudo.
fonte
Tentarei usar palavras muito básicas para que você possa entender facilmente a diferença entre Random e secureRandom e a importância da SecureRandom Class.
Você já se perguntou como o OTP (senha de uso único) é gerado? Para gerar um OTP, também usamos a classe Random e SecureRandom. Agora, para tornar seu OTP forte, o SecureRandom é melhor porque foram necessárias 2 ^ 128 tentativas, para quebrar o OTP, o que é quase impossível pela máquina atual, mas se for usada a Random Class, seu OTP pode ser quebrado por alguém que possa prejudicar seus dados porque demorou apenas 2 ^ 48 tentam rachar.
fonte