密码学基础(上):对称加密与非对称加密、哈希函数、数字签名原理
各位同学,欢迎来到《安全启动固件签名完整方案》的第二讲。今天我们要啃的这块骨头,是整个安全启动体系的基石——密码学基础。说实话,我当年刚接触嵌入式安全时,觉得这些数学概念离我太远了。直到第一次被要求给一个Bootloader做签名校验,才发现不懂这些,连需求文档都看不懂。
这一章我们只讲最核心的四个概念:对称加密、非对称加密、哈希函数、数字签名。别怕,我会用项目里的实际例子帮你理解。
核心观点:安全启动的本质,就是「验证身份 + 验证完整性」。对称加密管保密,非对称加密管身份,哈希函数管完整性,数字签名把后两者合二为一。
1. 对称加密:一把钥匙开一把锁
对称加密,说白了就是加密和解密用同一个密钥。就像你家里的门锁,用同一把钥匙锁门和开门。我最早接触对称加密是在做OTA升级包加密时,当时用的就是AES-128。
它的工作流程很简单:
- 发送方用密钥K加密明文P,得到密文C
- 接收方用同样的密钥K解密密文C,还原出明文P
常见的对称加密算法有:
| 算法 | 密钥长度 | 特点 | 我的建议 |
|---|---|---|---|
| AES | 128 / 192 / 256 bit | 速度快,硬件加速普遍 | 嵌入式首选,尤其AES-128 |
| DES | 56 bit | 已不安全,易被暴力破解 | 别用了,真的 |
| 3DES | 112 / 168 bit | 慢,兼容旧系统 | 能不用就不用 |
| SM4 | 128 bit | 国密标准,国内项目要求 | 国内客户指定时再用 |
项目经验:我在一个IoT网关项目里,用AES-128-CBC模式加密固件包。当时踩了个坑——IV向量写死了全零。结果安全审计直接被标记为高风险。记住:IV必须随机,每次加密都要不同。
对称加密最大的问题是什么?密钥分发。你想想看,如果我要给1000台设备发密钥,怎么保证密钥在传输过程中不被窃取?这就引出了非对称加密。
2. 非对称加密:公钥锁、私钥开
非对称加密用一对密钥:公钥和私钥。公钥可以公开,私钥必须保密。用公钥加密的数据,只有对应的私钥能解密;反过来,用私钥签名的数据,公钥可以验证。
我习惯把公钥想象成一个「锁盒」:谁都可以把东西放进去锁上,但只有持有私钥的人才能打开。
常见的非对称加密算法:
| 算法 | 典型密钥长度 | 速度 | 适用场景 |
|---|---|---|---|
| RSA | 2048 / 4096 bit | 慢 | 签名验证、密钥交换 |
| ECC | 256 bit (相当于RSA 3072) | 较快 | 资源受限设备,安全启动 |
| SM2 | 256 bit | 较快 | 国密替代ECC |
避坑指南:我曾经在一个项目里用了RSA 1024位密钥做固件签名。两年后客户要求过FIPS认证,才发现1024位已经被NIST禁止了。现在做新项目,RSA至少2048位,ECC至少256位。别给自己挖坑。
非对称加密虽然解决了密钥分发问题,但计算量太大。加密1MB的固件用RSA?那得等到天荒地老。所以实际项目中,我们通常用「混合加密」——用非对称加密传输对称密钥,再用对称加密加密实际数据。
3. 哈希函数:数据的指纹
哈希函数能把任意长度的数据,压缩成固定长度的摘要。这个摘要就像数据的「指纹」——内容变了,指纹一定变。
哈希函数有三个核心特性:
- 单向性:从摘要反推原始数据,理论上不可能
- 抗碰撞性:很难找到两个不同数据有相同摘要
- 雪崩效应:输入改一个bit,输出一半以上的bit都会变
我在安全启动里最常用的哈希算法是SHA-256。为什么是256位?因为128位已经不够安全了,MD5和SHA-1都被攻破过。
关键理解:哈希函数不加密数据,它只生成「校验和」。在安全启动中,我们先用SHA-256计算固件的哈希值,然后对这个哈希值做签名。这样既保证了完整性,又避免了直接对大文件做非对称加密的性能开销。
来看一个实际例子,计算固件哈希的伪代码:
// 假设固件数据在 firmware_buf 中,长度 fw_len
uint8_t hash[32]; // SHA-256 输出 32 字节
sha256_context_t ctx;
sha256_init(&ctx);
sha256_update(&ctx, firmware_buf, fw_len);
sha256_final(&ctx, hash);
// 此时 hash 数组里就是固件的指纹
// 任何对固件的修改,都会导致 hash 完全不同
4. 数字签名:签名 + 验证的完整流程
数字签名把非对称加密和哈希函数结合起来。它解决两个问题:
- 身份认证:这个固件确实是厂商发布的
- 完整性校验:固件没有被篡改过
签名流程(在安全的生产服务器上执行):
- 计算固件的SHA-256哈希值
- 用厂商的私钥对哈希值加密,生成签名
- 把签名附加到固件后面,一起发布
验证流程(在设备端Bootloader中执行):
- 从固件中提取签名
- 计算固件本身的SHA-256哈希值
- 用厂商的公钥解密签名,得到原始哈希值
- 比较两个哈希值:一致则通过,不一致则拒绝启动
个人经验:我做过一个项目,Bootloader里验证签名时,忘了把签名本身从固件哈希计算中排除。结果每次验证都失败——因为签名变了,哈希就变了,哈希变了签名又不对,死循环了。嗯,这种低级错误,犯过一次就记住了。
用代码表示签名验证的核心逻辑:
// Bootloader 中的签名验证
int verify_firmware(const uint8_t* fw, uint32_t fw_len,
const uint8_t* signature, uint32_t sig_len,
const public_key_t* pub_key) {
uint8_t hash[32];
// 1. 计算固件哈希
sha256(fw, fw_len, hash);
// 2. 用公钥解密签名,得到原始哈希
uint8_t decrypted_hash[32];
rsa_verify(pub_key, signature, sig_len, decrypted_hash);
// 3. 比较两个哈希
if (memcmp(hash, decrypted_hash, 32) == 0) {
return VERIFY_SUCCESS; // 签名验证通过
} else {
return VERIFY_FAILED; // 固件被篡改或签名无效
}
}
5. 四种技术的对比总结
最后,我用一个表格帮你理清这四种技术的关系:
| 技术 | 密钥 | 主要用途 | 安全启动中的角色 | 性能 |
|---|---|---|---|---|
| 对称加密 | 单密钥 | 数据加密 | 固件传输加密(可选) | 快 |
| 非对称加密 | 公钥 + 私钥 | 身份认证、密钥交换 | 签名/验证的核心 | 慢 |
| 哈希函数 | 无密钥 | 完整性校验 | 生成固件指纹 | 快 |
| 数字签名 | 私钥签名 + 公钥验证 | 身份 + 完整性 | 安全启动的核心机制 | 中等 |
重要提醒:安全启动中,公钥必须被安全地存储在设备中。如果攻击者能替换公钥,那整个签名体系就形同虚设。我见过一个产品,公钥存在外部Flash里,没有任何保护。结果被逆向后,攻击者用自己的公钥替换了原厂公钥,轻松刷入了恶意固件。公钥存储,我们会在后面的章节详细讲。