Arduinoで真の(擬似ではなく)乱数を取得するための最良の方法、または少なくとも可能な限り最良の近似は何ですか?私の理解では、関数randomSeed(analogRead(x))は十分にランダムではありません。
可能であれば、メソッドは基本的なArduinoセットアップのみを活用する必要があります(追加のセンサーはありません)。外部センサーを使用したソリューションは、基本的なセットアップよりもランダム性が大幅に改善される場合に歓迎されます。
Arduinoで真の(擬似ではなく)乱数を取得するための最良の方法、または少なくとも可能な限り最良の近似は何ですか?私の理解では、関数randomSeed(analogRead(x))は十分にランダムではありません。
可能であれば、メソッドは基本的なArduinoセットアップのみを活用する必要があります(追加のセンサーはありません)。外部センサーを使用したソリューションは、基本的なセットアップよりもランダム性が大幅に改善される場合に歓迎されます。
回答:
エントロピーライブラリの使用:
真の乱数の信頼できるストリームを生成するウォッチドッグタイマーの自然なジッター
マイクロコントローラのピンを使い果たしず、外部回路を必要としないため、このソリューションが気に入っています。また、これにより、外部障害の影響を受けにくくなります。
ライブラリに加えて、ライブラリなしでマイクロコントローラのPRNGのランダムシードを生成するために使用されるのと同じ手法の使用を示すスケッチも提供します:https : //sites.google.com/site/astudyofentropy/project-definition / timer-jitter-entropy-sources / entropy-library / arduino-random-seed
randomSeed(analogRead(x))
は、255の数値シーケンスのみを生成します。これにより、すべてのコンボを試行し、出力ストリームに結合できるオラクルを生成して、すべての出力を100%予測するのは簡単です。あなたは正しい軌道に乗っていますが、それは単なる数字のゲームであり、もっとたくさん必要です。たとえば、4つのADCから100のアナログ読み取り値を取得し、それらをすべて加算して、それを供給するrandomSeed
方がはるかに良いでしょう。最大限のセキュリティを確保するには、予測不能な入力と非決定的な混合の両方が必要です。
私は暗号作成者ではありませんが、ハードウェアとソフトウェアのランダムジェネレーターの研究と構築に何千時間も費やしてきたので、学んだことを共有しましょう。
予測不能な入力:
潜在的に予測不可能な入力:
外部予測不能入力:
RANDOM_REG32
高速で予測不可能なワンストップ収集
最後に行うことは、エントロピーをそのまま吐き出すことです。コインのバケツよりもコインの反転を推測する方が簡単です。合計は良いです。unsigned long bank;
その後bank+= thisSample;
は良いです。ロールオーバーします。bank[32]
さらに良いです、読んでください。出力のチャンクごとに、少なくとも8個の入力サンプルを収集するのが理想的です。
ポイズニングに対する保護 ボードを加熱すると、特定の最大クロックジッターが発生する場合、それは攻撃ベクトルです。analogRead()入力に向けてRFIをブラストするのと同じです。別の一般的な攻撃は、ユニットを単に抜いて、蓄積されたすべてのエントロピーをダンプします。速度を犠牲にしても、安全であるとわかるまでは数値を出力しないでください。
EEPROM、SDなどを使用して、長期にわたってエントロピーを維持したいのはこのためです。32バンクを使用するFortuna PRNGを調べてください。各バンクは前のバンクの半分の頻度で更新されます。そのため、妥当な時間内に32銀行すべてを攻撃することは困難です。
処理
「エントロピー」を収集したら、それをクリーンアップし、逆戻りが困難な方法で入力から離す必要があります。SHA / 1/256はこれに適しています。プレーンテキストの脆弱性がないため、速度を上げるためにSHA1(または実際にはMD5でも)を使用できます。収穫するには、完全なエントピーバンクを使用しないでください。output = sha1( String(micros()) + String(bank[0]) + [...] );
また、エントロピーバンクの変更がない場合、常に異なる出力に「塩」を追加して、同一の出力を防止します。低蓄積された耳鼻咽喉科、およびその他の一般的な問題。
タイマー入力を使用するには、それらを不確定にする必要があります。これは単純delayMicroseconds(lastSample % 255)
です; 予測できない時間を一時停止し、「連続した」クロック読み取りの差を不均一にします。if(analogRead(A1)>200){...}
A1がうるさい場合、または動的入力にフックされている場合、のように半規則的に行います。フローの各分岐を判別するのがかなり困難になると、逆コンパイル/リッピングされた出力での暗号分析を防ぐことができます。
本当のセキュリティとは、攻撃者がシステム全体を知っていて、それを克服するのに無力な場合です。
最後に、作業内容を確認してください。ENT.EXE(nix / macでも利用可能)を使用して出力を実行し、適切かどうかを確認します。最も重要なのは、カイ二乗分布で、通常は33%〜66%のはずです。1.43%、99.999%、またはそのようなエッジの効いた、連続した複数のテストを取得した場合、ランダムはくだらないです。また、エントロピーENTレポートを1バイトあたり可能な限り8ビットに近いものにする必要があります。
TLDR:最も簡単なフールプルーフ方法は、ESP8266のHWRNGを使用することです。速く、均一で、予測不可能です。Ardunioコアを実行しているESP8266で次のようなものを実行し、シリアルを使用して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"
}
**編集
これは私がしばらく前に書いたベアボードHWRNGスケッチで、単なるコレクターとしてではなく、シリアルポートから吐き出されるCSPRNG全体として動作します。プロミニ向けに構築されていますが、他のボードに簡単に適応できるはずです。フローティングアナログピンだけを使用することもできますが、それらに何かを追加することをお勧めします。マイク、LDR、サーミスタ(室温付近で最大に広がるようにトリミングされている)、さらには長いワイヤのように。耳障りな音さえあれば、耳鼻咽喉科でかなりうまくいきます。
このスケッチには、回答とフォローアップコメントで述べたいくつかの概念が統合されています。エントロピーの蓄積、理想的ではないエントロピーのオーバーサンプリングによるストレッチ(フォンノイマンによるとクール)、均一性へのハッシングです。エントロピー品質の推定は行わず、「おそらく動的なものをギミックする」ことと、暗号プリミティブを使用して混合することを優先します。
// 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()
私の経験から、analogRead()
フローティングピンのエントロピーは非常に低くなっています。呼び出しごとに1ビットまたは2ビットのランダム性があります。あなたは間違いなくより良いものが欲しいです。per1234の回答で提案されているように、ウォッチドッグタイマーのジッターは、適切な代替手段です。ただし、非常に遅いレートでエントロピーを生成します。これは、プログラムの開始時に必要な場合に問題になる可能性があります。dandavisにはいくつかの優れた提案がありますが、通常はESP8266または外部ハードウェアが必要です。
まだ言及されていない興味深いエントロピーソースが1つあります。それは、初期化されていないRAMの内容です。MCUの電源を入れると、そのRAMビットの一部(たぶん最も対称的なトランジスタを持つもの)がランダムな状態で起動します。このhackadayの記事で説明したように 、これはエントロピーソースとして使用できます。コールドブートでのみ利用できるため、初期エントロピープールを満たすために使用できます。その後、他の低速ソースから定期的に補充します。このようにして、プールがゆっくりいっぱいになるのを待たずに、プログラムが作業を開始できます。
これは、AVRベースのArduinoでこれをどのように収集できるかの例です。以下のコードスニペットは、後でフィードするシードを構築するためにRAM全体をXORしsrandom()
ます。トリッキーな部分は、Cランタイムが.dataおよび.bssメモリセクションを初期化する前にハーベストを行う必要があり、Cランタイムが上書きしない場所にシードを保存する必要があることです。これは、特定のメモリセクションを使用して行われ
ます。
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);
}
ウォームリセットでは、SRAMが保持されるため、エントロピープールの内容全体が保持されることに注意してください。この同じコードを使用して、リセット後も収集されたエントロピーを保持できます。
編集:ローカルの代わりにseed_from_ram()
グローバルで機能する私の最初のバージョンの問題を修正しました。これにより、シードがそれ自体とXORされ、これまでに収集されたすべてのエントロピーが破壊される可能性があります。random_seed
seed
analogRead()
自分が何をしているのかを知っていれば、あなたは使用できることについて正しいです。プールのエントロピーの推定値を更新するときに、そのランダム性を過大評価しないように注意する必要があります。私のポイントはanalogRead()
、主に貧しいまだの批判として意味されることが多い繰り返さ「レシピ」:randomSeed(analogRead(0))
一度だけでsetup()
、それの十分を前提としています。
analogRead(0)
呼び出しごとに1ビットのエントロピーがある場合、繰り返し呼び出すと、エントロピーの10000/8 = 1.25 KBytes /秒、エントロピーライブラリの150倍になります。
エントロピーを実際に必要とせず、スタートアップごとに異なる擬似乱数のシーケンスを取得したいだけであれば、EEPROMを使用して連続したシードを反復処理できます。技術的にはプロセスは完全に確定的ですが、実際randomSeed(analogRead(0))
には、接続されていないピンよりもはるかに優れているため、多くの場合、0または1023の同じシードで開始します。EEPROMに次のシードを保存すると、別の毎回シード。
#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);
}
実際のエントロピーが必要な場合は、クロックドリフトから収集するか、外部ノイズを増幅して収集します。また、多くのエントロピーが必要な場合、外部ノイズが唯一の実行可能なオプションです。ツェナーダイオードは、特に5〜6Vを超える電圧源がある場合によく選択されます(適切なツェナーダイオードでも5Vで動作しますが、エントロピーが少なくなります)。
(ソース)。
アンプの出力はアナログピンに接続する必要があります。アナログピンはそれぞれanalogRead()
最大数10 MHz のエントロピーを数ビット生成します(Arduinoがサンプリングできるよりも高速です)。