密码学基础(上):对称加密、非对称加密与哈希函数

说实话,很多做OTA的工程师一开始都觉得密码学是数学家的事。我当年也这么想——直到第一次看到固件被逆向破解,签名被伪造,更新包被篡改。嗯,从那以后我老老实实把这几块基础啃完了。

这一章我们只聊三样东西:AES、RSA/ECC、SHA-256。它们在OTA里各司其职,缺一不可。你想想看,更新包从云端到设备,要保密、要防篡改、要验证来源——正好对应这三类算法。

核心观点:OTA安全不是靠一个算法,而是靠对称加密、非对称加密、哈希函数三者组合。单用任何一个,都会出大问题。
OTA安全更新 — 密码学三件套 OTA 更新包 对称加密 (AES) 加密固件内容 非对称加密 (RSA/ECC) 签名 & 密钥交换 哈希函数 (SHA-256) 完整性校验 实际流程:用 AES 加密固件 → 用 RSA/ECC 签名哈希 → 设备端先验签名再解密 三者缺一不可,否则更新包可能在传输中被替换或篡改 目标:机密性 + 完整性 + 身份认证

1. 对称加密:AES

对称加密,说白了就是一把钥匙开一把锁。加密和解密用同一个密钥。在OTA场景里,我们通常用AES来加密固件本身。

我个人习惯用AES-256-GCM。为什么?因为GCM模式自带认证标签,能同时检测数据是否被篡改。我在一个车机项目里遇到过,有人用AES-ECB模式加密固件,结果每个16字节块独立加密,攻击者可以重排块顺序——设备照样解密成功,但跑的是被调换过的代码。嗯,从那以后我再也不用ECB了。

避坑指南:我曾经在资源受限的MCU上硬跑AES-256-CBC,结果发现IV(初始化向量)写死在代码里。攻击者只要拿到一份明文和密文,就能推算出密钥。IV必须随机生成,每次更新都不一样。

AES在OTA中的典型用法:

  • 加密固件负载:云端用AES密钥加密固件,设备用相同密钥解密。
  • 加密传输通道:如果TLS不可用,AES可以用来保护更新包的传输。
  • 加密存储:设备收到固件后,用AES加密存储在Flash中,防止物理读取。
// AES-256-GCM 加密示例(伪代码)
key = random_bytes(32)          // 256位密钥
iv  = random_bytes(12)          // 96位随机IV
ciphertext, tag = aes_gcm_encrypt(key, iv, firmware_data)
// 传输时:iv + ciphertext + tag

2. 非对称加密:RSA 与 ECC

非对称加密,公钥加密,私钥解密。在OTA里,我们主要用它来做两件事:数字签名和密钥交换。

你想想看,如果云端用私钥签名固件,设备用公钥验签——只要公钥没被篡改,设备就能确认固件来自真正的云端。这就是OTA安全的基石。

RSA vs ECC:怎么选?

对比项 RSA (2048位) ECC (256位)
安全等级 112位 128位
密钥长度 2048位 256位
签名速度 慢 快
验签速度 快 较快
资源消耗 高(尤其私钥操作) 低
OTA推荐场景 云端签名(计算资源充足) 设备端验签(资源受限)

我在一个IoT项目里踩过坑:用RSA-2048在Cortex-M4上做签名验证,每次验签要花将近2秒。后来换成ECC-256,降到200毫秒以内。设备端验签是高频操作,ECC明显更友好。

注意:千万不要在设备端存储私钥!私钥只放在云端HSM或安全服务器上。设备端只存公钥。我曾经见过有人把私钥硬编码在固件里——那等于把家门钥匙贴在门上。

OTA中的典型签名流程:

  1. 云端计算固件的SHA-256哈希值。
  2. 用私钥对哈希值签名,生成签名数据。
  3. 设备收到固件 + 签名后,用公钥验签。
  4. 验签通过,才允许安装固件。
// ECDSA 签名与验签(伪代码)
// 云端签名
hash = sha256(firmware_data)
signature = ecdsa_sign(private_key, hash)

// 设备验签
hash = sha256(firmware_data)
valid = ecdsa_verify(public_key, hash, signature)
if (!valid) reject_update()

3. 哈希函数:SHA-256

哈希函数,输入任意长度数据,输出固定长度摘要。SHA-256输出256位,也就是32字节。它的核心特性是单向性和抗碰撞性——你没法从哈希值反推原文,也很难找到两个不同输入产生相同哈希。

在OTA里,哈希函数无处不在:

  • 完整性校验:设备下载完固件后,计算SHA-256,与云端公布的哈希值对比。不一致就说明数据被篡改。
  • 签名的基础:非对称签名不是直接签固件,而是签固件的哈希。因为固件可能很大,哈希后只有32字节,签名效率高得多。
  • 增量更新:差分OTA中,用哈希来识别哪些块发生了变化。
关键点:哈希函数不提供机密性,只提供完整性。固件本身还是需要AES加密。哈希 + 签名 = 防篡改 + 防伪造;哈希 + 加密 = 防泄露 + 防篡改。

我个人习惯在OTA更新包中同时包含两个哈希值:一个是固件整体的SHA-256,另一个是每个分块的哈希列表。这样设备可以边下载边校验,不用等全部下载完才发现数据坏了。

// 分块哈希校验示例
block_size = 4096
for each block in firmware:
    block_hash = sha256(block)
    block_hashes.append(block_hash)
// 传输时:block_hashes + firmware + overall_hash + signature

4. 三者在OTA中的协同工作

光讲理论没意思,我们直接看一个真实的OTA安全更新流程:

  1. 云端准备:用AES-256-GCM加密固件,生成密文和认证标签。
  2. 云端签名:计算密文的SHA-256哈希,用ECC私钥签名。
  3. 打包传输:将AES密钥(用设备公钥加密)、IV、密文、签名打包成更新包。
  4. 设备接收:用设备私钥解密AES密钥,得到对称密钥。
  5. 设备验签:计算密文哈希,用云端公钥验签。
  6. 设备解密:验签通过后,用AES密钥解密固件。
  7. 设备校验:解密后的固件再算一次哈希,与云端公布的固件哈希对比。
  8. 安装:全部通过,才允许刷写固件。
经验之谈:我曾经在步骤5和步骤7之间漏掉了步骤6——先解密再验签。结果攻击者篡改密文后,设备解密出乱码,验签失败,但解密过程已经消耗了大量内存和CPU。正确的顺序是:先验签,再解密。验签失败直接拒绝,不浪费资源。

你看,AES负责保密,RSA/ECC负责身份认证和密钥分发,SHA-256负责完整性。三者环环相扣,少一个都不行。