4、密码学基础(下):数字签名原理,公钥基础设施(PKI)与证书链

好,咱们接着聊密码学。上一节我们把对称加密、非对称加密、哈希函数这些基础工具都过了一遍。这一节,我们要用这些工具搭一个真正的“安全体系”——数字签名、PKI 和证书链。

说白了,加密解决的是“别人看不懂”的问题,而签名解决的是“这东西到底是不是你发的”问题。在嵌入式安全里,后者往往更重要。你想想看,如果固件被篡改了,你还在傻乎乎地升级,那后果……嗯,我见过太多这样的案例了。

4.1 数字签名:不只是“签名”那么简单

数字签名,本质上是用私钥对数据的哈希值进行加密。验证方用公钥解密,比对哈希值。如果一致,说明数据没被改过,而且确实是你签的。

我个人习惯把数字签名比作“盖章+封条”。盖章证明身份,封条保证完整性。缺一不可。

核心流程:
  1. 发送方计算数据的哈希值(比如 SHA-256)
  2. 用私钥加密这个哈希值,得到签名
  3. 发送数据 + 签名给接收方
  4. 接收方用公钥解密签名,得到哈希值 A
  5. 接收方自己计算数据的哈希值 B
  6. 比较 A 和 B,一致则验证通过

我在项目中遇到过一个问题:有人直接把整个固件用私钥加密,当作签名。这其实是个误区。非对称加密很慢,尤其是大文件。正确的做法是只签哈希值,而不是签整个文件。这个坑,我踩过一次,后来再也没犯过。

4.2 公钥基础设施(PKI):信任的起点

好,现在你有了签名和验签的能力。但问题来了:你怎么拿到对方的公钥?

如果公钥被中间人替换了,那整个签名体系就崩塌了。这就是 PKI 要解决的问题——建立一个可信的第三方,来管理公钥的发放和验证。

PKI 的核心组件包括:

  • CA(证书颁发机构):信任的锚点,负责签发证书
  • 证书:把公钥和身份信息绑定在一起,由 CA 签名
  • CRL(证书吊销列表):记录哪些证书已经失效
  • RA(注册机构):辅助 CA 做身份审核
我的经验:在嵌入式设备里,我们通常不会跑完整的 PKI 体系,太耗资源了。但我们会把 CA 的根证书烧死在芯片的 OTP(一次性可编程)区域里。这样,设备启动时就能用这个根证书去验证固件签名证书的合法性。

4.3 证书链:层层信任,环环相扣

证书链,说白了就是“你信任我,我信任他”的传递关系。根 CA 自签自己的证书,然后签发中间 CA,中间 CA 再签发终端实体证书。

为什么要搞这么复杂?直接让根 CA 签发所有终端证书不行吗?

嗯,这里要注意:根 CA 的私钥是最高机密,如果泄露了,整个体系就完了。所以根 CA 通常离线保存,只在签发中间 CA 时才上线。日常的证书签发由中间 CA 完成。这样即使中间 CA 的私钥泄露,也只是影响一部分,根 CA 可以吊销它,重新签发。

我曾经帮一家车厂设计过 OTA 升级的证书链。他们的做法是:

  • 根 CA 离线,存储在硬件安全模块(HSM)里
  • 中间 CA 用于签发每个车型的固件签名证书
  • 终端证书用于实际签名固件,有效期只有 1 年

这样即使某个车型的证书泄露,也不会影响其他车型。而且每年轮换证书,安全性大大提高。

避坑指南:我曾经见过一个团队,把根证书的私钥直接放在编译服务器上。结果服务器被入侵,所有设备的信任链都废了。记住:根私钥必须离线存储,最好用 HSM 或智能卡。

4.4 嵌入式场景下的 PKI 实践

在嵌入式设备里,PKI 的实现和 PC 端有很大不同。资源受限、网络不稳定、设备数量巨大,这些都是挑战。

我建议的做法是:

  1. 预置根证书:在芯片出厂时,把根 CA 证书烧入 OTP 或 eFuse
  2. 证书链验证:设备启动时,验证固件签名证书的合法性,一直追溯到根证书
  3. 证书吊销:通过黑名单机制,或者设置短有效期,来应对证书泄露
  4. 硬件加速:尽量使用芯片自带的硬件加密引擎,别用软件实现,太慢了

下面这张图展示了嵌入式安全启动中典型的证书链结构:

根 CA(离线) 签发 中间 CA 签发 固件签名证书 签名 固件镜像 信任锚点 私钥离线存储 日常签发 可在线操作 有效期短 定期轮换

4.5 一个简单的签名验证代码示例

下面是一个用 OpenSSL 做签名验证的简化示例。实际嵌入式开发中,你会用 mbedTLS 或 WolfSSL 这类轻量级库。

// 伪代码:固件签名验证
int verify_firmware(const uint8_t* firmware, size_t fw_len,
                    const uint8_t* signature, size_t sig_len,
                    const uint8_t* cert, size_t cert_len) {
    
    // 1. 解析证书,提取公钥
    x509_cert_t* x509 = x509_parse(cert, cert_len);
    if (!x509) return -1;
    
    // 2. 验证证书链(从当前证书追溯到根证书)
    if (!verify_cert_chain(x509, root_ca_cert)) {
        return -2;  // 证书不可信
    }
    
    // 3. 计算固件哈希
    uint8_t hash[32];
    sha256(firmware, fw_len, hash);
    
    // 4. 用证书中的公钥验证签名
    if (!rsa_verify(x509->public_key, hash, 32, signature, sig_len)) {
        return -3;  // 签名不匹配
    }
    
    return 0;  // 验证通过
}
小技巧:在实际项目中,我习惯把证书链验证和签名验证分开。这样如果证书链验证失败,可以快速定位是证书问题还是签名问题。调试的时候能省不少时间。

4.6 常见问题与避坑

最后,分享几个我在项目中踩过的坑:

  • 证书过期:设备可能几年不联网,证书过期了都不知道。我建议用长有效期根证书 + 短有效期终端证书的组合。
  • 时间同步:证书验证需要准确时间。没有 RTC 的设备怎么办?可以用固件版本号或生产日期作为参考。
  • 性能问题:RSA-2048 签名验证在低端 MCU 上可能要几百毫秒。如果启动时间敏感,可以考虑用 ECC 算法,速度快很多。
  • 存储空间:证书链可能占用几十 KB。对于 Flash 只有 256KB 的小设备,这是个不小的开销。

我曾经在一个项目里,因为没处理好证书过期问题,导致一批设备在某个日期后全部无法升级。后来我们加了一个“宽限期”机制:证书过期后 30 天内仍然允许升级,但会记录日志。这样既保证了安全,又给了用户缓冲时间。

好了,这一节的内容就到这里。数字签名和 PKI 是嵌入式安全的基石,理解透了,后面的安全启动和固件升级就顺理成章了。

核心要点回顾:
  • 数字签名 = 私钥加密哈希值,公钥解密验证
  • PKI 解决“公钥从哪来”的信任问题
  • 证书链实现分层信任,根 CA 必须离线保护
  • 嵌入式场景要关注性能、存储、时间同步等实际问题

可可精品资料库