3. 密码学基础(上):对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA256)的原理与选型

各位同学,欢迎来到密码学基础的第一部分。说实话,很多做嵌入式开发的兄弟,一听到「密码学」三个字就头大,觉得那是数学博士才搞的东西。其实不然。你想想看,我们做物联网MCU的,每天跟传感器数据、固件升级、设备认证打交道,密码学就是我们的看门狗——平时不觉得,一旦丢了钥匙,那才叫欲哭无泪。

我个人习惯把密码学分成三大块:对称加密非对称加密哈希函数。今天咱们就把这三兄弟挨个捋一遍,重点讲清楚它们「是什么、怎么用、选哪个」。

3.1 对称加密:AES 的前世今生

对称加密,说白了就是「一把钥匙开一把锁」。加密和解密用同一个密钥。你想想看,这就像你家的门锁,出门用钥匙锁上,回家用同一把钥匙打开。简单粗暴,效率高。

在物联网领域,AES(Advanced Encryption Standard) 是绝对的主角。为什么?因为它在硬件实现上极其高效,很多MCU甚至内置了AES硬件加速模块。我在项目中遇到过用软件模拟AES的情况,那叫一个慢——一个128字节的数据包加密要花几十毫秒,这在实时控制场景下根本没法用。

3.1.1 AES 的核心参数

参数 说明 我的建议
密钥长度 128 / 192 / 256 位 MCU上优先选128位,平衡安全与性能
分组大小 固定128位(16字节) 注意数据填充,PKCS7最常用
工作模式 ECB / CBC / CTR / GCM 等 千万别用ECB!CBC或GCM更安全

这里我要特别强调一下工作模式的选择。我曾经接手过一个智能门锁的项目,前任工程师用了AES-ECB模式。结果呢?同样的明文块会产生同样的密文块,攻击者只要看到重复的密文模式,就能猜出你按了哪个密码键。嗯,这就是典型的「看起来加密了,实际上等于没加密」。

⚠️ 避坑指南: 我曾经在调试一个NB-IoT终端时,发现AES-CBC模式下的IV(初始化向量)被写死了全0。这意味着每次加密的起始状态都一样,安全性大打折扣。正确的做法是:每次加密都生成随机IV,并随密文一起传输。

3.1.2 AES 在MCU上的实现

如果你的MCU有硬件AES,那直接用寄存器操作就好。如果没有,那就得用软件实现了。这里给出一段伪代码示例,展示AES-CBC加密的典型流程:

// AES-128 CBC 加密示例(伪代码)
uint8_t key[16] = {0x2b, 0x7e, ...};  // 128位密钥
uint8_t iv[16]  = {0x00, 0x01, ...};  // 随机IV
uint8_t plaintext[32] = {...};         // 待加密数据

// 第一步:初始化AES引擎
aes_init(key, AES_128);

// 第二步:CBC模式加密
aes_cbc_encrypt(plaintext, 32, iv, ciphertext);

// 第三步:发送IV + 密文
uart_send(iv, 16);
uart_send(ciphertext, 32);

你可能会问:「为什么要把IV也发出去?」因为接收端需要同样的IV才能解密。IV本身不需要保密,但必须保证每次加密都不同。这就像你每次出门前换一把新锁——锁本身可以公开,但钥匙必须保密。

3.2 非对称加密:RSA 与 ECC 的较量

对称加密有个致命弱点:密钥分发。你想想看,如果我要给一万台设备分发密钥,总不能派人挨个去烧录吧?这时候就需要非对称加密登场了。

非对称加密使用一对密钥:公钥私钥。公钥可以公开,私钥必须保密。用公钥加密的数据只能用私钥解密,反之亦然。这就像你家的信箱——任何人都可以把信塞进去(用公钥加密),但只有你有钥匙打开(用私钥解密)。

3.2.1 RSA:老当益壮

RSA 基于大整数分解的数学难题。简单说就是:两个大质数相乘很容易,但把乘积分解回两个质数极难。RSA 的密钥长度通常为 1024、2048 或 4096 位。

但在MCU上,RSA 有个大问题:。我记得有一次做 OTA 固件升级签名验证,用 RSA-2048 验签一次要花 2 秒钟。对于电池供电的设备来说,这简直是灾难。而且 RSA 的密钥和签名长度都很长,2048 位的密钥意味着 256 字节的签名,这在窄带物联网场景下传输成本很高。

💡 经验之谈: 我个人习惯在MCU上只用RSA做签名验证,不做加密。因为验签只需要公钥,私钥可以安全地保存在服务器端。这样既利用了RSA的安全性,又避免了私钥泄露的风险。

