进行模拟“真实世界”测量通常是entropy(又名真实随机数据)的良好来源。模拟源总是会叠加一些不可预测的噪声,这些噪声可以被“捕获”。然而,如前所述,测量数据很少是无偏的。
模拟电气测量也可能或多或少容易受到无法控制的影响甚至来自外部的攻击,例如通过导致传感器饱和。 EMI 也可能会干扰测量;在通话期间将普通手机放置在合理靠近电路的位置很可能会对任何模拟信号造成严重破坏。
偏差
无偏、均匀分布的高熵数通常是人们想要的,因为它们的属性(不是值)在某种程度上被归一化,因此可以更可靠地预测.
当以 10 位分辨率测量模拟输入时,理想情况下,从测量中收集的数字范围将涵盖从 0 到 1024 的所有值,并且每个值的出现频率(或概率)与来自那个范围。
实际上,这些值通常(或多或少)正态分布(高斯分布)围绕具有某些特征标准偏差的某个平均值,例如每个样本大约 500 @ 10 位。
去偏
因此,为了生成具有所需属性的随机值(见上文),需要进行一些去偏操作:需要某种randomness extractor。
使用加密函数,如(单向)哈希函数或密码算法,通常也很容易产生所需的结果;但是这是有代价的:这些功能的“混合”在设计上是非常强大的,这使得在转换后无法确定源数据的质量(=随机性)。即使是最简单的确定性值序列(例如 {1,2,3,4,5...}全部。
在 µC 上生成熵
时间
在微控制器环境中,很少想到的一个很好的熵来源是事件的时间。使用高速计时器,可以通过读取计时器值以响应某些异步事件来收集真正的熵。这种与运行定时器无关的事件可能是用户按下按钮、另一个(子)系统或 IC 启动的通信开始,或者基本上任何其他未由 µC 触发的事件(或任何同步子系统)本身。
事实上,熵甚至可以从两个独立时钟源中获取;例如,通过另一个时钟对一个时钟的计时周期进行计时。这为 Atmel AVR µC(在 Arduino 中使用)打开了几个非常有趣的可能性,具体取决于 µC 的功能:
-
大多数 AVR 都有内部 EEPROM 存储器。对该内存的写操作由一个独立于主系统时钟的专用计时器计时(-据报道有一些芯片(不是类型!)测量表明情况可能并非如此)(编辑:请注意,在某些 AVR 中,例如 ATTiny25/45/85,EEPROM 时序来自内部 RC 振荡器,因此当该振荡器也被选为系统时钟源时不会收集熵);这可能取决于主时钟源(内部 R/C 与外部晶体/谐振器)。因此,相对于主系统时钟,在写入 EEPROM 所需的时间内,预计会有一些(真正随机的)抖动,这又可以通过高速定时器/计数器来测量。
较新的 AVR 能够让看门狗定时器产生软件中断而不是硬件复位。看门狗定时器在设计上由其自己的独立时钟源控制,从而产生可以测量的相对抖动。
许多 AVR 都能够使用由外部 32kHz 晶振提供时钟的专用定时器/计数器,以提高实时测量的准确性。这个外部晶体是与主时钟不相关的另一个事件源。 (不然一开始多出来的水晶就没用了……)
后者似乎很有希望,因为它具有相对较高带宽的潜力:当使用运行速度明显更快的系统定时器对每个 32kHz 时钟周期进行计时时(在当前的 AVR @ 20 MHz 上可以实现 600+ 倍!)并且保守地假设每次测量只有 1 位熵,这导致 每秒 32000+ 位熵 - 远远超过一个 µC 自身消耗的量。
编辑:同时,我对 32kHz 定时器方法进行了一些简单的测试,短期结果似乎质量很低。每个样本生成的熵的上限似乎非常低,而我什至没有测试样本是否存在源自或多或少规则相移的非明显模式。这个结果可能是由于我的 DUT 的主时钟由外部晶体驱动,当在有限的时间范围内观察时,可以预期(在测量精度内)与 32kHz 石英的频率相同。延长两个样本之间的时间(分钟?)可能会返回良好的熵,但带宽非常低。 (注意:测量到的抖动也可能部分是由于中断延迟不同,具体取决于触发中断时执行的机器指令。)
编辑 #2:我的 DUT (ATmega1284) 的内部 RC 振荡器似乎会产生明显的频率抖动(几 kHz/s);当由外部 32kHz 晶体计时时,在这个振荡器上运行确实似乎产生了相当多的熵 (kBits/s)。
我最近在一个小实验中研究了前两种方法。在我的 DUT 上,EEPROM 时序通常比 WDT 更有利:
定时 EEPROM 写操作在每次写操作产生大约 4.82 位的熵,而看门狗定时器在频率方面似乎更稳定,每次看门狗超时产生大约 3.92 位。此外,EEPROM 的写入时间看起来更加平滑高斯分布,而 WDT 的分布似乎有些不对称并且有很多像差。
N.b.:为单个熵测量聚合多个“随机”事件实际上可能会降低所获得的熵:源中的快速随机波动可能会部分地相互补偿,从而产生与平均值偏差较小的结果值。因此,例如,在同一时间进行 32k 次计时(晶体的 每个 周期一次)可以预期比计时一秒(RTC 晶体的 32k 周期)更多的熵时间。
内存未初始化
Avr-gcc 编译的应用程序通常在执行用户代码之前将整个片上 RAM 清除为 0x00,即main()。将代码放入早期的 .init 部分可以在原始未初始化 RAM 内容被 gcc 的初始化例程覆盖之前访问它。
由于 RAM 的物理存储单元(位)的微小差异以及取决于一些真正的随机热噪声(和其他影响),当电源被(重新)应用于时,并非每个单元都会将自身初始化为相同的已知状态芯片。在上电后立即将芯片 RAM 的内容与某些功能相结合可以产生大量的熵以供以后使用。 - 这样做的缺点是它只能在电源关闭一段时间然后再次打开时才能可靠地工作。通过硬件、软件或外部信号进行的正常芯片重置将保留 RAM 以前的内容,因此不是(总是)一个好的熵源。但是,由于在相当复杂的应用程序中很难预测重置时整个系统 (RAM) 的状态,因此无论如何在重置后可能会立即收集一些熵。
带宽要求
必须根据其带宽和应用程序使用熵的带宽来查看熵源的质量。一些熵收集方法在几秒钟内可能不会产生超过一位的熵,而其他方法(不是真的在 µCs 上......)可能会产生 100 kbit/s 或更多。
必须注意,不能通过算法从现有熵“创建”新熵! - 一位熵不能通过计算转换为两位熵。
因此,一个人(平均而言)每单位时间消耗的(实际)熵不能多于同时从熵源收集的熵。
提案
当需要强随机数时,将一个或多个真实熵源与强 PRNG 组合并不少见,每次新熵可用时,使用收集到的熵来(重新)播种 PRNG。
与熵源在同一时间实际提供的数据相比,PRNG 可用于生成基本不可预测的数据。
作为一种特殊的 PRNG,可以使用加密密码函数,其中熵用于初始化和更新密码的密钥。
Linux 的/dev/urandom 通常是这样实现的。
结论
如上所述,在普通微控制器上生成真正的随机数是非常可行的。对于所有其他熵源,需要分析熵源提供的原始数字,了解它们包含的真实熵量以及每个时间单位产生的熵量,以确定该源是否适合用例与否。
真正的熵源和强 PRNG 的组合是通常实现的方法,也应该在微控制器上使用。
编辑:
PRNG 方法可能不是加密密钥生成的最佳选择。为此,应该只使用真正的随机位来生成安全密钥。收集这么多的熵可能需要一些时间(可能需要几秒钟),但由于在 µC 上通常不会非常频繁地执行密钥生成,因此这很可能是可以接受的。 (在每秒有数百或更多 SSL (HTTPS) 连接的负载较重的服务器上,这将是另一个问题......)
为了生成适合密钥生成的高质量高熵比特流,应使用上述randomness extractor。
(另一方面,如果可以测量或估计源输出中的熵量,则可以简单地将密钥长度按(bitlength of key)/(entropy per bit sampled) 的因子缩放,然后直接使用来自熵源的原始低熵数据生成这个更长的密钥,然后它将具有与原始长度的完全随机密钥相同的整体熵。但是,如果这确实有效,则取决于密码如何处理不同长度的密钥。)