3. 密码学基础(下):数字签名、消息认证码与PKI体系

上一讲我们聊了哈希和对称加密,算是热身。这一讲要啃硬骨头了——数字签名、HMAC、数字证书和PKI。说实话,这些概念单独拎出来都不难,难的是把它们串起来,理解它们在OTA固件更新里各自扮演什么角色。

我早年做车载OTA项目时,就吃过“只加密不签名”的亏。固件包用AES加密传输,自认为万无一失,结果被攻击者做了个中间人替换——虽然解不开密文,但人家直接把旧版本固件(有已知漏洞)的密文丢过来,设备照样解密升级了。嗯,从那以后,我再也不敢把“完整性校验”和“来源认证”当回事了。

3.1 消息认证码(HMAC)—— 你确定数据没被改过?

先讲HMAC。它解决的是“数据完整性 + 来源真实性”两个问题。说白了,就是通信双方共享一把密钥,发方用密钥对消息算一个“校验标签”,收方用同样的密钥重新算一遍,比对一致就说明消息没被篡改,而且确实来自持有密钥的一方。

HMAC的核心公式(简化版):

HMAC(K, M) = H( (K' ⊕ opad) || H( (K' ⊕ ipad) || M ) )

其中 K' 是密钥经过填充后的版本,opad/ipad 是固定的填充常量。看着复杂,其实本质就是“两次哈希 + 密钥混合”。

我在实际项目中,常用HMAC做固件包的“完整性校验标签”。比如OTA服务器下发固件时,同时下发一个HMAC值。设备端用预置的共享密钥重新计算,如果匹配,就说明固件在传输过程中没被篡改。

避坑指南: 我曾经见过有人直接用MD5做完整性校验,结果被碰撞攻击搞得很惨。HMAC因为引入了密钥,即使哈希函数本身有弱点(比如MD5),攻击者也无法在不知道密钥的情况下伪造有效的MAC。所以,永远不要用裸哈希替代HMAC。

3.2 数字签名(ECDSA)—— 非对称世界的“手写签名”

HMAC有个硬伤:密钥必须共享。在OTA场景里,如果每台设备都存着和服务器一样的密钥,那任何一台设备被破解,整个系统的密钥就全泄露了。这就是为什么我们需要数字签名。

数字签名基于非对称密码学。服务器用私钥签名,设备用公钥验签。私钥只有服务器有,公钥可以公开分发。这样即使某台设备的公钥被篡改,也影响不到其他设备。

ECDSA(椭圆曲线数字签名算法)是目前嵌入式领域的主流选择。相比RSA,它签名更短、计算更快,特别适合资源受限的MCU。

特性 RSA-2048 ECDSA-P256
密钥长度 2048 bits 256 bits
签名长度 256 bytes 64 bytes
验签速度(MCU) 较慢(~50ms) 较快(~10ms)
安全强度 112 bits 128 bits

我个人习惯在IoT设备上优先选ECDSA-P256。为什么?因为很多MCU硬件自带椭圆曲线加速引擎,验签时间能压到5ms以内。而RSA-2048在低端Cortex-M0上可能要跑几百毫秒,用户体验很差。

注意: ECDSA的随机数质量至关重要。如果签名时使用的随机数重复或可预测,私钥会被直接算出来。索尼PS3当年就栽在这个坑上——他们签名时用了固定的k值,结果私钥被黑客公开还原。所以,一定要用硬件真随机数发生器(TRNG),别用伪随机。

3.3 数字证书与PKI体系 —— 谁来证明公钥是真的?

好,现在设备拿到了固件和签名,用公钥验签。但问题来了:设备怎么知道手里的公钥真的是OTA服务器的? 如果攻击者在第一次固件下发时,同时替换了固件、签名和公钥,设备照样能验签通过——因为攻击者用自己的私钥签了名。

这就是“公钥分发问题”。解决方案就是数字证书和PKI(公钥基础设施)。

数字证书本质上是一个“公钥 + 身份信息 + CA签名”的数据结构。CA(证书颁发机构)是一个可信的第三方,它用自己的私钥给服务器的公钥签名。设备出厂时只预置CA的根证书(根公钥),然后通过证书链逐级验证,最终信任服务器公钥。

PKI证书链验证流程 根CA(自签名证书) 中间CA证书 OTA服务器证书 固件 + ECDSA签名 签发 签发 签名 设备预置内容 • 根CA证书(不可更改) • 设备唯一ID • 私钥(用于双向认证) 验证路径: 根CA → 中间CA → 服务器 → 固件签名

你想想看,这个链条最关键的环节是什么?是根证书。根证书必须在设备出厂时以安全方式烧录,并且设置为“不可更新”或“仅通过物理授权更新”。如果根证书被篡改,整个信任体系就崩塌了。

我的经验: 在OTA系统中,我建议至少使用两级CA(根CA + 中间CA)。根CA离线存储,只在签发中间CA时使用。日常固件签名由中间CA完成。这样即使中间CA私钥泄露,只需要吊销中间证书并重新签发,根证书纹丝不动。这叫“最小权限原则”。

3.4 固件验证中的完整流程

好了,我们把上面所有知识点串起来,看看一次安全的OTA固件更新,设备端到底做了什么:

  1. 下载阶段: 设备通过HTTPS(TLS)下载固件包。TLS握手过程中,服务器出示证书链,设备验证证书链有效性(PKI)。
  2. 完整性校验: 固件包包含一个清单文件(manifest),里面有固件的哈希值。设备先计算固件哈希,与清单中的哈希比对。这一步确保固件没被损坏。
  3. 签名验证: 清单文件本身带有ECDSA签名。设备用从证书链中提取的服务器公钥验签。这一步确保清单(以及固件哈希)确实来自合法服务器。
  4. 版本检查: 验证通过后,检查固件版本号是否高于当前版本。防止回滚攻击。
  5. 解密(如需要): 如果固件加密传输,设备用预置的对称密钥或通过密钥协商解密。
  6. 刷写执行: 所有校验通过后,设备才将固件写入存储分区并重启。

关键提醒: 步骤顺序不能乱。我见过一个项目,先解密再验签——结果攻击者构造一个格式错误的密文,导致解密函数崩溃,直接跳过了验签步骤。正确的做法是:先验签,再解密,最后刷写。验签失败直接拒绝,不给后续任何机会。

3.5 小结:选型建议

最后,给正在做OTA安全设计的你一些具体建议:

  • 资源极度受限(Cortex-M0,几十KB RAM): 用HMAC-SHA256做完整性校验,配合预置共享密钥。虽然密钥管理麻烦,但计算开销最小。
  • 主流MCU(Cortex-M4/M7,几百KB RAM): 上ECDSA-P256 + 两级PKI。硬件加速器能大幅降低验签时间。
  • 应用处理器(Linux/Android): 直接用TLS + 代码签名,证书链可以做到三级以上,支持OCSP在线证书状态查询。
  • 永远不要: 只用CRC做完整性校验、只用Base64编码当加密、把私钥硬编码在代码里。

嗯,密码学基础就讲到这里。这些概念是OTA安全的基石,后面讲安全启动、安全存储、密钥管理时,我们会反复用到它们。你先把这些消化掉,后面就顺了。