In Arduino eine wirklich zufällige Zahl erhalten

13

Was ist die beste Methode, um in Arduino eine echte (im Gegensatz zu einer Pseudo-Zufallszahl) zu erhalten, oder zumindest die bestmögliche Annäherung? Nach meinem Verständnis ist die Funktion randomSeed (analogRead (x)) nicht zufällig genug.

Wenn möglich, sollte die Methode das grundlegende Arduino-Setup alleine nutzen (keine zusätzlichen Sensoren). Lösungen mit externen Sensoren sind willkommen, wenn sie die Zufälligkeit gegenüber dem Grundaufbau deutlich verbessern.

Rexcirus
quelle
Was ist die Anwendung? Muss es kryptografisch sicher sein? Was machst du dann mit der Zufälligkeit? Ohne einen externen Chip, der ein TRNG aus einer physikalischen Entropiequelle implementiert, hat man dann Pech. Sie können auch ein determenistisches RNG wie ein HMAC-DRBG implementieren und es aus einer statischen Quelle und einer Entropiequelle mit geringer Qualität extrahieren, was jedoch immer noch nicht kryptografisch sicher ist.
Maximilian Gerhardt
Ja, ich benötige Zufallszahlen für kryptografisch sichere Anwendungen.
Rexcirus

Antworten:

10

Die Entropy-Bibliothek verwendet:

der natürliche Jitter des Watchdog-Timers, um einen zuverlässigen Strom von echten Zufallszahlen zu erzeugen

Diese Lösung gefällt mir, weil sie keine Pins Ihres Mikrocontrollers belegt und keine externen Schaltkreise erfordert. Dies macht es auch weniger anfällig für externe Ausfälle.

Neben einer Bibliothek wird auch eine Skizze bereitgestellt, die die Verwendung derselben Technik demonstriert, mit der ein zufälliger Startwert für den PRNG des Mikrocontrollers ohne die Bibliothek generiert wird: https://sites.google.com/site/astudyofentropy/project-definition / timer-jitter-entropiequellen / entropiebibliothek / arduino-random-seed

per1234
quelle
8

randomSeed(analogRead(x))erzeugt nur 255 Folgen von Zahlen, was es trivial macht, alle Combos auszuprobieren und ein Orakel zu erzeugen, das mit Ihrem Ausgabestream gekoppelt werden kann und die gesamte Ausgabe zu 100% vorhersagt. Sie sind jedoch auf dem richtigen Weg, es ist nur ein Zahlenspiel, und Sie brauchen VIEL mehr davon. Beispiel: Nehmen Sie 100 analoge Lesevorgänge von 4 ADCs, addieren Sie diese und geben Sie sie an. Das randomSeedwäre viel besser. Für maximale Sicherheit benötigen Sie sowohl unvorhersehbare Eingaben als auch nicht deterministisches Mischen.

Ich bin kein Kryptograf, aber ich habe Tausende von Stunden damit verbracht, nach zufälligen Generatoren für Hardware und Software zu suchen und diese zu erstellen.

Unvorhersehbare Eingabe:

  • analogRead () (auf schwebenden Pins)
  • GetTemp ()

Möglicherweise unvorhersehbarer Eingang:

  • micros () (mit nicht deterministischer Abtastperiode)
  • Taktjitter (niedrige Bandbreite, aber verwendbar)
  • readVCC () (falls nicht batteriebetrieben)

Externer unvorhersehbarer Eingang:

  • Temperatur-, Feuchtigkeits- und Drucksensoren
  • Mikrofone
  • LDR Spannungsteiler
  • Rauschen des Transistors in Sperrrichtung
  • Kompass / Beschleunigungsjitter
  • esp8266 wifi hotspot scan (ssid, db, etc)
  • esp8266 timing (die Hintergrund-WLAN-Aufgaben machen geplante micros () -Aufrufe unbestimmt)
  • esp8266 HWRNG - RANDOM_REG32Extrem schnell und unvorhersehbar

sammeln Das letzte, was Sie tun möchten, ist Entropie auszuspucken, wie es kommt. Es ist einfacher, einen Münzwurf zu erraten als einen Eimer mit Münzen. Summieren ist gut. unsigned long bank;dann ist später bank+= thisSample;gut; es wird sich überschlagen. bank[32]ist noch besser, lesen Sie weiter. Sie möchten für jeden Output-Block mindestens 8 Input-Samples sammeln, im Idealfall viel mehr.

