3. 密码学基础(下)

好,我们接着往下聊。上一节我们把哈希、对称加密、非对称加密这几个基础工具理了一遍。这一节,咱们要把它们串起来,看看数字签名到底是怎么工作的,密钥该怎么管,以及——嗯,一个看似不起眼但坑最多的环节:随机数生成。

我在固件安全这块摸爬滚打这些年,可以很负责任地告诉你:签名机制是防篡改的基石,密钥管理是签名机制的命门,而随机数生成,往往是整个链条里最先断裂的那一环。

3.1 数字签名:不只是“加密的逆过程”

很多人刚接触数字签名时,会把它理解成“用私钥加密,用公钥解密”。这个说法在技术实现上有点道理,但逻辑上完全不对。数字签名要解决的核心问题不是保密,而是身份认证 + 完整性校验。

说白了,签名要回答两个问题:

  • 这东西是不是你签的?(防抵赖)
  • 这东西签完之后有没有被人动过?(防篡改)

3.1.1 签名流程:先哈希,再加密

实际工程中,我们不会直接对固件镜像做签名运算——那太慢了。正确的做法是:

  1. 计算固件的哈希值(比如 SHA-256),得到一个固定长度的摘要。
  2. 用签名者的私钥对这个摘要进行加密(实际是签名算法,不完全是加密,但可以这么理解)。
  3. 把签名结果附加到固件后面,一起发布。

为什么先哈希?因为非对称加密很慢。一个几十MB的固件,直接做签名运算可能要好几秒,而哈希只需要几十毫秒。你想想看,产线烧录的时候,每台设备多等几秒,成本就上去了。

核心公式(非严格但好记):

签名 = 私钥_加密( 哈希(固件) )

验签 = 公钥_解密(签名) == 哈希(固件) ? 通过 : 失败

3.1.2 验签流程:谁都能验,但谁也改不了

验签的过程刚好反过来。设备端拿到固件和签名后:

  1. 用发布者的公钥解密签名,得到原始的哈希值。
  2. 自己计算当前固件的哈希值。
  3. 两个哈希值对比——一致则通过,不一致则拒绝。

这里有个关键点:公钥是公开的。任何人都可以验签,但只有持有私钥的人才能生成有效的签名。这就是防抵赖的基础。

我曾经在一个IoT项目里遇到过这样的问题:设备端验签时,直接把签名结果当成原始数据去对比,忘了先解密。结果当然是永远验不过。排查了两天才发现,嗯,代码里少调了一个 RSA 解密函数。这种低级错误,在压力大的时候特别容易犯。

3.1.3 常见的签名算法选型

算法 密钥长度 签名长度 性能 我的建议
RSA-PSS 2048/4096 与密钥等长 验签快,签名慢 兼容性好,但密钥偏大
ECDSA 256/384 64/96字节 整体快 资源受限设备首选
Ed25519 256 64字节 极快 现代方案,我最近常用

我个人习惯:新项目优先考虑 Ed25519。它签名短、速度快、实现也安全。RSA 虽然老牌,但2048位的签名有256字节,在固件包体积敏感的场景下不太友好。

避坑指南: 不要自己实现签名算法。OpenSSL、mbedTLS、libsodium 这些库都经过大量审计,直接用就好。我曾经见过一个团队自己写了 RSA 签名,结果填充模式搞错了,签名在99%的情况下都能通过,但就是那1%的边界情况会导致验签失败——这种bug最难查。

3.2 密钥管理基础:生成、存储、分发

算法选得再好,密钥管不好,一切等于零。密钥管理是嵌入式安全里最容易被忽视、但后果最严重的环节。

3.2.1 密钥生成:熵要够,源要纯

生成密钥时,核心要求是不可预测性。如果你用了一个可预测的种子来生成密钥,那你的加密体系就是纸糊的。

在嵌入式设备上,生成密钥通常有两种场景:

  • 产线预置: 在安全环境中生成,然后烧录到设备。这种方式可控性高,但密钥分发是个麻烦事。
  • 设备端生成: 设备首次启动时自己生成密钥对,然后把公钥上报。这种方式更安全,因为私钥从未离开设备。

我个人更倾向于第二种。但要注意,设备端的随机数生成器必须足够好——这个话题我们马上会细讲。

3.2.2 密钥存储:硬件隔离是王道

密钥存储的原则很简单:不要让软件直接接触到明文密钥。

常见的存储方案:

方案 安全等级 成本 适用场景
纯软件存储(Flash/文件) 低 零 仅用于演示/开发
加密后存储(Key Wrap) 中 低 对成本敏感的产品
HSM/SE 安全元件 高 中 金融、车规级设备
TEE(可信执行环境) 高 中 主流智能手机、高端MCU

嗯,这里要注意:千万不要把私钥硬编码在代码里。我见过不止一个产品,固件里直接写着一行 const uint8_t private_key[] = {0x...}。反编译一下,密钥就全裸了。这种产品,说难听点,跟没上锁一样。

