3、密码学基础(上):对称加密与非对称加密、哈希函数、数字签名原理

各位同学,欢迎来到密码学基础部分。说实话,很多做嵌入式开发的朋友一听到「密码学」三个字就头大,觉得那是数学博士才搞的东西。其实不然。你不需要懂那些复杂的数论证明,但你必须知道每种工具是干什么的、用在哪儿、有什么坑。

我在做OTA安全方案的时候,见过太多因为密码学选型不当导致的安全事故。有的产品用了过时的算法,有的密钥硬编码在固件里,有的签名验证流程形同虚设。嗯,今天我们就把这些基础打牢。

核心观点:嵌入式OTA安全的核心,就是保证固件的「机密性、完整性、真实性」。对称加密管机密性,哈希函数管完整性,数字签名管真实性。三者缺一不可。

密码学基础核心知识体系 OTA安全密码学基础 对称加密 非对称加密 哈希函数 AES-128/256 ECB / CBC / GCM 模式 密钥长度与性能权衡 RSA / ECC / ECDSA 公钥加密 / 私钥签名 数字签名 = 私钥签名 + 公钥验签 SHA-256 / SHA-3 完整性校验 哈希链与固件校验 三者结合 → 安全OTA更新

3.1 对称加密:一把钥匙开一把锁

对称加密,说白了就是加密和解密用同一个密钥。你想想看,就像你家大门,用一把钥匙锁上,也用同一把钥匙打开。简单、高效,适合大量数据的加密。

在嵌入式世界里,最常用的对称加密算法是 AES(Advanced Encryption Standard)。我个人习惯用 AES-128 或 AES-256,具体选哪个看你的MCU性能。128位密钥已经足够安全,256位更保险但计算量也更大。

我的经验:在STM32这类Cortex-M4芯片上,AES-128加密1KB数据大约需要几十微秒。如果MCU没有硬件加密引擎,纯软件实现会慢很多。选型时一定要先跑个benchmark。

对称加密有几个关键参数你必须搞清楚:

  • 密钥长度:128位、192位、256位。越长越安全,但越慢。
  • 工作模式:ECB、CBC、GCM等。千万别用ECB模式!
  • 初始化向量(IV):CBC和GCM模式需要IV,每次加密必须不同。

避坑指南:我曾经在一个项目中看到有人用ECB模式加密固件。结果呢?因为ECB模式下相同的明文块会生成相同的密文块,攻击者通过分析密文模式就能推断出固件结构。从那以后,我只要看到ECB就条件反射地摇头。

这里给个简单的AES-CBC加密代码示例,方便你理解实际用法:

// 伪代码:AES-128 CBC 加密固件块
void aes_cbc_encrypt(uint8_t *plaintext, uint8_t *ciphertext, 
                     uint8_t *key, uint8_t *iv, size_t len) {
    // 1. 初始化AES上下文
    AES_CTX ctx;
    AES_set_key(&ctx, key, 128);
    
    // 2. 执行CBC模式加密
    AES_cbc_encrypt(&ctx, plaintext, ciphertext, len, iv, AES_ENCRYPT);
    
    // 3. 注意:IV会被修改,需要保存或重新生成
}

3.2 非对称加密:公钥和私钥的默契

非对称加密就更有意思了。它有两把钥匙:一把公钥,一把私钥。公钥可以公开,私钥必须保密。用公钥加密的数据只能用私钥解密,反过来,用私钥签名的数据可以用公钥验证。

你可能会问:「为什么不用对称加密搞定一切?」原因很简单——密钥分发问题。在OTA场景下,你的设备可能有几百万台,如果每台设备都预置同一个对称密钥,一旦一台被破解,所有设备都完蛋。非对称加密就解决了这个问题:设备只存公钥,私钥放在服务器端,安全得多。

常见的非对称算法有:

算法 密钥长度 安全强度 嵌入式适用性
RSA 2048 / 4096 位 高 计算量大,适合签名验证
ECC(椭圆曲线) 256 / 384 位 高(同等安全下密钥更短) 非常适合嵌入式,速度快
ECDSA 基于ECC 高 数字签名首选

我个人强烈推荐在嵌入式OTA中使用 ECC + ECDSA。为什么?因为256位的ECC密钥就能提供和3072位RSA相当的安全强度,但计算量小得多。在资源受限的MCU上,这个差距是致命的。

3.3 哈希函数:数据的指纹

哈希函数,你可以把它理解成数据的「指纹」。不管输入多大,输出都是固定长度(比如SHA-256输出256位)。而且,只要输入有一点点变化,输出就会完全不一样。

哈希函数有几个重要特性:

  • 单向性:从哈希值无法反推出原始数据。
  • 抗碰撞性:很难找到两个不同的输入产生相同的哈希值。
  • 雪崩效应:输入改一个比特,输出大约一半的比特都会变。

在OTA更新中,哈希函数主要用来做完整性校验。设备下载完固件后,计算固件的哈希值,和服务器下发的哈希值对比。如果一致,说明固件在传输过程中没有被篡改。

关键点:哈希函数只能保证完整性,不能保证真实性。也就是说,它能告诉你数据有没有被改过,但不能告诉你数据是不是来自合法的服务器。要解决真实性问题,需要数字签名。

3.4 数字签名:给固件盖上防伪章

数字签名,本质上就是「用私钥对哈希值加密」。流程是这样的:

  1. 服务器计算固件的哈希值。
  2. 服务器用私钥对这个哈希值进行签名(加密)。
  3. 服务器把固件 + 签名一起发给设备。
  4. 设备用公钥验证签名:解密得到哈希值,和自己计算的哈希值对比。

如果两个哈希值一致,说明两件事:第一,固件没有被篡改(完整性);第二,固件确实来自持有私钥的服务器(真实性)。

这里我画个流程图,帮你理清思路:

数字签名生成与验证流程 固件文件 SHA-256 哈希 私钥签名 数字签名 网络传输 接收固件 SHA-256 哈希 公钥验签 哈希对比验证 验证通过 ✓

实战建议:在嵌入式设备上,公钥验签通常比私钥签名快得多。所以签名放在服务器端(算力充足),验签放在设备端(资源受限)。这个分工很合理。

3.5 嵌入式环境下的选型建议

好了,理论讲完了,咱们来点实际的。在嵌入式OTA场景下,我建议这样搭配:

  • 固件加密:用 AES-128-GCM。GCM模式自带认证,能同时保证机密性和完整性。
  • 密钥交换:用 ECDH(椭圆曲线Diffie-Hellman)协商会话密钥。
  • 数字签名:用 ECDSA with SHA-256。签名长度短,验签速度快。
  • 固件完整性:用 SHA-256 哈希链,逐块校验。

我曾经踩过的坑:有一次我图省事,直接用RSA-2048做签名,结果在Cortex-M0上验签一次花了将近2秒。用户升级固件要等半天,体验极差。后来换成ECDSA with secp256r1,验签时间降到100毫秒以内。所以,算法选型一定要考虑MCU的算力。