3、密码学基础(下):数字签名(ECDSA)、密钥管理、随机数生成器(TRNG/DRBG)的安全要求

好,咱们接着聊。上一节我们把哈希、对称加密、非对称加密这些基础概念过了一遍。这一节,我重点讲三个在固件安全更新里真正要命的东西:数字签名、密钥管理,还有随机数生成器。

说实话,这三个东西任何一个出问题,你的固件更新机制基本就形同虚设了。我在项目里见过太多“签名验签都做了,但还是被攻破”的案例,问题往往就出在这几个环节上。

3.1 数字签名(ECDSA)—— 固件更新的“防伪标签”

数字签名,说白了就是给固件贴个防伪标签。你下载一个固件,怎么确认它真的是厂家发布的,而不是中间人塞给你的恶意版本?靠的就是签名。

我个人习惯用 ECDSA(椭圆曲线数字签名算法)。为什么?因为它比 RSA 的密钥短得多,但安全强度相当。在嵌入式设备这种资源受限的环境里,256 位的 ECDSA 密钥,效果跟 3072 位的 RSA 差不多,但计算速度快、占用空间小。

核心流程:

  1. 签名(在安全的生产环境做): 用私钥对固件哈希值进行签名,生成签名值。
  2. 验签(在设备上做): 设备用预置的公钥,验证签名是否匹配。

这里有个坑,我踩过。ECDSA 的签名过程中,需要一个随机数 k。这个 k 如果重复了,或者可预测,私钥就直接暴露了。嗯,这不是理论问题,是真实发生过的事。索尼 PlayStation 3 的 ECDSA 签名就因为 k 值固定,被黑客直接算出了私钥。

警告: 绝对不要使用固定的 k 值!每次签名必须生成全新的、不可预测的随机数。这是 ECDSA 的命门。

代码层面,我建议用 mbedTLS 或 OpenSSL 的封装库。自己手写 ECDSA?除非你是数学系博士,否则别碰。下面是一个典型的验签流程(伪代码):

// 伪代码:固件验签流程
uint8_t firmware_hash[32];  // SHA-256 哈希值
uint8_t signature[64];      // ECDSA 签名 (r, s)
uint8_t public_key[64];     // 公钥 (x, y)

// 1. 计算固件哈希
sha256(firmware, firmware_length, firmware_hash);

// 2. 使用预置公钥验签
int ret = ecdsa_verify(public_key, firmware_hash, signature);
if (ret != 0) {
    // 验签失败!拒绝更新
    return ERROR_SIGNATURE_INVALID;
}
// 验签通过,继续更新流程

你看,代码本身不复杂。复杂的是怎么安全地管理那把公钥和私钥。

3.2 密钥管理 —— 整个安全体系的“七寸”

密钥管理,是我认为嵌入式安全里最难的部分。算法可以选现成的,代码可以抄开源的,但密钥怎么存、怎么分发、怎么更新,每个项目都不一样。

我曾经见过一个产品,公钥直接硬编码在固件里,而且用的是同一个密钥对,一用就是五年。后来密钥泄露了,所有已出货的设备都没法安全更新,只能召回。那叫一个惨。

关于密钥管理,我总结了几个必须遵守的原则:

  • 私钥永远不出设备: 签名必须在安全的环境(如 HSM、离线服务器)完成。私钥一旦进入设备,就等于公开了。
  • 公钥必须防篡改: 公钥虽然可以公开,但必须保证它没有被替换。通常的做法是烧录在一次性可编程(OTP)存储器里,或者用证书链验证。
  • 密钥分级: 不要所有东西都用一把密钥。我习惯分三级:
    • 根密钥: 离线保存,用于签发中间证书或设备证书。一年用不了几次。
    • 固件签名密钥: 用于日常固件签名。可以定期轮换。
    • 会话密钥: 每次更新临时生成,用于加密传输通道。
密钥类型 存储位置 生命周期 安全要求
根私钥 离线 HSM / 保险柜 5-10 年 物理隔离,多人授权
固件签名私钥 构建服务器 HSM 1-2 年 定期轮换,访问审计
设备公钥 设备 OTP / eFuse 设备全生命周期 一次性写入,不可修改
会话密钥 RAM(临时) 单次更新会话 用完即焚

提示: 如果你用的是 MCU,优先利用芯片自带的 OTP 或 eFuse 来存储公钥哈希。这样即使固件被读取,攻击者也改不了公钥。

3.3 随机数生成器(TRNG / DRBG)—— 安全的“基石”

很多人不重视随机数,觉得“差不多随机就行了”。但密码学里,随机数质量直接决定安全强度。你想想看,如果密钥生成用的随机数可预测,那你的加密跟没加密有什么区别?

嵌入式设备里,我们通常用两种随机数:

  • TRNG(真随机数生成器): 基于物理噪声,比如芯片的热噪声、时钟抖动。优点是真正不可预测,缺点是速度慢、可能受环境影响。
  • DRBG(确定性随机数生成器): 基于一个种子(seed),用算法(如 CTR_DRBG、HMAC_DRBG)生成随机数。速度快,但种子必须来自 TRNG。

我建议的做法是:用 TRNG 给 DRBG 播种,然后用 DRBG 提供日常所需的随机数。这样既保证了不可预测性,又保证了速度。

警告: 不要直接用标准库的 rand() 函数!那个是伪随机数,用于游戏可以,用于密码学等于自杀。我曾经在代码审查里看到有人用 rand() 生成密钥,当场就让他改了。

安全要求方面,有几个硬指标:

  • 熵源必须经过健康测试: TRNG 输出的数据要定期做统计测试(比如 NIST SP 800-90B 的测试),确保没有偏向或周期性。
  • DRBG 必须支持即时 reseed: 每次生成密钥或签名前,最好重新播种一次,防止状态泄露。
  • 随机数不能重复: 尤其是在 ECDSA 签名中,k 值重复等于私钥泄露。DRBG 的计数器模式可以保证这一点。

嗯,这里要注意。很多 MCU 厂商现在都内置了硬件 TRNG,比如 STM32 的 RNG 外设。但硬件 TRNG 也不是万能的,我遇到过某款芯片的 TRNG 在低温下输出质量下降。所以,一定要做上电自检,确认 TRNG 工作正常后再使用。

最后总结一句:密码学基础这部分,说白了就是“别自己发明算法,别乱存密钥,别用烂随机数”。把这三点守住了,你的固件更新安全就成功了一大半。