WEBKT

在资源受限的嵌入式设备上,如何高效采集环境熵生成高质量随机数种子?

33 0 0 0

老王我浸淫嵌入式领域多年,深知在那些“螺蛳壳里做道场”的设备上,哪怕是一个小小的随机数生成,也可能成为安全性和性能的瓶颈。尤其是在缺乏硬件真随机数发生器(TRNG)的MCU上,如何从环境中“榨取”出高质量的熵,并将其混合成一个可靠的随机数种子,这绝对是个硬核技术活。今天就来跟大家聊聊我的经验。

一、为啥非得“折腾”环境熵?

很多嵌入式设备的安全功能(如TLS握手、密钥生成、设备ID等)都需要高质量的随机数。如果用伪随机数发生器(PRNG)但没有一个真正随机的初始种子,那它生成的序列就是可预测的,安全性就无从谈起。而专用的TRNG往往成本较高,或不是所有MCU都集成,所以,从运行环境的“噪音”里寻找熵,就成了我们嵌入式工程师的必修课。

二、核心熵源及其采集技巧

1. 模拟数字转换器(ADC)噪声

几乎所有MCU都有ADC。即便输入端接地或连接到一个稳定的电压,ADC转换结果的最低几位(LSB)仍然会表现出随机抖动,这正是我们想要的熵。

  • 采集方法:
    • 将一个ADC输入引脚接地或连接到一个稳定的参考电压。
    • 以高频率(例如,每秒数千次)连续进行ADC转换。
    • 只取每次转换结果的最低几位(例如,1位或2位),因为高位往往比较稳定,熵值较低。
    • 将这些低位收集起来,构成原始熵流。
  • 实现细节:
    • 可以配置ADC为DMA模式,让DMA自动将转换结果存入内存缓冲区,减少CPU干预。
    • 在中断服务程序(ISR)中触发ADC转换或处理DMA完成中断,但ISR中处理逻辑要尽可能精简。
    • 注意事项: 确保ADC参考电压稳定,避免外部电磁干扰过大影响结果的随机性。

2. 定时器(Timer)抖动

MCU的定时器在理想情况下是精确的,但实际上,系统中断、总线竞争、指令缓存等因素都会导致定时器中断或事件触发时机出现微小的、不可预测的抖动(Jitter)。这些抖动是宝贵的熵源。

  • 采集方法:
    • 配置一个高分辨率的定时器(例如,系统滴答定时器或一个通用定时器),使其以固定频率(例如,每毫秒)触发中断。
    • 在中断服务程序(ISR)中,读取另一个更高分辨率的自由运行定时器的计数值。
    • 将当前计数值与上一次中断时的计数值进行比较,计算出两次中断的实际时间间隔。
    • 这个实际时间间隔与预期时间间隔之间的差值(即抖动)就是熵的来源。只取差值的最低几位。
  • 实现细节:
    • 确保用于测量抖动的定时器频率远高于作为参考的定时器频率,才能捕捉到微小的抖动。
    • ISR必须极短,避免引入新的、可预测的抖动。
    • 注意事项: 初始启动时可能抖动不明显,需要运行一段时间才能收集到足够的熵。如果系统负载稳定,抖动可能会减少,需要持续监测。

3. 其他潜在熵源(按需考量)

  • 未初始化RAM内容: 启动时读取未初始化的RAM区域,可能包含随机噪声。但通常只能用一次,且熵质量不可靠。
  • 外部中断时序: 用户按键、网络包到达时间等。这些事件通常频率不高,但其不确定性很高。
  • 内部RC振荡器频率漂移: 受温度、电压等影响,但通常变化缓慢,熵率较低。

三、熵池与高效混合算法

收集到的原始熵通常是低质量的,可能存在偏斜,需要经过混合(conditioning)才能生成高质量的种子。这里推荐使用“熵池”机制,将收集到的熵源源不断地加入一个缓冲池进行混合。

1. 熵池(Entropy Pool)概念

熵池是一个固定大小的内存区域,用于存储和混合来自各种熵源的数据。每次有新的熵位进入,它就会与池中的现有内容进行混合。

2. 高效混合算法

