1. 项目概述:嵌入式安全硬件的基石
在嵌入式系统,尤其是那些涉及支付终端、物联网设备认证、工业控制网络等对安全性有严苛要求的领域,随机数和加密运算的性能与质量直接决定了整个系统的安全底线。很多开发者习惯在软件层面调用标准库的
rand()
或使用 OpenSSL 进行加解密,这在资源受限、实时性要求高的嵌入式场景下往往捉襟见肘。软件实现的伪随机数生成器(PRNG)周期性和可预测性是其固有弱点,而纯软件的 AES 运算则会大量消耗宝贵的 CPU 周期,影响系统响应。
飞思卡尔(现为 NXP 半导体)的 MCF5373 微控制器集成的 随机数生成器(RNG) 和 对称密钥硬件加速器(SKHA) ,正是为解决这些痛点而生的片上硬件模块。RNG 模块并非通过算法生成伪随机数,而是利用芯片内部的物理噪声源(环形振荡器)产生真正的随机性,为加密系统提供高质量的熵。SKHA 模块则是一个专用的协处理器,能够以远高于软件的速度执行 AES、DES、3DES 等标准对称加密算法。
这两个模块的组合,为嵌入式设备构建了一个从密钥生成、密钥交换到数据加密/解密的全链路硬件安全加速方案。理解它们的原理、掌握其驱动方法,是开发高安全等级嵌入式应用的必备技能。本文将深入这两个模块的内部机制,并手把手带你完成从寄存器配置到实际数据处理的完整流程,分享我在实际项目中调试和优化这类硬件模块的实战经验。
2. 硬件随机数生成器(RNG)深度解析
2.1 核心原理:从物理噪声到随机比特
硬件 RNG 的核心价值在于其熵源的非确定性和不可预测性。MCF5373 的 RNG 模块采用了经典的“振荡器采样”架构,其原理可以用一个简单的类比来理解:想象你有两个老旧的机械秒表,它们的走时并不精确,存在微小的、无法预测的快慢波动(即抖动)。你用其中一个秒表不规律的“滴答”声作为时钟,去采样另一个秒表指针的位置。由于两者都是不稳定的,采样得到的结果序列将是完全随机的。
在芯片内部,这个“不稳定的秒表”就是 环形振荡器 。它是由奇数个反相器首尾相连构成的环路,其振荡频率受制于半导体制造工艺、电源电压和环境温度的微小波动,这些波动是物理性的、不可预测的,构成了熵的源头。RNG 模块内部有多个这样的振荡器,它们的输出被用来驱动一个精心设计的 线性反馈移位寄存器 。
注意 :这里有一个关键的安全警告,在芯片手册中被明确标注为“CAUTION”。该架构(线性反馈移位寄存器+振荡器采样)虽然能产生统计特性良好的随机数据,但 缺乏严格的密码学安全性证明 。由于其内部状态可能具有线性特性,如果直接将输出用作加密密钥,理论上可能存在被攻击的风险。因此,手册强烈建议将 RNG 的输出作为种子,输入到一个经过认证的密码学安全伪随机数生成器(CSPRNG),如基于 AES 或 SHA-1 的算法中,以增强其安全性。
2.2 寄存器模型与工作流程
要驱动 RNG 模块,我们需要与四个核心寄存器打交道。理解每个比特位的含义,是正确操作硬件的基础。
RNG 控制寄存器
是大脑。最重要的位是
GO
位。硬件复位后,RNG 内部的熵生成电路就已经开始运行,但数据不会进入输出 FIFO。只有当你将
RNGCR[GO]
置为 1,模块才开始每 256 个时钟周期向 FIFO 推送一个 32 位的随机数。
SLM
位用于低功耗模式,置位后会关闭振荡器以省电。
HA
位则是一个安全增强功能,一旦启用,如果发生 FIFO 下溢(在空的时候执行读操作),就会触发一个粘滞的安全违规标志,只有硬件复位才能清除,这有助于在安全审计中追踪异常操作。
RNG 状态寄存器
是眼睛。
RNGSR[OFL]
字段是驱动编程中最需要关注的。它是一个 8 位值,实时指示输出 FIFO 中存有多少个可用的 32 位随机数。在读取数据前,
必须
先查询此字段,确认
OFL > 0
,否则读空 FIFO 会触发下溢错误。
EI
和
FUF
位分别指示错误中断和 FIFO 下溢事件。
RNG 熵寄存器 是一个有趣的“后门”。它是只写的,允许你将外部熵源(如传感器噪声、用户输入间隔时间等)注入到 RNG 的内部状态中。这是一个可选但推荐的操作,可以进一步提升随机性的质量。每次写入都会以保持熵值不减少的方式更新内部状态。
RNG 输出 FIFO
是手。它是一个 16 字深的先进先出队列。当
OFL
指示有数据时,直接读取
RNGOUT
地址即可获得一个 32 位随机数,读取后
OFL
值减 1。
2.3 实战驱动步骤与避坑指南
根据手册,标准操作流程如下:
- 系统初始化/复位 。
- (可选)写入 RNGER :如果系统有其他熵源,此时可以写入,增强随机性。
-
配置并启动 RNG
:设置
RNGCR。通常需要设置GO=1启动,根据需求决定是否设置HA=1启用高安全告警,以及IM位来屏蔽或使能错误中断。 -
轮询状态,获取数据
:循环读取
RNGSR,检查OFL字段。一旦OFL > 0,即可从RNGOUT读取随机数。 - 重复 :根据应用需要,重复步骤 4。
在实际编码中,我强烈建议将上述过程封装成两个简洁的 API:
// 初始化 RNG 模块
void rng_init(void) {
// 1. 确保模块时钟已使能(查阅芯片系统时钟配置)
// 2. (可选)向 RNGER 写入外部熵,例如读取某个未使用的 ADC 通道值
// 3. 配置 RNGCR: 启动 GO, 根据需要设置 HA, IM
RNGCR = (1 << 0); // 设置 GO=1, 其他位默认0
}
// 获取一个 32 位随机数
uint32_t rng_get_random_word(void) {
// 安全等待,直到 FIFO 中有数据
while ((RNGSR & 0xFF0000) == 0) { // 提取 OFL 字段 (bits 23:16)
// 可以加入超时机制,防止硬件故障导致死循环
}
return RNGOUT; // 读取随机数
}
避坑经验一:务必检查 FIFO 状态
。这是我早期调试时踩过的大坑。在忙循环或中断服务程序中,如果没有检查
OFL
就直接读
RNGOUT
,会立刻触发下溢错误。虽然可能不会导致程序崩溃,但会置位错误状态位,如果使能了中断还会打断程序流程,给调试带来不必要的麻烦。
避坑经验二:理解“熵”与“速度” 。这个硬件 RNG 产生的是“真随机数”,速度受物理过程限制(每256周期一个32位数)。如果你的应用需要大量随机数流(例如用于视频游戏),应该用此模块产生的随机数作为种子,去初始化一个软件端的 CSPRNG(如 ChaCha20 或 CTR_DRBG)。这样既能保证初始熵的强度,又能获得高性能的随机数输出。
避坑经验三:低功耗模式切换
。当系统进入睡眠时,记得设置
SLM
位以关闭振荡器省电。在唤醒后,需要清除
SLM
位,并等待一段时间(具体时间需参考数据手册的启动时间参数)让振荡器稳定,再检查
OFL
或重新启动
GO
。直接唤醒后立即读取,很可能读到全0或无效数据。
3. 对称密钥硬件加速器(SKHA)原理与应用
3.1 算法支持与模式解析
SKHA 模块是一个高度集成的加密协处理器,支持 DES、3DES 和 AES 这三种最主流的对称加密算法。理解每种算法和模式的特点,是正确使用它的前提。
DES 与 3DES :DES 使用 56 位有效密钥(加上8位校验位共64位),加密 64 位数据块。由于其密钥长度在现代算力下已不安全,3DES 被广泛使用。3DES 使用 2 个或 3 个 DES 密钥对数据进行三次加密操作(加密-解密-加密,即 E-D-E)。SKHA 在硬件上实现了完整的 3DES 流程,只需配置为 3DES 模式并输入对应长度的密钥即可,无需软件进行三次调用,效率极高。
AES :这是当前的主流标准,SKHA 支持 AES-128,即使用 128 位密钥处理 128 位数据块。它在硬件内部进行10轮迭代运算,速度远超软件实现。
加密模式 是另一个关键概念,它决定了如何用同一个密钥加密多个数据块:
- ECB 模式 :最简单的模式,每个数据块独立加密。相同的明文块必然产生相同的密文块。这会导致模式泄露,例如加密一张位图,其轮廓依然可见。 除非万不得已,否则避免使用 ECB 。
- CBC 模式 :每个明文块在加密前,会先与前一个密文块进行异或操作。第一个块则与一个初始化向量异或。这破坏了确定性,相同的明文块序列会产生不同的密文序列。解密过程是反向的。这是最常用的模式之一,但因为是串行处理,无法并行加密。
- CTR 模式 :它将一个计数器进行加密,然后将加密结果与明文异或得到密文。解密过程完全相同。这是一个流密码模式,具有并行性(因为加密计数器不依赖数据),且加解密操作对称,硬件实现更简单。SKHA 支持可配置的计数器模数,非常灵活。
3.2 寄存器全景与数据流
SKHA 的寄存器比 RNG 更复杂,因为它要管理算法、密钥、数据、上下文等多种状态。我们可以将其工作流程想象成一个烹饪流水线:
-
配方设置
:
SKMR寄存器。你在这里选择“菜系”(ALG: AES/DES/3DES)、“烹饪方式”(CM: ECB/CBC/CTR)和“方向”(DIR: 加密/解密)。对于 CTR 模式,还要设置计数器的“进位规则”(CTRM)。 -
厨房控制
:
SKCR和SKCMR寄存器。SKCR控制 DMA 请求级别和中断使能,SKCMR则包含启动(GO)、复位(SWR)等命令。 -
原料准备
:
-
密钥
:通过
SKKDR1到SKKDR6寄存器写入。写入后,必须在SKKSR寄存器中声明密钥的实际字节长度(AES=16, DES=8, 2KEY-3DES=16, 3KEY-3DES=24)。 -
初始化向量/计数器
:对于 CBC 和 CTR 模式,需要将 IV 或初始计数器值写入
SKC1至SKC4上下文寄存器。 -
待处理数据量
:在
SKDSR中设置需要加密或解密的总字节数。数据长度必须是块大小的整数倍(DES/3DES为8的倍数,AES为16的倍数,CTR模式除外)。
-
密钥
:通过
-
投料与产出
:
-
输入 FIFO
:将要处理的数据块(明文或密文)通过
SKIN寄存器写入。 -
输出 FIFO
:从
SKOUT寄存器读取处理后的数据块(密文或明文)。
-
输入 FIFO
:将要处理的数据块(明文或密文)通过
-
状态监控
:通过
SKSR查看 FIFO 水位(IFL,OFL)、忙状态(BUSY)和完成标志(DONE)。通过SKESR排查任何错误。
3.3 核心操作流程与代码框架
一个完整的 AES-CBC 加密操作,其软件驱动流程如下。这个过程体现了硬件加速器“配置-喂数据-取结果”的典型交互模式。
// 假设以下寄存器地址均已宏定义
void skha_aes_cbc_encrypt(const uint8_t *key, const uint8_t *iv, const uint8_t *plaintext, uint8_t *ciphertext, uint32_t length) {
// 0. 安全检查:长度必须是16字节的倍数
if (length % 16 != 0) return;
// 1. 软件复位(可选,确保模块处于已知状态)
SKCMR = (1 << 0); // 设置 SWR 位,该位会自动清除
// 2. 等待复位完成
while ((SKSR & (1 << 3)) == 0); // 等待 RD 位为1
// 3. 设置模式:AES, CBC, 加密
SKMR = (0x00 << 0) | // ALG = 00 (AES)
(1 << 2) | // DIR = 1 (Encrypt)
(0x01 << 3); // CM = 01 (CBC)
// 4. 写入密钥 (128-bit = 16 bytes)
SKKDR1 = *(uint32_t*)(key);
SKKDR2 = *(uint32_t*)(key+4);
SKKDR3 = *(uint32_t*)(key+8);
SKKDR4 = *(uint32_t*)(key+12);
SKKSR = 16; // 声明密钥大小为16字节
// 5. 写入初始化向量 (IV)
SKC1 = *(uint32_t*)(iv);
SKC2 = *(uint32_t*)(iv+4);
SKC3 = *(uint32_t*)(iv+8);
SKC4 = *(uint32_t*)(iv+12);
// 6. 设置需要处理的数据总长度(字节)
SKDSR = length;
// 7. 启动模块
SKCMR = (1 << 3); // 设置 GO 位
// 8. 数据搬运循环(此处为轮询方式,实际可用DMA优化)
uint32_t words_remaining = length / 4; // 转换为32位字数
const uint32_t *p_in = (const uint32_t*)plaintext;
uint32_t *p_out = (uint32_t*)ciphertext;
while (words_remaining > 0) {
// 8.1 检查输入FIFO是否有空间(IFL < 深度,深度通常为32)
if ((SKSR & 0x00FF0000) < (31 << 16)) { // 粗略判断,实际需查手册FIFO深度
SKIN = *p_in++;
words_remaining--;
}
// 8.2 检查输出FIFO是否有数据
if ((SKSR & 0xFF000000) != 0) { // OFL > 0
*p_out++ = SKOUT;
}
}
// 9. 等待处理完成
while ((SKSR & (1 << 1)) == 0); // 等待 DONE 位为1
// 10. 读取可能残留在输出FIFO中的数据
while ((SKSR & 0xFF000000) != 0) {
*p_out++ = SKOUT;
}
}
3.4 高级话题:DMA集成与性能优化
上面展示的是轮询方式,CPU 需要不断检查 FIFO 状态,效率较低。SKHA 模块支持 DMA,可以解放 CPU。
配置 DMA 的要点 :
-
设置请求水位
:在
SKCR寄存器中,IDMAL和ODMAL字段分别设置了输入和输出 FIFO 触发 DMA 请求的阈值。例如,设置为 8,意味着当输入 FIFO 空闲空间 >=8 个字时,或输出 FIFO 数据量 >=8 个字时,模块会向 DMA 控制器发出请求。 -
使能 DMA
:设置
SKCR中的IDMA和ODMA位为 1。 -
配置 DMA 通道
:需要配置 MCU 的 DMA 控制器,将源地址设为内存数据缓冲区,目标地址设为
SKIN(对于输入),以及源地址设为SKOUT,目标地址设为内存缓冲区(对于输出)。并设置传输数据宽度为 32 位,触发源选择为 SKHA 的对应请求信号。 -
启动
:设置好 DMA 和 SKHA 后,写入
SKDSR和SKCMR[GO],整个加密/解密过程将由 DMA 自动完成,CPU 仅在传输完成中断中处理后续事宜即可。
性能优化心得 :
- 批量处理 :尽量一次性加密大块数据,减少模块启动/停止的开销。
- 双缓冲 :在 DMA 传输时,可以设置双缓冲区。当 DMA 正在传输缓冲区 A 的数据到 SKHA 时,CPU 可以准备下一批数据到缓冲区 B,实现流水线操作,最大化吞吐量。
-
上下文保存
:如果系统需要处理多个独立的加密会话,需要在切换前保存
SKC1-SKC11的上下文寄存器,并在恢复会话时写回。否则,CBC 或 CTR 模式的链式状态会错乱,导致解密失败。
4. 常见问题排查与调试实录
即使理解了原理和流程,在实际硬件调试中依然会遇到各种问题。下面是我在多个项目中总结出的典型问题及其排查思路。
4.1 RNG 模块输出“不够随机”或周期性重复
- 现象 :获取的随机数序列通过简单的统计测试(如卡方检验)失败,或者肉眼观察发现有短周期重复的模式。
-
排查
:
-
检查熵源
:确认
SLM位未被置位(睡眠模式)。在低功耗设计中,从睡眠唤醒后是否给了振荡器足够的稳定时间?手册中通常会有“Start-up Time”参数。 -
注入外部熵
:尝试通过
RNGER寄存器注入一些变化的数据(如未连接的 GPIO 引脚电平、ADC 读取的噪声)。观察输出是否有改善。 - 遵循安全建议 :这是最常见的原因。你是否直接使用了 RNG 的输出? 永远不要 直接将硬件 RNG 的输出作为密钥或一次性密码本。必须将其作为种子,通过一个密码学安全的伪随机数生成函数(如 HMAC-DRBG 或 CTR-DRBG)进行扩展。用 NIST 的测试套件(如 NIST SP 800-22)测试最终输出。
-
检查熵源
:确认
- 解决 :在驱动层实现一个 CSPRNG 层。硬件 RNG 仅负责定期(如上电时、每隔一小时)提供高质量的种子来重置 CSPRNG 的状态。
4.2 SKHA 模块加密/解密结果不正确
这是最令人头疼的问题,原因可能多种多样。可以按照以下清单逐项核对:
| 问题现象 | 可能原因 | 排查步骤与解决方法 |
|---|---|---|
| 输出全是0或固定值 | 模块未正确启动或数据未输入 |
1. 确认
SKCMR[GO]
已置位。
2. 确认
SKDSR
已设置为正确的非零字节数。
3. 检查
SKSR[BUSY]
是否为1,确认模块在运行。
4. 使用调试器监控
SKIN
的写入操作和
SKOUT
的读取操作是否确实发生。
|
| 只有第一个块正确,后续全错 | CBC/CTR 模式上下文错误 |
1.
加密/解密方向是否匹配
?用 CBC 加密,必须用 CBC 解密,且 IV 相同。
2. 多包数据处理时,上下文是否连续 ?对于分片数据,前一片的最后一个密文块是下一片的 IV(CBC),或计数器需要连续(CTR)。必须在处理下一片前,将当前的上下文寄存器(
SKC1-SKC4
)保存并作为下一片的 IV 载入。
|
| 解密后数据尾部有乱码 | 数据长度不是块大小的整数倍 |
1. 确认
SKDSR
设置的长度是块大小的整数倍(AES=16, DES=8)。
2. CTR 模式例外 :CTR 是流模式,可以处理任意长度,最后不足块的部分会被自动处理。确认你使用的是否是 CTR 模式。 |
| 触发模式错误(IME) | 寄存器配置非法组合 |
1. 检查
SKMR
寄存器:算法、模式、方向位组合是否合法?例如,CTR 模数是否设置了保留值?
2. 在 AES 模式下是否错误地使能了 DES 密钥奇偶校验(
DKP
)?
|
| 触发密钥大小错误(KSE) | 密钥长度寄存器设置错误 |
1. 核对
SKKSR
的值:AES=0x10, DES=0x08, 2KEY-3DES=0x10, 3KEY-3DES=0x18。
2. 确认是在写入所有密钥数据 之后 才设置
SKKSR
。
|
| DMA 传输数据错位 | 字节序或数据对齐问题 |
1. 检查
SKCR[END]
位,确保其与你的内存字节序(大端/小端)匹配。
2. 确保 DMA 传输的数据宽度设置为 32 位,并且源/目标地址是 4 字节对齐的。 3. 检查输入数据的原始排列是否与密钥、IV 的写入顺序一致(通常是低位字节在前)。 |
4.3 中断与 DMA 配置相关故障
- 现象 :程序卡住、进入错误中断、或 DMA 传输不完整。
-
排查
:
-
检查错误状态寄存器
:第一时间读取
SKESR寄存器。它的每一个位都指向一个具体的错误原因,例如输入 FIFO 溢出、输出 FIFO 下溢、密钥奇偶校验错误等。这是最直接的诊断工具。 -
中断服务程序
:如果使能了中断,确保 ISR 正确读取了
SKSR和SKESR以判断是完成中断还是错误中断,并清除相应的中断标志(对于 SKHA,写SKCMR[CI]位可以清除错误中断)。 -
DMA 配置
:确认 DMA 的传输大小与
SKDSR设置的总字节数一致。检查 DMA 的源/目标地址是否在传输过程中正确递增。确保在 SKHA 处理完成(DONE置位)后,DMA 也恰好传输完最后的数据,否则可能发生 FIFO 溢出/下溢。
-
检查错误状态寄存器
:第一时间读取
4.4 一个真实的调试案例:CBC 模式上下文丢失
在一次物联网网关项目中,设备需要同时维护与多个传感器的加密连接。最初的设计是为每个连接单独初始化一次 SKHA,导致性能低下。改为复用 SKHA 后,出现了随机性的解密失败。
问题定位 :通过日志发现,解密失败发生在从一个传感器会话切换到另一个之后。检查代码发现,在切换会话时,只是重新配置了密钥和 IV,但没有保存和恢复前一个会话的 上下文寄存器 。
根本原因 :在 CBC 模式中,加密是一个链式过程。即使你为新的会话设置了新的 IV,SKHA 模块内部可能还残留着上一个会话最后一块加密后的中间状态(对于解密则是相反)。如果不进行上下文保存/恢复,这个残留状态会污染新的会话。
解决方案
:在
SKSR[DONE]
置位后、切换任务前,增加一个上下文保存步骤:
// 保存当前上下文
uint32_t context[11]; // 根据算法可能不需要全部11个
context[0] = SKC1;
context[1] = SKC2;
// ... 保存 SKC3-SKC11 等(根据算法模式确定需要保存的数量)
// 然后才能重新配置密钥、IV,开始新会话
// 恢复旧会话时
SKC1 = context[0];
SKC2 = context[1];
// ... 恢复其他上下文寄存器
// 再继续写入后续数据
这个案例让我深刻体会到,硬件加速器虽然快,但它是有状态的。管理好这些状态,是多任务、多会话环境下稳定运行的关键。
5. 安全最佳实践与系统集成建议
将 RNG 和 SKHA 集成到实际产品中,不仅仅是让它们跑起来,更要确保整个系统的安全性是经得起推敲的。
关于随机数的黄金法则 :
- 永远用硬件 RNG 作种子,而非直接输出 。这是手册中强调、且必须遵守的第一原则。集成一个像 CTR_DRBG 这样的 CSPRNG 算法。
- 定期重播种 。CSPRNG 的状态如果长期不更新,理论上也存在被推测的风险。可以设定每隔一段时间(如生成 1GB 数据后)或每次重要操作前,用硬件 RNG 的新输出重新为 CSPRNG 播种。
-
熵池混合
:如果系统有其他可用的熵源(如网络数据包到达时间 jitter、ADC 噪声),可以通过
RNGER注入,或者在你的 CSPRNG 实现中与硬件种子混合。
关于密钥管理 :
- 密钥不出芯片 :理想情况下,由硬件 RNG 生成的真随机数作为种子,在芯片内部通过 CSPRNG 生成会话密钥,并直接加载到 SKHA 的密钥寄存器中使用。避免在外部总线或内存中明文传输长期密钥。
- 密钥定期更新 :对于长期连接,应使用密钥派生函数定期更新会话密钥。
- 清除寄存器 :在完成加密操作或处理完敏感数据后,尤其是系统进入低功耗或复位前,通过软件向密钥寄存器、上下文寄存器、输入/输出 FIFO 写入无关数据,以清除残留信息。有些安全芯片会提供“清零”命令,对于通用 MCU,则需要主动覆盖写。
系统级考量 :
- 时钟安全 :确保供给 RNG 和 SKHA 的时钟是稳定且干净的。异常的时钟可能导致 RNG 熵质量下降或 SKHA 计算错误。
- 访问控制 :在有多核或特权模式的系统中,可以考虑通过内存保护单元将 RNG/SKHA 的寄存器地址空间仅限安全内核或可信任务访问。
- 侧信道攻击防护 :虽然硬件实现本身比软件更能抵抗计时攻击等侧信道攻击,但在系统层面仍需注意,例如确保加密操作的时间不随密钥或数据变化而显著变化(SKHA 作为硬件模块,其时间通常是固定的,这是一个优势)。避免在加密过程中进行依赖数据的跳转或内存访问。
最后,硬件安全模块是强大的工具,但它不是“银弹”。一个安全的系统是深度防御的结果,需要将安全的硬件、严谨的软件实现、合理的系统架构以及持续的威胁建模结合起来。从弄懂每一个寄存器的含义开始,到构建一个健壮、高效的驱动层,再到设计出符合产品安全需求的密钥管理与协议流程,每一步都需要耐心和细致。
577

被折叠的 条评论
为什么被折叠?



