1、签名更新机制基础

各位同学,咱们今天聊的是整个课程的地基——签名更新机制。说白了,就是搞清楚「凭什么系统相信一个更新包是安全的」。我做了这么多年安全,见过太多人跳过基础直接研究攻击手法,结果连日志都看不懂。嗯,咱们先把根扎稳。

1.1 数字签名原理

数字签名不是加密,这点我反复跟团队强调。它解决的是三个问题:

  • 身份认证——这个包到底是谁发的?
  • 完整性——包在路上被人改过没有?
  • 不可否认性——发件人不能事后说「我没发过」

核心流程其实就四步,我画个图你们就懂了:

数字签名核心流程 数字签名核心流程 发送方(签名者) 原始数据 哈希运算 摘要(Hash) 私钥加密 数字签名 接收方(验证者) 接收数据 哈希运算 摘要(Hash') 公钥解密 解密得摘要 Hash == Hash' ?

看到没?发送方用私钥加密摘要,接收方用公钥解密对比。私钥只有发送方有,公钥谁都能拿。这就是非对称加密的魅力。

关键认知:数字签名 ≠ 加密数据。签名只保护「谁发的」和「改没改」,不保护内容隐私。更新包本身可以是明文,签名是附加上去的。

1.2 更新包签名流程

实际工程里,签名不是对着原始数据直接签。我见过不少新手犯这个错。标准流程是这样的:

  1. 打包——把二进制、元数据、版本号打成 ZIP 或 tar.gz
  2. 计算摘要——对整个包做 SHA-256 或 SHA-512
  3. 签名摘要——用私钥对摘要做 RSA 或 ECDSA 签名
  4. 封装——把签名、证书链、算法标识塞进包尾或单独文件

举个例子,Android OTA 包的签名结构:

update.zip
├── META-INF/
│   ├── MANIFEST.MF        # 文件摘要列表
│   ├── CERT.SF            # 签名文件
│   └── CERT.RSA           # 签名 + 证书
├── system/
├── boot.img
└── payload.bin

我个人习惯在 CERT.RSA 里同时嵌入完整证书链,这样验证端不需要额外下载根证书。但要注意,证书链长度不能超过 4 级,否则某些老旧固件会解析失败——我在一个 IoT 项目里踩过这个坑。

工程建议:签名算法优先选 ECDSA 而非 RSA。同样的安全强度下,ECDSA 签名短 80%,对嵌入式设备特别友好。我去年帮客户优化一个车载系统,光改签名算法就把更新包体积降了 40%。

1.3 签名验证链

验证不是「拿公钥解一下就行」。真正的验证是一条链:

层级 组件 验证内容 常见失败原因
L1 根证书 自签名,信任锚点 根证书过期或被吊销
L2 中间证书 由根证书签发 中间证书缺失或签名算法不匹配
L3 代码签名证书 由中间证书签发 证书用途不含「代码签名」扩展
L4 签名值 由代码签名证书私钥生成 摘要不匹配或签名格式错误

验证链的完整流程是这样的:

1. 从更新包提取签名和证书链
2. 检查证书链完整性(每个上级签下级)
3. 验证根证书是否在系统信任存储中
4. 检查每个证书的有效期和吊销状态
5. 用叶子证书的公钥解密签名,得到摘要A
6. 对更新包本体计算摘要B
7. 比较摘要A == 摘要B

这里有个容易被忽视的点——步骤 4 的吊销检查。我曾经遇到一个案例:攻击者用一个已吊销的证书签名,但目标设备没有联网做 CRL/OCSP 检查,结果就放行了。嗯,离线环境下的吊销检查是个大课题,后面章节会专门讲。

高危陷阱:很多系统只验证证书链的签名关系,不验证证书用途(Key Usage / Extended Key Usage)。如果签名证书的用途是「SSL 服务器认证」而非「代码签名」,理论上应该拒绝。但实际代码里,我见过至少 3 款主流产品跳过了这个检查。

1.4 常见误区与避坑

做签名验证,最容易翻车的几个地方:

  • 只验签名不验证书链——攻击者可以用自签名证书伪造更新
  • 硬编码公钥——一旦私钥泄露,没法轮换,只能发固件升级修复
  • 忽略时间戳——证书过期后签名仍然有效?需要 RFC 3161 时间戳服务
  • 摘要算法太弱——MD5 和 SHA-1 已经可以被碰撞,至少用 SHA-256

我记得有一次做安全审计,发现某厂商的智能门锁用 MD5 做更新包摘要。我跟他们说这不行,对方回复「门锁算力不够跑 SHA-256」。后来我帮他们换成了 BLAKE2s,速度比 MD5 还快,安全性直接拉到 128 位。很多时候不是做不到,是没想到。

核心原则:签名验证链的强度取决于最弱的一环。证书链、算法强度、吊销检查、时间戳——少一个环节,整个体系就可能被绕过。后面章节我们会逐一演示这些环节如何被攻击,以及如何防御。