在资源受限设备上,既要保证混合效果,又要控制计算开销。

  • 首选:加密哈希函数 (如 SHA-256)

    • 优点: 提供了极强的单向性和扩散性,即使输入熵很小,也能输出一个高质量、看起来完全随机的散列值。被广泛认为是混合熵的最佳实践。
    • 实现: 将熵池设计为一个SHA-256的上下文结构(sha256_ctx)。每当有新的熵数据(例如,一个字节或一个字)到来时,将其作为输入更新哈希上下文。
      // 伪代码
      sha256_ctx entropy_pool_ctx;
      void init_entropy_pool() {
          sha256_init(&entropy_pool_ctx);
      }
      void add_entropy(uint8_t new_entropy_byte) {
          sha256_update(&entropy_pool_ctx, &new_entropy_byte, 1);
      }
      void get_seed(uint8_t *seed_buffer) {
          sha256_ctx temp_ctx = entropy_pool_ctx; // 复制上下文,避免影响后续熵收集
          sha256_final(&temp_ctx, seed_buffer); // 输出256位(32字节)种子
      }
      
    • 资源考量: SHA-256的计算量相对较大,内存占用约几十到一百多字节(取决于具体实现)。对于一些极度受限的MCU(如8位单片机),可能需要考虑更轻量级的替代方案。然而,对于主流的Cortex-M系列MCU,SHA-256通常是可接受的开销,尤其是在不频繁调用时。
  • 轻量级替代方案:循环冗余校验(CRC)

    • 优点: 计算速度极快,硬件通常有CRC加速器,内存占用极小。
    • 缺点: 不具备加密安全性,不能抵抗恶意攻击。其混合效果不如哈希函数。只能在对安全性要求不是极高,且资源极端受限的场景下作为妥协方案。
    • 实现: 利用MCU内置的CRC模块,将收集到的熵数据不断喂给CRC计算,并将CRC寄存器的值作为熵池状态。
      // 伪代码
      uint32_t entropy_crc_accumulator = 0xFFFFFFFF; // CRC初始值
      void add_entropy_crc(uint8_t new_entropy_byte) {
          // 假设使用STM32 HAL库的CRC
          entropy_crc_accumulator = HAL_CRC_Accumulate(&hcrc, &new_entropy_byte, 1);
          // 或者手动实现CRC计算
      }
      uint32_t get_seed_crc() {
          return entropy_crc_accumulator; // 输出32位种子
      }
      
    • 建议: 除非SHA-256绝对不可行,否则不推荐单独使用CRC作为熵混合器。可以考虑将CRC作为熵预处理步骤,然后用一个更强的混合器(如轻量级分组密码算法)再次混合。

四、高质量种子生成与CSPRNG

从熵池中提取的混合结果就是我们的高质量种子。接下来,我们用它来初始化一个加密安全的伪随机数发生器(CSPRNG)

  • 选择CSPRNG:
    • HMAC-DRBG: 基于HMAC的确定性随机比特生成器,是NIST推荐的标准之一,安全性高。
    • CTR_DRBG: 基于分组密码(如AES)计数器模式的DRBG,也是NIST推荐。如果MCU有硬件AES加速,这是个不错的选择。
    • ChaCha20: 流密码,可作为DRBG使用,速度快,内存占用小,安全性高。
  • 初始化与重播种:
    • 初始化: 在设备启动时,收集足够多的熵(通常需要几百到上千个原始熵位,或者在熵池中累积一定时间),然后从熵池中提取种子,初始化CSPRNG。
    • 重播种(Reseeding): CSPRNG本身是确定性的。虽然它可以生成很长的随机序列,但为了对抗长期攻击或熵源的退化,我们需要定期或在熵池积累了足够新的熵时,用熵池中的新熵再次给CSPRNG重播种。这能有效增强随机性。

五、实现细节与最佳实践

  1. 中断上下文与临界区: 熵收集通常在中断中进行,要特别注意ISR的执行时间,避免阻塞其他任务。访问共享的熵池数据时,务必使用互斥锁或禁用中断来保护临界区。
  2. 启动阶段的熵收集: 设备刚启动时,环境熵可能不足,需要耐心收集一段时间。在此期间,如果需要随机数,可以先使用一个临时、低质量的种子,或者干脆等待足够熵后再进行安全操作。
  3. 熵的“去偏”: 原始熵源可能不是均匀分布的(例如,某个位0的概率高于1)。可以通过简单的哈希函数(如SHA-256本身就具备去偏效果)或Von Neumann去偏器(每两位输入生成一位输出,舍弃相同位)来提高熵质量。
  4. 熵估算(可选): 尽管在嵌入式设备上精确估算熵非常困难,但大致了解熵的生成速率能帮助我们判断何时可以安全地从熵池中提取种子。一些简单的统计测试(如频率测试、游程测试)可以提供参考。
  5. 设计模块化: 将熵源驱动、熵池混合器、CSPRNG等功能模块化,便于测试、替换和维护。

总结

在资源受限的嵌入式设备上构建高质量的随机数生成器,是一项充满挑战但至关重要的任务。通过巧妙利用ADC噪声、定时器抖动等环境熵源,结合SHA-256等加密哈希函数进行高效混合,再辅以成熟的CSPRNG,我们完全可以在有限的硬件条件下,为设备提供满足安全需求的随机性。这背后,是嵌入式工程师对硬件特性的深刻理解和对安全架构的精妙设计。希望这些经验能对大家有所启发!

硬核老王 嵌入式安全随机数生成熵池

评论点评