2、密码学基础(上):对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA256)

做OTA升级,说白了就是在跟「中间人」和「篡改」这两个敌人打仗。你想想看,升级包从云端飞到设备上,路上要是被人换了、改了,那设备就成别人的了。所以,密码学就是我们手里最趁手的武器。

这一章,我把升级包里最常用的三种密码学工具掰开揉碎讲清楚。对称加密、非对称加密、哈希函数,这三兄弟各司其职,缺一不可。

核心认知: 对称加密管「快」,非对称加密管「安全分发」,哈希函数管「完整性」。升级包签名校验,正是这三者的组合拳。
升级包密码学三件套 对称加密 (AES) 加密/解密用同一把钥匙 非对称加密 (RSA/ECC) 公钥加密,私钥解密 哈希函数 (SHA256) 任意输入 → 固定摘要 速度快,适合加密升级包本体 密钥分发是最大痛点 速度慢,适合加密对称密钥 公钥公开,私钥保密 不可逆,防篡改 用于校验升级包完整性 升级包签名校验组合流程 SHA256 计算摘要 RSA 私钥签名 AES 加密包体 设备端:AES解密 → SHA256验摘要 → RSA公钥验签名

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 模式必须用
⚠️ 避坑指南: 我曾经在一个项目里看到有人用 ECB 模式加密固件。结果呢?同样的数据块加密结果一模一样,攻击者一眼就能看出固件的结构。ECB 模式在密码学里已经被判了死刑,千万别碰。

我个人习惯用 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,密钥短、计算快,省电又省内存。

💡 小技巧: 签名时不要直接对升级包本体签名,那样太慢了。正确的做法是:先对升级包做 SHA256 哈希,得到一个 32 字节的摘要,然后对这个摘要做 RSA/ECC 签名。设备端验签时,也是先算哈希,再验签名。

来看一个签名与验签的伪代码:

// 服务端:生成签名
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 升级里是怎么配合的。

一个典型的升级包制作流程是这样的:

  1. 计算哈希: 对原始固件做 SHA256,得到 32 字节摘要。
  2. 签名: 用私钥对摘要做 RSA/ECC 签名,得到签名数据。
  3. 加密: 生成随机 AES 密钥,用 AES 加密固件本体。
  4. 打包: 把加密后的固件、签名、AES 密钥(用公钥加密过的)打包成一个升级文件。

设备端收到升级包后,反向操作:

  1. 解密 AES 密钥: 用私钥解密得到 AES 密钥。
  2. 解密固件: 用 AES 密钥解密固件本体。
  3. 验哈希: 计算解密后固件的 SHA256,跟包里的摘要对比。
  4. 验签名: 用公钥验证签名,确保摘要和固件都没被篡改。
⚠️ 重要提醒: 公钥一定要安全地存储在设备里!我见过有人把公钥放在 Flash 的明文区域,结果被攻击者读出来,替换了升级包。正确的做法是把公钥烧死在 OTP(一次性可编程)区域,或者用安全芯片保护。

这一章的内容就到这里。对称加密、非对称加密、哈希函数,这三样东西是 OTA 升级安全的基石。你理解了它们各自的角色和配合方式,后面讲签名校验流程的时候,就会觉得顺理成章了。