在无硬件TRNG的Cortex-M0上构建安全PRNG:熵源利用与实现方法
36
0
0
0
在资源受限的Cortex-M0微控制器上,构建一个用于生成加密密钥和初始化向量(IV)的伪随机数生成器(PRNG)是一项常见的安全挑战,尤其是在缺乏硬件真随机数生成器(TRNG)的情况下。虽然软件PRNG无法提供与硬件TRNG同等级别的熵,但通过精心设计和利用可用的熵源,可以构建一个在许多应用场景中足够安全的解决方案。本文将探讨几种常见的熵源利用方法,并给出一个结合哈希和熵池的实现思路。
1. 熵源选择与评估
在Cortex-M0上,可用的熵源通常质量不高且可能相关,因此需要混合使用并进行后处理。
- 内部ADC噪声:这是最常用的熵源之一。通过多次采样内部参考电压或未连接的引脚,可以获取噪声数据。然而,ADC噪声可能具有相关性,且在某些温度或电压条件下可能变得可预测。建议进行多次采样(例如,每次采样之间加入延迟)并进行去相关处理。
- 未初始化的RAM内容:在系统复位后,SRAM的内容是随机的(取决于上电特性)。这可以作为一个很好的初始熵源。但要注意,如果系统在运行中多次复位,且复位源相同,RAM内容可能呈现某种模式。可以定期(如每次启动时)收集并混合这些数据。
- 定时器抖动:利用高精度定时器的计数值,特别是当其由内部RC振荡器驱动时,会存在抖动。通过读取定时器计数器在特定事件(如按键中断、ADC转换完成)前后的值,可以捕获这种抖动。需要注意的是,如果定时器由稳定的晶振驱动,抖动可能非常小。
- 其他来源:如通信接口(UART、SPI)接收数据的时序、电源电压的微小波动(如果ADC支持)等,但这些通常更难以利用且相关性更强。
2. 熵池与后处理
单一熵源不可靠,因此需要建立一个熵池,并使用密码学安全的哈希函数(如SHA-256,但需考虑Cortex-M0的计算能力;对于资源极紧张的系统,可考虑轻量级哈希如SHA-3的Sponge构造或BLAKE2s)进行后处理,以“提炼”熵并生成均匀的输出。
基本步骤:
- 熵收集:周期性地从上述多个熵源收集原始数据,并将其混合到一个熵池中。例如,可以使用一个循环缓冲区来存储这些数据。
- 熵估计:对于每个熵源,进行简单的熵估计(例如,计算数据的最小熵)。但这在资源受限设备上可能过于复杂,一个更实用的方法是固定混合所有收集的数据。
- 哈希提取:定期(或当熵池累积到一定量后)对熵池数据应用哈希函数,将哈希输出的一部分作为PRNG的种子或直接作为随机数输出。哈希函数的单向性和抗碰撞性有助于将低质量的熵转化为高质量的伪随机输出。
3. PRNG算法选择
对于生成加密密钥和IV,需要的是密码学安全的伪随机数生成器(CSPRNG)。在Cortex-M0上,推荐使用基于分组密码或哈希函数的CSPRNG。
- 基于哈希的DRBG(确定性随机比特生成器):例如,使用HMAC-DRBG或CTR-DRBG的简化版本。核心思想是维护一个内部状态(种子),每次请求随机数时,用哈希函数更新状态并输出一部分。
- 示例流程(简化版):
- 初始化:使用上述熵池方法生成一个初始种子
S0。 - 生成随机数:
R = Hash(S || Counter),其中S是当前状态,Counter是一个递增的计数器。然后更新状态S = Hash(S || R)。输出R的一部分作为随机数。 - 重播种:定期(例如,每生成一定数量的随机字节后)用新的熵源数据重新初始化或混合种子,以防止状态泄露后的长期影响。
- 初始化:使用上述熵池方法生成一个初始种子
4. 实现注意事项与安全警告
- 性能与资源:Cortex-M0计算能力有限,SHA-256可能较慢。评估是否可接受,或考虑使用更轻量级的算法(如SHA-3的Keccak,或特定于嵌入式的轻量级密码学原语)。
- 熵源监控:在系统启动时,确保有足够的熵源数据被收集。如果熵不足,应延迟关键加密操作(如密钥生成)或发出警告。
- 侧信道攻击:在软件实现中,定时攻击是真实威胁。确保哈希计算和状态更新的代码路径尽可能恒定时间,避免基于数据的分支。
- 种子管理:种子(状态)必须保密,并妥善存储(如加密存储)。一旦种子泄露,所有生成的随机数都可预测。
- 合规性:对于需要FIPS 140-2或类似认证的场景,软件PRNG通常不被认可。请务必评估应用场景的风险等级。
5. 一个简单的实现框架示例(伪代码)
// 假设有一个轻量级哈希函数 `void hash(const uint8_t *input, size_t len, uint8_t *output)`
// 假设有一个熵收集函数 `void collect_entropy(uint8_t *buffer, size_t len)`
uint8_t prng_state[32]; // 256位状态
uint8_t counter[8]; // 64位计数器
uint8_t entropy_buffer[32];
void prng_init() {
collect_entropy(entropy_buffer, 32);
hash(entropy_buffer, 32, prng_state); // 初始种子
memset(counter, 0, 8);
}
void prng_reseed() {
collect_entropy(entropy_buffer, 32);
// 混合新熵到状态中
uint8_t temp[32];
hash(prng_state, 32, temp);
for(int i=0; i<32; i++) prng_state[i] ^= entropy_buffer[i] ^ temp[i];
}
void prng_generate(uint8_t *output, size_t len) {
// 简单示例:每次生成一个哈希块
// 实际中需要更复杂的DRBG结构
uint8_t temp[32];
while(len > 0) {
// 生成随机数据
uint8_t data[40]; // 状态+计数器
memcpy(data, prng_state, 32);
memcpy(data+32, counter, 8);
hash(data, 40, temp);
// 输出部分
size_t to_copy = (len < 32) ? len : 32;
memcpy(output, temp, to_copy);
output += to_copy;
len -= to_copy;
// 更新计数器和状态
increment_counter(counter, 8);
// 简单更新状态:新状态 = Hash(旧状态 || 计数器)
hash(prng_state, 32, temp);
memcpy(prng_state, temp, 32);
}
}
总结
在Cortex-M0上构建安全的PRNG,关键在于混合多个弱熵源并使用密码学哈希函数进行后处理。虽然无法达到硬件TRNG的安全级别,但对于许多非最高安全等级的应用(如物联网设备会话密钥生成),这种方案是可行且实用的。务必进行充分的测试和评估,确保熵源在不同环境和条件下都能提供足够的随机性,并时刻关注最新的密码学实践和漏洞信息。
注意:本文提供的信息仅供教育和参考目的。在实际生产环境中部署加密系统前,应咨询安全专家并进行严格的第三方安全评估。