3.2.2 ECC:后起之秀

ECC(椭圆曲线密码学)是近年来在物联网领域大放异彩的方案。它基于椭圆曲线离散对数难题,在同等安全强度下,密钥长度远小于RSA。

安全等级 RSA密钥长度 ECC密钥长度
80位 1024位 160位
112位 2048位 224位
128位 3072位 256位
256位 15360位 512位

看到没?128位安全等级下,ECC只需要256位密钥,而RSA需要3072位。这意味着什么?意味着ECC在MCU上运算更快、占用内存更少、传输带宽更低。我在一个LoRaWAN终端项目中,用ECC-256做密钥交换,整个握手过程只用了不到100毫秒,而如果用RSA-2048,至少需要1秒以上。

🔧 选型建议: 对于资源受限的MCU(如Cortex-M0/M3),优先选择ECC。具体曲线推荐使用 NIST P-256 或 Curve25519。前者兼容性好,后者性能更优。我个人在量产项目中更倾向于 Curve25519,因为它对侧信道攻击有天然抵抗力。

3.3 哈希函数:SHA256 的不可逆魔法

哈希函数,说白了就是「指纹提取器」。它能把任意长度的数据,压缩成一个固定长度的摘要。而且这个摘要是不可逆的——你无法从摘要反推出原始数据。更重要的是,哪怕原始数据只改了一个比特,摘要也会面目全非。

SHA256 是 SHA-2 家族的一员,输出256位(32字节)的哈希值。它在物联网中主要有三个用途:

  1. 固件完整性校验:下载固件后计算SHA256,与官方公布的哈希值对比,确保固件没被篡改。
  2. 密码存储:不存明文密码,只存密码的哈希值。即使数据库泄露,攻击者也拿不到原始密码。
  3. 数字签名的一部分:先对消息做哈希,再对哈希值做签名,效率更高。

这里我要提醒一句:SHA256 不是加密。加密是可逆的,哈希是不可逆的。我见过有新手把哈希函数当加密用,结果数据存进去就再也取不出来了——嗯,那场面相当尴尬。

3.3.1 SHA256 在MCU上的实现

大多数现代MCU都内置了SHA256硬件加速。如果没有,也可以用软件实现,但要注意性能。下面是一个典型的SHA256计算流程:

// SHA256 哈希计算示例
uint8_t data[] = "Hello, IoT World!";
uint8_t hash[32];  // 256位 = 32字节

// 初始化SHA256上下文
sha256_context_t ctx;
sha256_init(&ctx);

// 更新数据(可以分多次调用)
sha256_update(&ctx, data, strlen(data));

// 计算最终哈希
sha256_final(&ctx, hash);

// 打印哈希值(十六进制)
for(int i = 0; i < 32; i++) {
    printf("%02x", hash[i]);
}

你可能会问:「为什么要有 update 和 final 两步?」因为实际应用中,数据可能是分片到达的。比如从网络接收固件,一次只能收 1KB,但固件有 1MB。这时候就可以每收到一片就调用一次 update,最后再 final 得到完整的哈希值。这比把所有数据攒齐再计算要灵活得多。

⚠️ 避坑指南: 我曾经在调试一个OTA升级模块时,发现固件校验总是失败。查了两天才发现,问题出在哈希计算时把固件头部的版本号字段也包含进去了。而服务器端计算哈希时排除了这个字段。记住:哈希计算的数据范围必须严格一致,多一个字节、少一个字节都不行。

3.4 选型决策:到底该用哪个?

好了,三种密码学工具都介绍完了。你可能会问:「那我到底该用哪个?」别急,我根据实际项目经验,给你一个简单的决策树:

  • 需要加密大量数据? → 用 AES-128-CBC 或 AES-128-GCM。GCM模式自带认证,能防篡改。
  • 需要安全分发密钥? → 用 ECC-256 做密钥交换。如果必须兼容旧系统,才考虑 RSA-2048。
  • 需要校验数据完整性? → 用 SHA256。简单、高效、安全。
  • 需要数字签名? → 用 ECDSA(基于ECC的签名算法)。签名短、验签快。

最后说一句:不要自己发明密码算法。我见过太多「我觉得这个算法不够安全,我改一下」的案例,结果无一例外都出了问题。用标准算法、用成熟库、用硬件加速——这才是嵌入式工程师该做的事。

下一节我们会讲密码学基础的下半部分:密钥管理、随机数生成和侧信道攻击防护。到时候我会分享一个我在智能电表项目中踩过的坑——嗯,那个坑让我加了一个月的班。咱们下节课见。