randomSeed(analogRead(x))
solo producirá 255 secuencias de números, lo que hace que sea trivial probar todos los combos y producir un oráculo que se puede acoplar a su flujo de salida, prediciendo toda la salida al 100%. Sin embargo, estás en el camino correcto, es solo un juego de números y necesitas MUCHOS más de ellos. Por ejemplo, tomar 100 lecturas analógicas de 4 ADC, resumirlas todas y alimentarlas randomSeed
sería mucho mejor. Para una seguridad máxima, necesita una entrada impredecible y una mezcla no determinista.
No soy un criptógrafo, pero he pasado miles de horas investigando y construyendo generadores aleatorios de hardware y software, así que permítanme compartir algo de lo que he aprendido:
Entrada impredecible:
- analogRead () (en pines flotantes)
- GetTemp ()
Entrada potencialmente impredecible:
- micros () (con un período de muestra no determinista)
- jitter de reloj (bajo ancho de banda, pero utilizable)
- readVCC () (si no funciona con batería)
Entrada externa impredecible:
- sensores de temperatura, humedad y presión
- micrófonos
- Divisores de voltaje LDR
- ruido de transistor de polarización inversa
- brújula / fluctuación de aceleración
- exploración de punto de acceso wifi esp8266 (ssid, db, etc.)
- sincronización esp8266 (las tareas wifi en segundo plano hacen que los micros programados () recuperen indeterminado)
- esp8266 HWRNG - extremadamente
RANDOM_REG32
rápido e impredecible, una parada
recolectar
Lo último que quieres hacer es escupir entropía tal como viene. Es más fácil adivinar un lanzamiento de moneda que un cubo de monedas. Sumar es bueno. unsigned long bank;
entonces más tarde bank+= thisSample;
es bueno; se volcará. bank[32]
es aún mejor, sigue leyendo. Desea recopilar al menos 8 muestras de entrada para cada porción de salida, idealmente mucho más.
Protección contra el envenenamiento
Si calentar el tablero causa una cierta fluctuación máxima de reloj, es un vector de ataque. Lo mismo con la voladura de RFI hacia las entradas analogRead (). Otro ataque común simplemente desconecta la unidad y por lo tanto descarga toda la entropía acumulada. No debe generar números hasta que sepa que es seguro hacerlo, incluso a costa de la velocidad.
Es por eso que desea mantener algo de entropía a largo plazo, utilizando EEPROM, SD, etc. Busque en el PRNG de Fortuna , que usa 32 bancos, cada uno actualizado la mitad de veces que el anterior. Eso hace que sea difícil atacar a los 32 bancos en un tiempo razonable.
Procesamiento
Una vez que recolecta "entropía", debe limpiarla y separarla de la entrada de una manera difícil de revertir. SHA / 1/256 es bueno para esto. Puede usar SHA1 (o incluso MD5 realmente) para la velocidad ya que no tiene una vulnerabilidad de texto sin formato. Para cosechar, nunca use el banco de entopy completo, y SIEMPRE SIEMPRE agregue una "sal" a la salida que es diferente cada vez para evitar salidas idénticas sin cambios en el banco de entropía: output = sha1( String(micros()) + String(bank[0]) + [...] );
la función sha oculta las entradas y blanquea la salida, protegiendo contra semillas débiles, bajo acumulado ent, y otros problemas comunes.
Para usar entradas de temporizador, debe hacerlas indeterministas. Este es un simple como delayMicroseconds(lastSample % 255)
; que pausa una cantidad de tiempo impredecible, haciendo que las lecturas de reloj "sucesivas" no sean uniformes en la diferencia. Haga eso de forma regular, como if(analogRead(A1)>200){...}
, siempre que A1 sea ruidoso o esté conectado a una entrada dinámica. Hacer que cada bifurcación de su flujo sea bastante difícil de determinar evitará el criptoanálisis en la salida descompilada / extraída.
La verdadera seguridad es cuando el atacante conoce todo su sistema y aún no puede vencerlo.
Por último, revisa tu trabajo. Ejecute su salida a través de ENT.EXE (también disponible para nix / mac) y vea si es bueno. Lo más importante es la distribución de chi cuadrado, que generalmente debe estar entre 33% y 66%. Si obtienes 1.43% o 99.999% o algo así de nervioso, más de una prueba en una fila, tu azar es una mierda. También desea que los informes ENT de entropía estén lo más cerca posible de 8 bits por byte,> 7.9 con seguridad.
TLDR: La forma más simple de prueba de tontos es con el HWRNG de ESP8266. Es rápido, uniforme e impredecible. Ejecute algo como esto en un ESP8266 que ejecute el núcleo Ardunio, y use serial para hablar con el 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
Aquí hay un boceto de HWRNG sin formato que escribí hace un tiempo, operando no solo como un colector, sino como un CSPRNG completo escupiendo desde el puerto serie. Está diseñado para un pro-mini, pero debe ser fácilmente adaptable a otras placas. Puede usar solo pines analógicos flotantes, pero es mejor agregarles cosas, preferiblemente cosas diferentes. Al igual que los micrófonos, los LDR, los termistores (recortados a la máxima dispersión alrededor de la temperatura ambiente) e incluso cables largos. Funciona bastante bien en ENT si tienes incluso un ruido moderado.
El boceto integra varias nociones que he mencionado en mi respuesta y comentarios de seguimiento: acumulación de entropía, estiramiento sobremuestreando una entropía menos que ideal (von Neumann dijo que es genial), y un hash a la uniformidad. Se renuncia a la estimación de la calidad de la entropía a favor de "dar algo posiblemente dinámico" y mezclar usando una primitiva criptográfica.
// 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()