randomSeed(analogRead(x))
produzirá apenas 255 seqüências de números, o que torna trivial tentar todos os combos e produzir um oráculo que possa acoplar ao seu fluxo de saída, prevendo 100% da saída. Você está no caminho certo, porém, é apenas um jogo de números e precisa de muito mais deles. Por exemplo, fazer 100 leituras analógicas de 4 ADCs, resumindo todas elas e alimentando isso randomSeed
seria muito melhor. Para segurança máxima, você precisa de entradas imprevisíveis e mixagens não determinísticas.
Não sou um criptógrafo, mas passei milhares de horas pesquisando e construindo geradores aleatórios de hardware e software, então deixe-me compartilhar um pouco do que aprendi:
Entrada imprevisível:
- analogRead () (em pinos flutuantes)
- GetTemp ()
Entrada potencialmente imprevisível:
- micros () (com um período de amostra não determinístico)
- tremulação do relógio (largura de banda baixa, mas utilizável)
- readVCC () (se não for alimentado por bateria)
Entrada imprevisível externa:
- sensores de temperatura, umidade e pressão
- microfones
- Divisores de tensão LDR
- ruído de transistor com polaridade inversa
- tremulação da bússola / aceleração
- varredura do ponto de acesso wifi esp8266 (ssid, db, etc)
- tempo esp8266 (as tarefas wifi em segundo plano tornam os micros () buscados indeterminados)
- esp8266 HWRNG -
RANDOM_REG32
extremamente rápido e imprevisível, uma parada
coletando
A última coisa que você quer fazer é cuspir a entropia como ela aparece. É mais fácil adivinhar um lançamento de moeda do que um balde de moedas. A soma é boa. unsigned long bank;
depois bank+= thisSample;
é bom; vai rolar. bank[32]
é ainda melhor, continue a ler. Você deseja coletar pelo menos 8 amostras de entrada para cada parte da saída, idealmente muito mais.
Protegendo contra envenenamento
Se o aquecimento da prancha causar um certo jitter máximo de clock, esse é um vetor de ataque. O mesmo ocorre com o jateamento de RFI nas entradas analogRead (). Outro ataque comum simplesmente desconectou a unidade, despejando toda a entropia acumulada. Você não deve produzir números até saber que é seguro fazê-lo, mesmo ao custo da velocidade.
É por isso que você quer manter alguma entropia em torno de longo prazo, utilizando EEPROM, SD, etc Olhe para a Fortuna PRNG , que usa 32 bancos, cada um atualizados metade tão frequentemente quanto o anterior. Isso dificulta o ataque a todos os 32 bancos em um período de tempo razoável.
Processamento
Depois de coletar a "entropia", é necessário limpá-la e separá-la da entrada de uma maneira difícil de reverter. SHA / 1/256 é bom para isso. Você pode usar o SHA1 (ou até mesmo o MD5) para obter velocidade, pois não possui uma vulnerabilidade em texto sem formatação. Para colher, nunca use o banco de entopia completo e SEMPRE adicione um "sal" à saída que é diferente a cada vez para evitar saídas idênticas, sem alterações no banco de entropia: output = sha1( String(micros()) + String(bank[0]) + [...] );
a função sha oculta as entradas e clareia a saída, protegendo contra sementes fracas, baixo nível de acúmulo acumulado e outros problemas comuns.
Para usar as entradas do timer, você precisa torná-las indeterministas. Este é um simples como delayMicroseconds(lastSample % 255)
; que pausa uma quantidade imprevisível de tempo, tornando o relógio "sucessivo" com uma diferença não uniforme. Faça isso de forma semi-regular, if(analogRead(A1)>200){...}
desde que A1 seja barulhento ou conectado a uma entrada dinâmica. Tornar cada bifurcação do seu fluxo bastante difícil de determinar impedirá a criptoanálise na saída descompilada / rasgada.
A verdadeira segurança é quando o invasor conhece todo o sistema e ainda não consegue superá-lo.
Por fim, verifique seu trabalho. Execute sua saída através do ENT.EXE (também disponível para nix / mac) e veja se é bom. O mais importante é a distribuição do qui quadrado, que geralmente deve estar entre 33% e 66%. Se você receber 1,43% ou 99,999% ou algo do tipo, mais de um teste consecutivo, sua aleatória é uma porcaria. Você também deseja que os relatórios ENT da entropia sejam o mais próximo possível de 8 bits por byte,> 7,9, com certeza.
TLDR: A maneira mais simples e segura de enganar é o HWRNG do ESP8266. É rápido, uniforme e imprevisível. Execute algo assim em um ESP8266 executando o núcleo do Ardunio e use o serial para conversar com o AVR:
// ESP8266 Arduino core code:
void setup(){
Serial.begin(9600); // or whatever
}
void loop() {
// Serial.write((char)(RANDOM_REG32 % 256)); // "bin"
Serial.print( String(RANDOM_REG32, HEX).substring(1)); // "hex"
}
** editar
aqui está um esboço de HWRNG que escrevi há algum tempo, operando não apenas como um coletor, mas como um CSPRNG inteiro saindo da porta serial. Ele foi desenvolvido para um profissional, mas deve ser facilmente adaptável a outras placas. Você pode usar apenas pinos analógicos flutuantes, mas é melhor adicionar itens a eles, de preferência coisas diferentes. Como microfones, LDRs, termistores (aparados ao máximo na temperatura ambiente) e até fios longos. Faz muito bem em ENT se você tiver um ruído moderado.
O esboço integra várias noções que mencionei na minha resposta e nos comentários seguintes: acumular entropia, esticar por sobre-amostrar uma entropia abaixo do ideal (von neumann disse que é legal) e buscar uniformidade. Ele renuncia à estimativa da qualidade da entropia em favor de "me dê qualquer coisa possivelmente dinâmica" e à mistura usando um primitivo criptográfico.
// AVR (ardunio) HWRNG by dandavis. released to public domain by author.
#include <Hash.h>
unsigned long read[8] = {0, 0, 0, 0, 0, 0, 0, 0};
const int pincount = 9; // adjust down for non pro-mini boards
int pins[9] = {A0, A1, A2, A3, A4, A5, A6, A7, A0}; // adjust for board, name analog inputs to be sampled
unsigned int ticks = 0;
String buff = ""; // holds one round of derivation tokens to be hashed.
String cache; // the last read hash
void harvest() { // String() slows down the processing, making micros() calls harder to recreate
unsigned long tot = 0; // the total of all analog reads
buff = String(random(2147483647)) + String(millis() % 999);
int seed = random(256) + (micros() % 32);
int offset = random(2147483647) % 256;
for (int i = 0; i < 8; i++) {
buff += String( seed + read[i] + i + (ticks % 65), HEX );
buff += String(random(2147483647), HEX);
tot += read[i];
}//next i
buff += String( (micros() + ticks + offset) % 99999, HEX);
if (random(10) < 3) randomSeed(tot + random(2147483647) + micros());
buff = sha1( String(random(2147483647)) + buff + (micros()%64) + cache); // used hash to uniform output and waste time
Serial.print( buff ); // output the hash
cache = buff;
spin();
}//end harvest()
void spin() { // add entropy and mix
ticks++;
int sample = 128;
for (int i = 0; i < 8; i++) { // update ~6/8 banks 8 times
read[ read[i] % 8] += (micros() % 128);
sample = analogRead( pins[i] ); // a read from each analog pin
read[ micros() % 8] += ( read[i] % 64 ); // mix timing and 6LSBs from read
read[i] += sample; // mix whole raw sample
read[(i + 1) % 8] += random(2147483647) % 1024; // mix prng
read[ticks % 8] += sample % 16; // mix the best nibble of the read
read[sample % 8] += read[ticks % 8] % 2147483647; // intra-mix banks
}
}//end spin()
void setup() {
Serial.begin(9600);
delay(222);
int mx = 2028 + ((analogRead(A0) + analogRead(A1) + analogRead(A2) + analogRead(A3)) % 256);
while (ticks < mx) {
spin();
delay(1);
randomSeed(read[2] + read[1] + read[0] + micros() + random(4096) + ticks);
}// wend
}// end setup()
void loop() {
spin();
delayMicroseconds((read[ micros() % 8] % 2048) + 333 );
delay(random(10));
//if (millis() < 500) return;
if ((ticks % 16) == (millis() % 16) ) harvest();
}// end loop()