警告: 如果你的设备没有硬件安全模块,至少也要用芯片内置的唯一ID(如UID)对密钥进行加密存储。虽然不算绝对安全,但至少能挡住90%的脚本小子。

3.2.3 密钥分发:最薄弱的环节

密钥分发是整个密钥管理里最难的一环。产线上怎么把密钥安全地烧录到每台设备?公钥怎么安全地传递给验证方?

常见的做法是:

  • 公钥证书化: 用根证书签发设备证书,设备出厂时预置根证书公钥。这样,设备只需要信任一个根,其他密钥都可以通过证书链验证。
  • 安全烧录: 产线通过加密通道把密钥写入设备的HSM或OTP区域。烧录完成后,产线端立即销毁密钥副本。
  • 密钥轮换: 定期更换签名密钥。即使某个密钥泄露,也只影响一段时间内的固件。

我曾经参与过一个项目,产线为了方便,直接用U盘拷贝密钥文件到烧录工位。结果U盘丢了……嗯,后面的事情我就不细说了。从那以后,我坚持所有密钥分发必须走加密通道,并且要有完整的审计日志。

3.3 随机数生成:安全体系的“地基”

随机数生成看起来是个小问题,但它是整个密码学体系的根基。如果随机数可预测,那么:

  • 密钥生成 = 可预测 → 加密 = 可破解
  • 签名随机数(如 ECDSA 的 k 值)可预测 → 私钥可直接被还原

这不是理论上的风险。2010年,索尼的PS3签名密钥被破解,就是因为 ECDSA 签名时用了固定的 k 值。两个不同的签名一对比,私钥就暴露了。这个案例在安全圈里被讲了无数遍,但每次讲都让人后背发凉。

3.3.1 真随机 vs 伪随机

类型 来源 特点 嵌入式常用方案
真随机数(TRNG) 物理噪声源(热噪声、时钟抖动) 不可预测,但可能偏斜 MCU内置TRNG外设
伪随机数(PRNG) 算法 + 种子 可重复,但种子不可预测则安全 ChaCha20 / AES-CTR_DRBG

实际工程中,我们通常用TRNG的输出来作为PRNG的种子。TRNG负责提供不可预测的熵,PRNG负责把熵扩展成任意长度的随机数序列。这样既保证了安全性,又保证了效率。

3.3.2 嵌入式设备上的随机数陷阱

嵌入式设备获取高质量随机数比PC难得多。PC有鼠标移动、键盘敲击、网络包到达时间等丰富的熵源,而嵌入式设备往往只有:

  • ADC采样的噪声
  • 时钟抖动
  • 传感器读数

这些熵源的质量参差不齐。我遇到过最典型的问题是:设备刚上电时,熵池几乎是空的。如果此时调用随机数生成函数,得到的可能是全0或者可预测的序列。

我的习惯做法: 设备启动后,先花几百毫秒收集熵。收集多个独立熵源(比如两个ADC通道 + 一个温度传感器),混合后作为种子。如果芯片有硬件TRNG,优先使用,但也要做自检——有些芯片的TRNG在低电压下会输出固定值。

3.3.3 随机数生成的最佳实践

  1. 永远不要用 rand() 或 random() 做密码学用途。 这些函数是给游戏和仿真用的,不是给安全用的。
  2. 使用密码学安全的随机数生成器(CSPRNG)。 比如 mbedTLS 的 mbedtls_ctr_drbg_random(),或者 libsodium 的 randombytes_buf()。
  3. 定期 reseed。 即使初始种子很好,长时间运行后也需要补充新的熵。
  4. 测试随机数质量。 产线上可以跑一些简单的统计测试(比如 NIST SP 800-22 的子集),确保每颗芯片的TRNG都工作正常。

一句话总结: 随机数生成不是“随便找个数”就行。它是整个签名体系的基石。基石不稳,上面盖的房子再漂亮也没用。

3.4 本章知识体系总览

下面这张图把数字签名、密钥管理、随机数生成的关系串起来了。你可以把它当作本章的“思维导图”。

密码学基础(下)知识体系 密码学基础(下)核心知识体系 数字签名 密钥管理 随机数生成 先哈希,再签名 验签:公钥解密 + 哈希对比 算法选型:RSA / ECDSA / Ed25519 防抵赖 + 防篡改 不要自己实现签名算法 生成:熵要够,源要纯 存储:硬件隔离 > 加密存储 > 明文 分发:证书化 + 安全烧录 密钥轮换:定期更换 禁止硬编码私钥 TRNG + PRNG 组合 熵源:ADC / 时钟抖动 / 传感器 上电时熵池可能为空 使用 CSPRNG,不用 rand() 定期 reseed + 质量测试 三者关系:随机数 → 密钥生成 → 数字签名 任何一个环节出问题,整个安全体系都会崩塌

这张图里,三个模块是环环相扣的。随机数生成的质量直接影响密钥的不可预测性,而密钥的安全管理又是数字签名可信度的前提。任何一个环节出问题,整个安全体系都会崩塌——这不是危言耸听,是我在项目里亲眼见过的教训。