randomSeed(analogRead(x))
produrrà solo 255 sequenze di numeri, il che rende banale provare tutte le combo e produrre un oracolo che può accoppiarsi al flusso di output, prevedendo tutto l'output al 100%. Tuttavia, sei sulla buona strada, è solo un gioco di numeri e ne hai bisogno MOLTO di più. Ad esempio, prendere 100 letture analogiche da 4 ADC, sommarle tutte e darle da mangiare randomSeed
sarebbe molto meglio. Per la massima sicurezza, sono necessari sia input imprevedibili sia missaggi non deterministici.
Non sono un crittografo, ma ho trascorso migliaia di ore a ricercare e costruire generatori casuali di hardware e software, quindi lasciami condividere alcune delle cose che ho imparato:
Input imprevedibile:
- analogRead () (su pin flottanti)
- GetTemp ()
Input potenzialmente imprevedibile:
- micros () (con un periodo di campionamento non deterministico)
- clock jitter (larghezza di banda ridotta, ma utilizzabile)
- readVCC () (se non alimentato a batteria)
Ingresso imprevedibile esterno:
- sensori di temperatura, umidità e pressione
- microfoni
- Divisori di tensione LDR
- rumore del transistor a polarizzazione inversa
- jitter bussola / accelerazione
- scansione hotspot wifi esp8266 (ssid, db, ecc)
- temporizzazione esp8266 (le attività wifi in background rendono i micros () recuperati indeterminati)
- esp8266 HWRNG -
RANDOM_REG32
-molto veloce e imprevedibile, a 1 stop
collezionare
L'ultima cosa che vuoi fare è sputare entropia man mano che arriva. È più facile indovinare un lancio della moneta che un secchio di monete. La somma è buona. unsigned long bank;
poi più tardi bank+= thisSample;
va bene; rotolerà. bank[32]
è ancora meglio, continua a leggere. Volete raccogliere almeno 8 campioni di input per ogni blocco di output, idealmente molto di più.
Proteggersi dall'avvelenamento
Se il riscaldamento della scheda provoca un certo jitter di clock massimo, questo è un vettore di attacco. Lo stesso vale per l'RMI di sabbiatura verso gli ingressi analogRead (). Un altro attacco comune semplicemente scollega l'unità scaricando così tutta l'entropia accumulata. Non dovresti produrre numeri finché non sai che è sicuro farlo, anche a costo della velocità.
Questo è il motivo per cui vuoi mantenere un po 'di entropia a lungo termine, usando EEPROM, SD, ecc. Guarda il PRNG Fortuna , che utilizza 32 banche, ognuna aggiornata la metà di quella precedente. Ciò rende difficile attaccare tutte e 32 le banche in un ragionevole lasso di tempo.
Elaborazione
Una volta raccolta "entropia", è necessario ripulirla e separarla dall'input in modo difficile da invertire. SHA / 1/256 è buono per questo. Puoi usare SHA1 (o addirittura MD5 davvero) per la velocità poiché non hai una vulnerabilità in chiaro. Per raccogliere, non utilizzare mai il banco full entopy e SEMPRE aggiungere SEMPRE un "sale" all'output che è ogni volta diverso per evitare output identici dato nessun cambiamento di banca entropica: output = sha1( String(micros()) + String(bank[0]) + [...] );
la funzione sha nasconde input e imbianca output, proteggendo da semi deboli, basso accumulo ent e altri problemi comuni.
Per utilizzare gli ingressi timer, è necessario renderli indeterministici. Questo è un semplice delayMicroseconds(lastSample % 255)
; che mette in pausa un periodo di tempo imprevedibile, rendendo l'orologio "successivo" legge differenze non uniformi. Fatelo semi-regolarmente, ad esempio if(analogRead(A1)>200){...}
, a condizione che A1 sia rumoroso o agganciato a un input dinamico. Rendere piuttosto difficile determinare ogni fork del flusso impedirà la crittoanalisi sull'output decompilato / rippato.
La vera sicurezza è quando l'attaccante conosce l'intero sistema ed è ancora impotente per superarlo.
Infine, controlla il tuo lavoro. Esegui l'output tramite ENT.EXE (disponibile anche per nix / mac) e verifica se funziona. La più importante è la distribuzione del chi quadro, che dovrebbe essere compresa tra il 33% e il 66%. Se ottieni l'1,43% o il 99,999% o qualcosa di simile, più di un test di fila, il tuo caso è una schifezza. Volete anche che l'ENTrop entri nel rapporto il più vicino possibile a 8 bit per byte,> 7.9 di sicuro.
TLDR: il modo più semplice a prova di folle è l'HWRNG dell'ESP8266. È veloce, uniforme e imprevedibile. Esegui qualcosa del genere su un ESP8266 che esegue il core Ardunio e usa il seriale per parlare con l'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"
}
** modificare
ecco uno schizzo HWRNG a bordo nudo che ho scritto qualche tempo fa, operando non solo come un collezionista, ma come un intero CSPRNG che sputa fuori dalla porta seriale. È costruito per un pro-mini ma dovrebbe essere facilmente adattabile ad altre schede. Puoi usare solo pin analogici flottanti, ma è meglio aggiungere cose, preferibilmente cose diverse. Come microfoni, LDR, termistori (tagliati alla massima diffusione attorno alla temperatura ambiente) e persino fili lunghi. Fa abbastanza bene in ENT se hai anche un rumore moderato.
Lo schizzo integra diverse nozioni che ho citato nella mia risposta e nei commenti di follow-up: accumulare entropia, allungando per eccesso di campionamento entropia non ideale (von Neumann ha detto che è bello) e hash per l'uniformità. Rinuncia alla stima della qualità dell'entropia a favore di "dammi qualsiasi cosa possibilmente dinamica" e mescolandosi usando una primitiva crittografica.
// 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()