Schutz vor Vergiftungen Wenn das Erhitzen der Platine einen gewissen maximalen Clock-Jitter verursacht, ist dies ein Angriffsvektor. Dasselbe gilt für das Abstrahlen von RFI zu den analogen Read () - Eingängen. Ein weiterer häufiger Angriff, bei dem die Einheit einfach vom Stromnetz getrennt wird und die gesamte angesammelte Entropie verloren geht. Sie sollten erst dann Zahlen ausgeben, wenn Sie wissen, dass dies auch auf Kosten der Geschwindigkeit sicher ist.

Aus diesem Grund möchten Sie mit EEPROM, SD usw. eine gewisse Entropie auf lange Sicht erhalten. Schauen Sie sich den Fortuna PRNG an , der 32 Bänke verwendet, von denen jede halb so oft aktualisiert wird wie die vorherige. Das macht es schwierig, alle 32 Banken in angemessener Zeit anzugreifen.

Verarbeitung Sobald Sie "Entropie" gesammelt haben, müssen Sie diese bereinigen und auf eine schwer umkehrbare Weise von der Eingabe trennen. SHA / 1/256 ist dafür gut. Sie können SHA1 (oder sogar MD5) für die Geschwindigkeit verwenden, da Sie keine Sicherheitsanfälligkeit in Klartext haben. Zur Ernte, nie die volle entopy Bank verwenden und immer immer ein „Salz“ mit dem Ausgang hinzufügen , die jedes Mal anders ist , um identische Ausgänge zu verhindern gegeben keine Entropie Bankwechsel: output = sha1( String(micros()) + String(bank[0]) + [...] );Die sha Funktion beide kaschiert Eingänge und bleicht Ausgang, Schutz vor schwachen Samen, niedrige akkumulierte ent und andere häufige Probleme.

Um Timer-Eingänge zu verwenden, müssen Sie sie unbestimmt machen. Dies ist eine einfache als delayMicroseconds(lastSample % 255); Dadurch wird eine unvorhersehbare Zeitspanne angehalten, und die "aufeinanderfolgende" Uhr liest den Unterschied ungleichmäßig. Tun Sie dies regelmäßig, if(analogRead(A1)>200){...}vorausgesetzt, A1 ist verrauscht oder mit einem dynamischen Eingang verbunden. Wenn es schwierig ist, jede Gabelung Ihres Flusses zu bestimmen, wird die Kryptoanalyse für dekompilierte / gerippte Ausgaben verhindert.

Wirkliche Sicherheit besteht darin, dass der Angreifer Ihr gesamtes System kennt und es dennoch nicht überwinden kann.

Zuletzt überprüfen Sie Ihre Arbeit. Führen Sie Ihre Ausgabe über ENT.EXE aus (auch für nix / mac verfügbar) und prüfen Sie, ob sie funktioniert . Am wichtigsten ist die Chi-Quadrat-Verteilung, die normalerweise zwischen 33% und 66% liegen sollte. Wenn Sie 1,43% oder 99,999% oder so etwas bekommen, mehr als einen Test hintereinander, ist Ihr Zufall Mist. Sie möchten auch, dass die Entropie-HNO-Berichte so nahe wie möglich bei 8 Bit pro Byte liegen,> 7,9 sicher.

TLDR: Der einfachste und narrensicherste Weg ist das HWRNG des ESP8266. Es ist schnell, gleichmäßig und unvorhersehbar. Führen Sie so etwas auf einem ESP8266 mit Ardunio-Core aus und verwenden Sie die serielle Schnittstelle, um mit dem AVR zu kommunizieren:

// 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"
}

** bearbeiten

Hier ist eine HWRNG-Skizze, die ich vor einiger Zeit geschrieben habe und die nicht nur als Sammler, sondern als ganzer CSPRNG fungiert, der aus der seriellen Schnittstelle ausspuckt. Es ist für einen Pro-Mini gebaut, sollte aber leicht an andere Boards anpassbar sein. Sie können nur schwebende analoge Pins verwenden, aber es ist besser, ihnen etwas hinzuzufügen, vorzugsweise verschiedene Dinge. Wie Mikrofone, LDRs, Thermistoren (auf maximale Temperatur eingestellt) und sogar lange Drähte. Bei HNO funktioniert es ziemlich gut, wenn Sie sogar mäßiges Rauschen haben.

Die Skizze integriert mehrere Begriffe, die ich in meiner Antwort und den nachfolgenden Kommentaren erwähnt habe: Akkumulieren der Entropie, Dehnen durch Überabtasten der weniger als idealen Entropie (von Neumann sagte, es sei cool) und Hasching zur Gleichförmigkeit. Es verzichtet auf die Schätzung der Entropiequalität zugunsten von "irgendetwas Mögliches Dynamisches" und Vermischen unter Verwendung eines kryptografischen Primitivs.

