Algoritmos pseudoaleatórios
Em Java nós temos geradores de números pseudoaleatórios, mas o que significa isso? Significa que uma sequência de números gerados por estes algoritmos aparentam ser aleatórios mas não são, na verdade eles são baseados em um algoritmo que possui uma lógica que gera números em uma sequência extraída de uma valor inicial, a semente.Semente (seed)
Todos algoritmos de geração pseudoaleatória partem de um conjunto de valores iniciais, chamado semente, para realizar os cálculos de obtenção de cada número gerado em uma sequência determinística.Determinístico
Os algoritmos pseudoaleatórios são determinísticos pois, conhecendo-se o algoritmo e a semente utilizada é possível replicar (determinar) uma sequência de valores gerada previamente. Ou, dependendo do caso, conhecendo-se o algoritmo e a uma parte da sequência é possível encontrar a semente através de ataque de força bruta.RNG - Random Number Generator ou Gerador de Números Aleatórios
Os "fabricantes" de números aleatórios. Em Java é uma classe que implemente um algoritmo gerador de valores aleatórios. Passaremos a utilizar somente RNG daqui em diante.java.lang.Math.random
Dentro da classe utilitária java.lang.Math
existe o método estático random
que é capaz de gerar números pseudoaleatórios do tipo double
compreendidos entre 0,0 (inclusive) e 1,0. Este método de geração de números aleatórios é o método mais utilizado, principalmente por iniciantes na linguagem, mas também por ser mais "simples" de trabalhar.Na primeira chamada ao método
Math.random
será criada uma instância estática de um RNG padrão (new Random()
) com semente baseada no momento da execução (System.currentTimeMillis()
), e após isso, todas as chamadas usarão o mesmo RNG.Este método de geração tem a vantagem de manter apenas uma instância de um RNG durante toda a execução do seu sistema sem a necessidade de implementações adicionais. Mas as chamadas são sincronizadas, o que leva a degradação de performance em sistemas multithread que necessitem realizar chamadas concorrentes e em grande volume.
public class NumerosAleatorios {
public static void main(String[] args) {
// Gerando números reais de 0,0 até 0,999999...
for(int i = 0; i < 50; i++) {
System.out.print(Math.random() + " ");
}
System.out.print("\n");
// Gerando números inteiros de 15 até 24
for(int i = 0; i < 50; i++) {
int n = (int)(Math.random() * 10) + 15;
System.out.print(n + " ");
}
}
}
java.util.Random
Como vimos acima, o RNG padrão do Java é obtido através da classe Random. Ao trabalharmos diretamente com esta classe temos acesso a diversos métodos de geração de valores aleatórios, bem como a possibilidade de definir a semente. A chamada Math.random
é um atalho para Random.nextDouble
que retornará um valor double
, mas podemos obter, também, valores inteiros e boolean
entre outros.Como esta classe permite que possamos definir ou redefinir a semente o teste que iremos ver logo abaixo, sempre que executado, retornará o mesmo resultado, exatamente a mesma sequência de valores, expondo assim o determinismo do algoritmo de uma forma mais clara.
public class NumerosAleatorios {
public static void main(String[] args) {
Random rng;
rng = new Random(1234567890);
// Gerando uma sequência de números de 0 até 19
for(int i = 0; i < 50; i++) {
System.out.print(rng.nextInt(20) + " ");
}
System.out.print("\n");
rng.setSeed(1234567890);
// Gerando a mesma sequência de números de 0 até 19
for(int i = 0; i < 50; i++) {
System.out.print(rng.nextInt(20) + " ");
}
}
}
Como pode ser observado, este método possui uma forma muito mais intuitiva de geração de números inteiros dentro de um conjunto pré definido, e por questões de clareza é preferível ao método anterior. Mas é importante lembrar de criar os controles para a manutenção da instância do seu RNG, ou seja, não é recomendável criar um RNG a cada vez que precisar gerar um valor aleatório.E, da mesma forma que no método anterior, caso seu sistema faça uso intenso de multithreading e geração aleatória a sua instância do RNG pode ficar sobrecarregada e gerar contenção, mas isto é facilmente contornável com a manutenção de mais de uma instância de RNG, o que não é possível com o uso de
Math.random
.java.util.concurrent.ThreadLocalRandom
Caso seu sistema faça uso de multithread e geração aleatória é possível fazer uso desta classe especial que o Java fornece a partir da versão 1.7. Resumidamente, este classe é uma instância da classe Random
que é isolada em uma thread, que não pode ser instanciada e nem pode ter sua semente definida. Para fazer uso dela basta chamar ThreadLocalRandom.current()
e o seu método preferido.java.security.SecureRandom
Esta classe respeita alguns conceitos de segurança criptográfica e tenta obter uma sequência não determinística e forte o bastante. O RNG obtido por esta classe vai depender da implementação do runtime Java e poderá fazer uso de recursos criptográficos do sistema operacional ou do hardware onde está sendo executado, por exemplo, no Linux o runtime poderá utilizar o gerador do SO baseado na chamada /dev/random
.Valores aleatórios versus Segurança
Tudo depende do objetivo que os valores aleatórios são necessários. Para sistemas simples, onde algum nível de aleatoriedade é requisito, como em jogos, e a previsibilidade destes valores não afeta o comportamento geral dos usuários, então os métodos não seguros são suficientes. Já em sistemas onde a previsibilidade dos valores é uma fator essencial na segurança, então um método seguro deve ser utilizado.Mas não fique tentado a utilizar apenas métodos seguros, pois esta segurança tem um preço, e o preço é no desempenho, métodos que dependam do SO podem sofrer contenção fora do controle do seu sistema.
Bem, meu objetivo é um entendimento geral dos recursos, então, caso você precise de geração em nível criptográfico recomendo uma leitura mais aprofundada nos artigos disponíveis na internet ou em livros, mas algumas dicas a serem seguidas:
- Utilize a classe
SecureRandom
com o Java mais recente possível;- Redefina a semente com alguma frequência, ou por tempo ou valores obtidos;
- Redefina a semente com valores de difícil previsão;
- Não armazene rastros que possam levar às sementes, como logs.
Obs.: Utilize a classe
SecureRandom
somente se for essencial, as outras dicas acima também podem ser utilizadas com a classe Random
.Para exemplificar, vai um código de um gerador de inteiros com redefinição de semente baseado em tempo.
import java.nio.ByteBuffer;
import java.security.SecureRandom;
public class SecureRNG {
private static SecureRandom sRNG = new SecureRandom();
private static long lastTimeSeeded = 0;
private static final long SEED_TIMEOUT = 10000; // reseed após 10 segundos
// A redefinição da semente será feita na primeira chamada após o timeout definido.
// O momento exato é de difícil previsão, vai depender do código e, normalmente, de uma ação do usuário.
private synchronized static void reseed() {
lastTimeSeeded = System.currentTimeMillis();
// nanoTime é de difícil previsão pois trabalha com aproximação
final long nanoTime = System.nanoTime();
// memória livre para a JRE é de difícil previsão pois depende do ambiente e varia de momento para momento
final long freeMemory = Runtime.getRuntime().freeMemory();
// monta um array de 16 bytes com os valores anteriores
final byte[] seed = ByteBuffer.allocate(16).putLong(nanoTime).putLong(freeMemory).array();
// a redefinição da semente na classe SecureRandom trabalha de forma adicional e não por substituição
sRNG.setSeed(seed);
}
public synchronized static int getInt(final int n) {
if(System.currentTimeMillis() > (lastTimeSeeded + SEED_TIMEOUT)) {
reseed();
}
return sRNG.nextInt(n);
}
}
Nenhum comentário:
Postar um comentário
Olá! Antes de postar seu comentário, por favor, observe que comentários técnicos, elogios e sugestões são antecipadamente agradecidos, esclarecimentos sobre os conceitos envolvidos na postagem serão respondidos da melhor forma possível, mas pedidos de ajuda técnica ou suporte individual deverão ser feitos através do formulário de contato. Grato!