RNG:生成随机数并理解随机性的用途

2026-09-10 00:00    #ESP32-S3   #ESP-IDF   #外设   #阶段3  

本课对应《DNESP32S3 使用指南(IDF 版)》第 31 章。配套完整工程:examples/26-rng。

环境与验收边界: DNESP32S3 V1.2,ESP-IDF v5.5.5;本机 macOS 构建通过。Ubuntu 26、外设接线、屏幕效果和实际测量仍待实板验收。下文输出均为预期格式,不能当作已采集的硬件日志。

本课完成后能做什么

硬件连接与资源

信号或资源对应关系
外设ESP32-S3 RNG 与本例临时启用的内部熵源
外部引脚无
资源约束不同时运行 ADC、Wi-Fi 或蓝牙示例

本课信号与数据处理流程

不同用途对随机数的要求不一样

游戏骰子关注分布是否合理;协议 nonce 还需要满足协议规定的唯一性或不可预测性;长期密钥涉及足够熵、秘密存储和完整安全设计。不能因为串口输出“看起来很乱”,就断言它适合生成密钥。

ESP32-S3 的随机数保证与硬件熵源状态有关。本例在没有 ADC、Wi-Fi、蓝牙活动的独立工程中,通过 bootloader_random_enable() 临时启用内部熵源,采样结束后 disable。不要把这段初始化直接搬进已有无线或 ADC 任务的综合程序。具体约束以官方随机数文档和目标芯片为准。

为什么 random % 6 存在微小偏差

32 位无符号数有 2^32 种取值,这个数量除以 6 的余数是 4。直接取模时,有几个点数会比其他点数多对应一个输入。

代码计算 limit = -bound % bound,无符号运算下 bound=6 时得到 4;遇到 0~3 重新取样,剩余区间大小可被 6 整除,再对 6 取模。这里要求 bound>0,示例固定为 6。

统计实验能证明什么,不能证明什么

程序取 6000 次样本,打印各点数次数。每个点数的次数接近 1000,但不应恰好等于 1000,也不要求每个字节在各次运行中都不同。有限样本只用于理解分布,不能证明加密安全。

打印的 16 个随机字节是演示数据,绝不能把真实密钥或身份凭证照此输出日志。工程每 100 次让出一次 CPU,避免把随机数统计写成长期独占核心的循环。

工程如何组织

本例围绕一个系统外设展开,先在 main/main.c 中按顺序展示完整调用过程;可复用的多设备驱动在后续工程中放到 components/BSP/。不要把芯片驱动的初始化藏在无法看到的外部脚本里。

应用代码:入口与处理流程

下面代码直接取自配套工程的 main/main.c,头文件和 BSP 实现在仓库中一并提供。初学时先看 app_main(),再追踪它调用的模块。

 1#include <stdio.h>
 2#include <stdint.h>
 3#include <stdbool.h>
 4#include <string.h>
 5#include <stdlib.h>
 6#include <assert.h>
 7#include "esp_err.h"
 8#include "esp_log.h"
 9#include "esp_timer.h"
10#include "freertos/FreeRTOS.h"
11#include "freertos/task.h"
12#include "esp_random.h"
13#include "bootloader_random.h"
14// 拒绝采样消除直接取模的偏差,bound 必须大于 0。
15static uint32_t uniform(uint32_t bound)
16{
17    uint32_t limit=-bound%bound,r;
18    do { r=esp_random(); } while(r<limit);
19    return r%bound;
20}
21void app_main(void)
22{
23    // 本例独占内部 ADC 熵源;不要与 ADC、Wi-Fi、蓝牙示例同时运行。
24    bootloader_random_enable();
25    uint8_t bytes[16];
26    esp_fill_random(bytes,sizeof(bytes));
27    ESP_LOG_BUFFER_HEX("random_demo",bytes,sizeof(bytes)); // 仅演示字节,绝不能打印真实密钥。
28    unsigned bins[6]={0};
29    for(int i=0;i<6000;i++) {
30        bins[uniform(6)]++;
31        if(i%100==0) vTaskDelay(pdMS_TO_TICKS(1));
32    }
33    bootloader_random_disable(); // 停止内部熵源后,不再声称后续输出有同等熵保证。
34    for(int i=0;i<6;i++) ESP_LOGI("dice","face=%d count=%u",i+1,bins[i]);
35}

CMake 与中文注释

1# 私有依赖只供本组件源码使用。
2idf_component_register(SRCS "main.c" INCLUDE_DIRS "."
3    PRIV_REQUIRES esp_timer bootloader_support)

每个工程有自己的 project() 名称和 sdkconfig.defaults。VS Code 已关闭普通 CMake 自动配置,C/C++ 语言服务从 build/compile_commands.json 读取编译信息。首次构建前,这个文件还不存在,需要先激活 IDF 并完成构建,无需手工拼接头文件路径。

编译、烧录与观察

 1# 激活 EIM 安装的 IDF;每个新终端都需要激活。
 2source ~/.espressif/tools/activate_idf_v5.5.5.sh
 3# 进入单个示例根目录,不是在 examples 总目录运行。
 4cd ~/mycode/esp32-blog/examples/26-rng
 5# 首次配置目标;已有正确配置时不用每次重复 set-target。
 6idf.py set-target esp32s3
 7# 编译会生成 build/compile_commands.json,供 VS Code 使用。
 8idf.py build
 9# 把这个值换成实际连接开发板的端口;macOS 通常为 /dev/cu.*。
10PORT=/dev/ttyUSB0
11idf.py -p "$PORT" flash monitor
12# 串口监视退出:Ctrl+]。

预期输出或屏幕内容(示意;数值、日志前缀和启动时间依现场而变):

1dice: face=1 count=<本轮统计>
2...
3dice: face=6 count=<本轮统计>

故障定位与逐项验收

先确认目标为 esp32s3,再检查实际端口、供电,并按本文引脚表核对跳线。编译成功只能说明源码与工具链兼容,模块型号、物理线路和时序仍需实板确认。

遇到通信错误,先检查错误码和对应模块,不要用“错误时返回 0”掩盖异常。文中的预期日志只供阅读程序时参考;实板验收时,应记录实际现象,保存日志和接线照片。

与教材和官方实现对照

对照教材第 31 章正文:增加熵源生命周期、资源冲突说明和无模偏差取样;不把统计结果当成安全认证。

本地验证记录与参考

返回课程目录