// 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()
Dandavis
quelle
(Es fehlen mir hier die Charaktere.) Gute Übersicht! Ich würde vorschlagen, einen Zähler für das Salz zu verwenden; micros () ist eine Verschwendung von Bits, da es zwischen den Aufrufen um mehrere Schritte springen kann. Vermeiden Sie die hohen Bits in analogen Eingängen, beschränken Sie sich auf die niedrigsten ein oder zwei Bits. Selbst bei einem gezielten Angriff sind diese schwer zu fassen (es sei denn, Sie können einen Draht an den Eingang anschließen). "Nicht deterministisches Mischen" ist in Software nicht möglich. Das SHA-1-Mischen ist standardisiert: crypto.stackexchange.com/a/6232 . Der Indet. Der Timer, den Sie vorschlagen, ist nur so zufällig wie die Quelle, die Sie bereits haben. Hier gibt es nicht viel zu gewinnen.
Jonas Schäfer
sha vereinfacht und schützt, sodass Sie sich beispielsweise keine Gedanken darüber machen müssen, wie viele Bits von einem analogen Eingang abgerufen werden sollen. Ein paar Zentimeter Draht, der an ein Analogkabel (oder eine serpentinenförmige Leiterplatte) angeschlossen ist, lässt es mehr als ein paar Bits schwingen. Das Mischen ist aufgrund des nicht gespeicherten und unbekannten Salzes, das dem Hash mit einer Unterprobe von akkumulierten Werten zugeführt wird, nicht deterministisch. micros () ist schwerer wiederzugeben als ein Zähler, insbesondere wenn er in nicht deterministischen Intervallen abgefeuert wird.
Dandavis
1
Ich habe eine Frage. Sie sagten, dass es besser ist, 100 Maßnahmen zu ergreifen. Aber ist das Ergreifen vieler Maßnahmen nicht eine Art "Durchschnitt", der die Wirksamkeit der Erfassung dieser "zufälligen" Daten einschränkt? Normalerweise erhalten Sie im Durchschnitt weniger laute (also weniger "zufällige") Messungen ...
frarugi87
Nun, ich empfehle konstante Abtastung, ich habe nur gesagt, 100 ist besser als 1, da es mehr Kombinationen bietet. Ein Akkumulationsmodell wie Yarrow / Fortuna ist immer noch viel besser. Erwägen Sie, diese 100 analogen Samples vor dem Hashing zu verketten (nicht zu summieren). Stärker, weil es die Reihenfolge der Samples wichtig macht, und wenn man ein Zeichen entfernt, erhält man einen ganz anderen Hash. Obwohl man die Stichproben mitteln könnte, um weniger Rauschen zu erhalten, müsste ein Angreifer alle Werte oder keine Übereinstimmung wörtlich vortragen ... Mein Hauptziel ist es, mehr zu akkumulieren, zu mischen und zu verifizieren als eine bestimmte Rauschquelle zu befürworten.
Dandavis
4

Nach meiner Erfahrung hat analogRead()ein Schwimmstift eine sehr niedrige Entropie. Vielleicht ein oder zwei zufällige Bits pro Anruf. Sie wollen auf jeden Fall etwas Besseres. Der in der Antwort von per1234 vorgeschlagene Jitter des Watchdog-Timers ist eine gute Alternative. Es erzeugt jedoch Entropie mit einer ziemlich langsamen Rate, was ein Problem sein kann, wenn Sie es direkt beim Start des Programms benötigen. Dandavis hat einige gute Vorschläge, aber sie erfordern im Allgemeinen entweder einen ESP8266 oder externe Hardware.

Es gibt eine interessante Entropiequelle, die noch nicht erwähnt wurde: den Inhalt des nicht initialisierten RAM. Wenn die MCU eingeschaltet wird, werden einige ihrer RAM-Bits (diejenigen, die zufällig die symmetrischsten Transistoren aufweisen) in einem zufälligen Zustand gestartet. Wie in diesem Hackaday-Artikel erörtert , kann dies als Entropiequelle verwendet werden. Es ist nur bei einem Kaltstart verfügbar, sodass Sie damit einen anfänglichen Entropiepool füllen können, den Sie dann regelmäßig aus einer anderen, möglicherweise langsamen Quelle auffüllen würden. Auf diese Weise kann Ihr Programm seine Arbeit aufnehmen, ohne darauf warten zu müssen, dass sich der Pool langsam füllt.

