在Arduino中获得真实(而不是伪)随机数的最佳方法是什么,或者至少是最佳近似方法是什么?据我了解,函数randomSeed(analogRead(x))不够随机。
如果可能的话,该方法应仅利用基本的Arduino设置(无需额外的传感器)。如果使用外部传感器的解决方案大大改善了基本设置的随机性,则欢迎使用。
在Arduino中获得真实(而不是伪)随机数的最佳方法是什么,或者至少是最佳近似方法是什么?据我了解,函数randomSeed(analogRead(x))不够随机。
如果可能的话,该方法应仅利用基本的Arduino设置(无需额外的传感器)。如果使用外部传感器的解决方案大大改善了基本设置的随机性,则欢迎使用。
Answers:
该熵库的用途:
看门狗定时器的自然抖动,以产生可靠的真实随机数流
我喜欢这个解决方案,因为它不占用您微控制器的任何引脚,也不需要任何外部电路。这也使得它较少受到外部故障的影响。
除了一个库,他们还提供了一个草图,演示了不使用库就可以为微控制器的PRNG生成随机种子的相同技术的用法:https : //sites.google.com/site/astudyofentropy/project-definition /计时器抖动熵源/熵库/ arduino随机种子
randomSeed(analogRead(x))
只会产生255个数字序列,这使得尝试所有连击并产生可以耦合到您的输出流的预言机变得微不足道,从而可以预测所有输出100%。但是,您的方向正确,这只是一个数字游戏,您还需要更多。例如,从4个ADC进行100次模拟读取,将它们全部求和,然后馈给它们randomSeed
会更好。为了获得最大的安全性,您既需要不可预测的输入又需要不确定的混合。
我不是密码学家,但是我花了数千个小时研究和构建硬件和软件随机生成器,所以让我分享一些我学到的知识:
不可预测的输入:
潜在的不可预测的输入:
外部不可预测的输入:
RANDOM_REG32
极快且不可预测的一站式收集
您想要做的最后一件事就是随即吐出熵。猜测硬币翻转比一桶硬币容易。总结是好的。unsigned long bank;
那以后bank+= thisSample;
好 它会翻转。bank[32]
更好,请继续阅读。您希望为每个输出块至少收集8个输入样本,最好是更多。
防止中毒 如果加热电路板会导致一定的最大时钟抖动,这就是攻击向量。与将RFI吹向AnalogRead()输入相同。另一种常见的攻击是拔掉设备的电源,从而丢弃所有累积的熵。您不应该输出数字,除非您知道这样做是安全的,即使这样做会牺牲速度。
这就是为什么您要使用EEPROM,SD等使熵长期保持不变的原因。研究Fortuna PRNG,它使用32个存储库,每个存储库的更新频率是之前存储库的一半。这使得很难在合理的时间内攻击所有32个银行。
处理
一旦收集到“熵”,就必须清除它,并以一种难以逆转的方式将其与输入分离。SHA / 1/256对此很有用。由于没有明文漏洞,因此可以使用SHA1(甚至是MD5)来提高速度。收获时,切勿使用完整的底物库,并且始终在每次输出时都添加一个“盐”,以防止相同的输出,因为没有熵库变化:output = sha1( String(micros()) + String(bank[0]) + [...] );
sha功能既可隐藏输入也可白化输出,以防止种子变弱,耳鼻喉科的累积率低,以及其他常见问题
要使用计时器输入,需要使它们不确定。这很简单delayMicroseconds(lastSample % 255)
;这会暂停不可预测的时间,从而使“连续”时钟读取的差异不一致。像if(analogRead(A1)>200){...}
A 一样,半定期执行此操作,前提是A1嘈杂或挂接到动态输入。使流程的每个分支很难确定将阻止对反编译/翻录输出的加密分析。
真正的安全是当攻击者知道您的整个系统并且仍然无能为力时才能克服它。
最后,检查您的工作。通过ENT.EXE(也可用于nix / mac)运行您的输出,并查看它是否有用。最重要的是卡方分布,通常应在33%至66%之间。如果您获得1.43%或99.999%或类似的毛病,连续进行多个测试,那么您的随机性就是垃圾。您还希望熵ENT报告每字节尽可能接近8位,肯定大于7.9。
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,热敏电阻(在室温下最大程度地散布),甚至是长电线。如果您有适度的噪音,它在ENT中的效果也很好。
该草图整合了我在回答和后续评论中提到的几个概念:积累熵,通过对不理想的熵进行过采样来拉伸(冯·诺伊曼说这很酷)以及哈希化为均匀性。它放弃了熵质量估计,而支持“给动态添加任何可能的东西”,并使用密码原语进行混合。
// 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()
在浮动引脚上的熵非常低。每次通话可能只有一两个比特的随机性。您肯定想要更好的东西。根据per1234的回答,看门狗定时器的抖动是一个很好的选择。但是,它以非常慢的速率生成熵,如果在程序启动时需要它,这可能是一个问题。dandavis有很多不错的建议,但它们通常需要ESP8266或外部硬件。
有一个有趣的熵源尚未被提及:未初始化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()
在全局版本random_seed
而不是本地版本上工作的最初版本中的一个问题seed
。这可能导致种子与其自身进行异或运算,从而破坏了迄今为止收获的所有熵。
analogRead(0)
每个调用具有1位熵,那么重复调用将产生10000/8 = 1.25 KB /秒的熵,是熵库的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()
最高十兆赫兹(比Arduino采样速度快)。