2、密码学基础(上):对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA256)
做OTA升级,说白了就是在跟「中间人」和「篡改」这两个敌人打仗。你想想看,升级包从云端飞到设备上,路上要是被人换了、改了,那设备就成别人的了。所以,密码学就是我们手里最趁手的武器。
这一章,我把升级包里最常用的三种密码学工具掰开揉碎讲清楚。对称加密、非对称加密、哈希函数,这三兄弟各司其职,缺一不可。
2.1 对称加密:AES 算法
对称加密,顾名思义,加密和解密用的是同一把钥匙。就像你家的门锁,出门用钥匙锁上,回家用同一把钥匙打开。简单、直接、快。
在OTA升级场景里,升级包本体通常很大,几十兆甚至上百兆。如果用非对称加密去搞,设备得算到天荒地老。所以,我们一般用对称加密来加密升级包本体。
AES(Advanced Encryption Standard) 是目前最主流的对称加密算法。它有三个关键参数:密钥长度、工作模式、填充方式。
| 参数 | 常见选项 | 我的建议 |
|---|---|---|
| 密钥长度 | AES-128 / AES-192 / AES-256 | 嵌入式设备推荐 AES-128,性能与安全平衡 |
| 工作模式 | ECB / CBC / CTR / GCM | 千万别用 ECB!推荐 GCM(自带认证)或 CBC |
| 填充方式 | PKCS7 / ZeroPadding / NoPadding | PKCS7 最通用,CBC 模式必须用 |
我个人习惯用 AES-128-CBC 搭配 PKCS7 填充。为什么?128位密钥在大多数 MCU 上都有硬件加速,跑起来飞快。CBC 模式每个密文块依赖前一个块,安全性比 ECB 高一个量级。
来看一个简单的 AES 加密示例(伪代码):
// 加密升级包
function encrypt_firmware(firmware_data, key, iv):
// key: 16字节 (AES-128)
// iv: 16字节 (初始化向量)
cipher = AES_CBC_new(key, iv, PKCS7_PADDING)
encrypted_data = cipher.encrypt(firmware_data)
return encrypted_data
// 解密升级包(设备端)
function decrypt_firmware(encrypted_data, key, iv):
cipher = AES_CBC_new(key, iv, PKCS7_PADDING)
firmware_data = cipher.decrypt(encrypted_data)
return firmware_data
这里有个细节:IV(初始化向量) 每次加密都应该随机生成,并且可以明文传输。IV 的作用是让同样的明文每次加密结果都不一样,防止攻击者通过统计规律破解。
2.2 非对称加密:RSA 与 ECC
对称加密有个死穴——密钥怎么安全地传给设备?你想想看,如果密钥在传输过程中被截获,那加密就形同虚设了。
非对称加密就是来解决这个问题的。它有一对钥匙:公钥 和 私钥。公钥可以公开,谁都可以拿;私钥只有你自己知道。用公钥加密的数据,只有私钥能解密;反过来,用私钥签名的数据,公钥可以验证。
在OTA升级里,非对称加密主要干两件事:
- 加密对称密钥: 用公钥加密 AES 密钥,设备用私钥解密得到 AES 密钥,再用 AES 密钥解密升级包本体。
- 数字签名: 用私钥对升级包的哈希值签名,设备用公钥验证签名,确保升级包没被篡改。
目前主流的非对称加密算法有两个:RSA 和 ECC(椭圆曲线加密)。
| 对比项 | RSA | ECC |
|---|---|---|
| 密钥长度 | 2048 / 4096 位 | 256 / 384 位 |
| 同等安全强度 | RSA-2048 ≈ ECC-224 | ECC-256 > RSA-3072 |
| 签名速度 | 慢(私钥操作) | 快 |
| 验签速度 | 快(公钥操作) | 较快 |
| 嵌入式适用性 | 资源消耗大,MCU吃力 | 密钥短,适合资源受限设备 |
我个人的经验是:如果设备是 Cortex-M4 以上的 MCU,有硬件加速的话,RSA-2048 完全够用。如果是低端 MCU,比如 Cortex-M0,我建议用 ECC-256,密钥短、计算快,省电又省内存。
来看一个签名与验签的伪代码:
// 服务端:生成签名
function sign_firmware(firmware_data, private_key):
hash = SHA256(firmware_data) // 32字节摘要
signature = RSA_sign(private_key, hash) // 256字节签名
return signature
// 设备端:验证签名
function verify_firmware(firmware_data, signature, public_key):
hash = SHA256(firmware_data)
is_valid = RSA_verify(public_key, hash, signature)
return is_valid // true 或 false
2.3 哈希函数:SHA256
哈希函数,说白了就是一个「数据指纹」生成器。不管你的输入是一行文字还是一个 100MB 的固件,它都能给你生成一个固定长度的摘要。而且,只要输入变了哪怕一个比特,摘要就完全不一样。
SHA256 是 SHA-2 家族的一员,输出 256 位(32 字节)的摘要。在 OTA 升级里,它的作用就是保证升级包的完整性。
哈希函数有三个核心特性:
- 单向性: 从摘要反推原始数据,理论上不可能。你想想看,256 位的输出空间,暴力破解的复杂度是 2^256,宇宙毁灭都算不完。
- 抗碰撞性: 找到两个不同的输入,让它们的 SHA256 摘要相同,计算上不可行。
- 雪崩效应: 输入改一个比特,输出大约一半的比特会翻转。
我曾经遇到过一个坑:有人用 MD5 做升级包的完整性校验。MD5 已经被破解了,攻击者可以构造两个不同的文件,让它们的 MD5 值相同。所以,现在做 OTA 升级,SHA256 是底线,别再碰 MD5 和 SHA1 了。
来看一个简单的 SHA256 计算示例:
// 计算升级包的 SHA256 摘要
function compute_hash(firmware_data):
// 逐块读取,避免内存爆炸
ctx = SHA256_CTX_new()
for each block in firmware_data:
ctx.update(block)
hash = ctx.final() // 32字节
return hash
// 验证完整性
function check_integrity(firmware_data, expected_hash):
actual_hash = compute_hash(firmware_data)
return actual_hash == expected_hash
这里有个工程细节:升级包可能很大,几十 MB,不能一次性加载到内存里。正确的做法是分块读取,逐块更新哈希上下文,最后再 final 出结果。几乎所有哈希库都支持这种流式操作。
2.4 三者的协同作战
讲完了三个工具,咱们来看看它们在 OTA 升级里是怎么配合的。
一个典型的升级包制作流程是这样的:
- 计算哈希: 对原始固件做 SHA256,得到 32 字节摘要。
- 签名: 用私钥对摘要做 RSA/ECC 签名,得到签名数据。
- 加密: 生成随机 AES 密钥,用 AES 加密固件本体。
- 打包: 把加密后的固件、签名、AES 密钥(用公钥加密过的)打包成一个升级文件。
设备端收到升级包后,反向操作:
- 解密 AES 密钥: 用私钥解密得到 AES 密钥。
- 解密固件: 用 AES 密钥解密固件本体。
- 验哈希: 计算解密后固件的 SHA256,跟包里的摘要对比。
- 验签名: 用公钥验证签名,确保摘要和固件都没被篡改。
这一章的内容就到这里。对称加密、非对称加密、哈希函数,这三样东西是 OTA 升级安全的基石。你理解了它们各自的角色和配合方式,后面讲签名校验流程的时候,就会觉得顺理成章了。