Hier ist ein Beispiel, wie dies auf einem AVR-basierten Arduino geerntet werden könnte. Das Code-Snippet unterhalb von XOR fasst den gesamten Arbeitsspeicher zusammen, um einen Startwert zu erstellen, an den er später weitergeleitet wird srandom(). Der schwierige Teil ist, dass das Ernten durchgeführt werden muss, bevor die C-Laufzeit die .data- und .bss-Speicherabschnitte initialisiert, und dann muss der Startwert an einem Ort gespeichert werden, den die C-Laufzeit nicht überschreibt. Dies erfolgt unter Verwendung bestimmter Speicherbereiche .

uint32_t __attribute__((section(".noinit"))) random_seed;

void __attribute__((naked, section(".init3"))) seed_from_ram()
{
    const uint32_t * const ramstart = (uint32_t *) RAMSTART;
    const uint32_t * const ramend   = (uint32_t *) RAMEND;
    uint32_t seed = 0;
    for (const uint32_t *p = ramstart; p <= ramend; p++)
        seed ^= *p;
    random_seed = seed;
}

void setup()
{
    srandom(random_seed);
}

Beachten Sie, dass bei einem Warm- Reset der SRAM erhalten bleibt, sodass der gesamte Inhalt Ihres Entropie-Pools erhalten bleibt. Derselbe Code kann dann verwendet werden, um die gesammelte Entropie über einen Reset hinweg beizubehalten.

Bearbeiten : Es wurde ein Problem behoben, das in meiner ursprünglichen Version seed_from_ram()auf globaler Ebene random_seedstatt auf lokaler Ebene auftrat seed. Dies könnte dazu führen, dass der Samen mit sich selbst XOR-verknüpft wird und die gesamte Entropie zerstört wird, die bisher geerntet wurde.

Edgar Bonet
quelle
Gute Arbeit! kann ich stehlen re: pins: ein oder zwei Bits von unknown sind ausreichend, wenn sie richtig verwendet werden; das würde nur die Ausgabegeschwindigkeit der Geheimhaltung (yuck) begrenzen, aber nicht die Geheimhaltung der Rechenleistung, die wir brauchen ...
dandavis
1
@dandavis: Ja, Sie können es sicher wiederverwenden. Sie haben Recht analogRead(), wenn Sie wissen, was Sie tun. Sie müssen nur darauf achten, die Zufälligkeit nicht zu überschätzen, wenn Sie eine Schätzung der Entropie Ihres Pools aktualisieren. Mein Punkt über analogRead()ist meistens als Kritik an einem armen, aber oft wiederholten „Rezept“ gedacht : randomSeed(analogRead(0)) Einfach mal rein setup()und davon ausgehen, dass es reicht.
Edgar Bonet
Wenn analogRead(0)1 Bit Entropie pro Aufruf vorhanden ist, ergibt ein wiederholter Aufruf 10000/8 = 1,25 KByte / s Entropie, das 150-fache der Entropiebibliothek.
Dmitry Grigoryev
0

Wenn Sie Entropie nicht wirklich benötigen und einfach bei jedem Start eine andere Folge von Pseudozufallszahlen erhalten möchten, können Sie EEPROM verwenden, um durch aufeinanderfolgende Seeds zu iterieren. Technisch gesehen ist der Prozess vollständig deterministisch, aber in der Praxis ist er viel besser als randomSeed(analogRead(0))bei einem nicht verbundenen Pin, wodurch Sie häufig mit demselben Startwert von 0 oder 1023 beginnen. Das Speichern des nächsten Startwerts im EEPROM garantiert, dass Sie mit einem anderen Startwert beginnen jedes Mal säen.

#include <EEPROM.h>

const int seed_addr = 0;
unsigned long seed;

void setup() {
    seed = EEPROM.read(seed_addr);
    EEPROM.write(seed_addr, seed+1);
    randomSeed(seed);
}

Wenn Sie echte Entropie benötigen, können Sie diese entweder aus der Zeitverschiebung oder durch Verstärken des externen Rauschens erfassen. Und wenn Sie viel Entropie benötigen, ist externes Rauschen die einzig gangbare Option. Zenerdiode ist eine beliebte Wahl, besonders wenn Sie eine Spannungsquelle über 5-6V haben (sie funktioniert mit einer geeigneten Zenerdiode auch mit 5V, erzeugt aber weniger Entropie):

Bildbeschreibung hier eingeben

( Quelle ).

Der Verstärkerausgang muss mit einem analogen Pin verbunden werden, der mehrere Entropiebits mit jeweils analogRead()bis zu zehn MHz erzeugt (schneller als Arduino abtasten kann).

Dmitry Grigoryev
quelle