3、密码学基础(上):对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA-256)

各位同学,欢迎来到密码学基础的第一讲。说实话,很多搞嵌入式开发的朋友一听到「密码学」三个字就头大,觉得那是数学博士才该碰的东西。其实不然。你想想看,我们做智能音箱的,每天要处理多少敏感数据?用户的语音指令、家庭Wi-Fi密码、甚至支付信息。没有密码学,这些东西就像写在明信片上寄出去一样。

我个人习惯把密码学比作「锁匠的工具箱」。今天咱们就打开这个工具箱,看看最常用的三把锁:AES、RSA/ECC、还有SHA-256。别担心,我不会跟你推公式,咱们就聊聊它们怎么用、用在哪儿、以及我踩过的坑。

3.1 对称加密:AES

对称加密,说白了就是「一把钥匙开一把锁」。加密和解密用同一个密钥。你想想看,就像你家大门钥匙,锁门和开门都是它。

在IoT世界里,AES(Advanced Encryption Standard)是绝对的主角。为什么?因为它快。我在项目中做过测试,同样一块Cortex-M4芯片,AES-128加密1KB数据只需要几十微秒,而RSA要几百毫秒。对于智能音箱这种需要实时响应的设备,速度就是生命。

AES的关键参数

  • 密钥长度:128位、192位、256位。我个人建议,能上256就别用128。虽然128位在理论上足够安全,但谁知道量子计算哪天就成熟了呢?
  • 工作模式:ECB、CBC、CTR、GCM等。这里我要特别提醒:千万别用ECB模式。我曾经接手过一个项目,前任工程师用了ECB模式加密固件,结果同样的明文块会产生同样的密文块,攻击者通过模式识别就能猜出固件结构。后来我们全部改成了GCM模式,既加密又认证,一步到位。
  • IV(初始化向量):每次加密都要用不同的IV。我见过有人把IV写死在代码里,那跟没用加密有什么区别?

实战经验:在智能音箱的固件加密中,我通常使用AES-256-GCM。密钥存储在芯片的OTP(一次性可编程)区域,IV由硬件随机数生成器产生。这样即使攻击者拿到了固件镜像,没有密钥也白搭。

// AES-256-GCM 加密示例(伪代码)
uint8_t key[32];   // 256位密钥,从OTP读取
uint8_t iv[12];    // 96位IV,由硬件RNG生成
uint8_t plaintext[] = "固件数据块";
uint8_t ciphertext[sizeof(plaintext)];
uint8_t tag[16];   // 认证标签

aes_gcm_encrypt(key, iv, plaintext, ciphertext, tag);
// 将ciphertext和tag一起存储到Flash中

小技巧:如果你的芯片没有硬件AES加速器,可以考虑使用XTS模式。它在软件实现上效率更高,而且适合存储加密场景。

3.2 非对称加密:RSA与ECC

对称加密有个致命问题:密钥怎么安全地传给对方?你想想看,如果我把家门钥匙快递给你,快递员不就能复制一把吗?

非对称加密解决了这个问题。它有两把钥匙:公钥公开,私钥保密。公钥加密的数据只能用私钥解密,反之亦然。这就像你家的信箱,任何人都可以往里面投信(用公钥加密),但只有你有钥匙打开它(用私钥解密)。

RSA:老当益壮

RSA是目前最广泛使用的非对称加密算法。它的安全性基于大整数分解的困难性。嗯,说白了就是:两个大质数相乘很容易,但把乘积分解回两个质数很难。

在IoT中,RSA主要用于两个场景:

  • 数字签名:用私钥签名固件,设备用公钥验证签名。确保固件没有被篡改。
  • 密钥交换:用公钥加密AES密钥,然后传输给设备。设备用私钥解密得到AES密钥。

但RSA有个问题:密钥越长,计算越慢。2048位的RSA签名在低端MCU上可能要几秒钟。我做过一个项目,用STM32F103做RSA-2048签名验证,结果启动时间从2秒变成了15秒。用户投诉说「音箱开机比微波炉还慢」。

ECC:后起之秀

ECC(椭圆曲线密码学)用更短的密钥提供相同的安全强度。256位的ECC相当于3072位的RSA。这意味着什么?更少的存储空间、更快的计算速度、更低的功耗。

我个人现在更倾向于ECC。在智能音箱的固件签名中,我使用ECDSA(椭圆曲线数字签名算法)。签名长度只有64字节(对于P-256曲线),而RSA-2048的签名是256字节。对于Flash空间紧张的IoT设备,这差别太大了。

安全级别 RSA密钥长度 ECC密钥长度 签名大小
80位 1024 160 40字节
112位 2048 224 56字节
128位 3072 256 64字节
192位 7680 384 96字节

注意:ECC虽然好,但实现起来比RSA复杂。我曾经在移植mbedTLS的ECC库时,因为曲线参数配置错误,导致签名验证一直失败。折腾了两天才发现是域参数的小端序问题。所以,如果你刚开始接触ECC,建议先用成熟的库(如mbedTLS、OpenSSL),不要自己实现。

3.3 哈希函数:SHA-256

哈希函数,你可以把它想象成「数字指纹」。不管输入多长,输出都是固定长度(SHA-256输出256位)。而且,输入稍微改一点,输出就面目全非。这就是所谓的「雪崩效应」。

在固件安全中,SHA-256的用途太多了:

  • 固件完整性校验:计算固件的哈希值,与官方发布的哈希值对比。如果不同,说明固件被篡改了。
  • 数字签名的一部分:先对固件做哈希,再对哈希值做签名。这样签名速度快,而且能保证固件完整性。
  • 密码存储:存储密码的哈希值,而不是明文。即使数据库泄露,攻击者也拿不到原始密码。

这里我要讲一个我踩过的坑。有一次,我在做OTA升级的固件校验,用了SHA-256计算固件哈希。结果发现,每次计算出来的哈希值都不一样。查了半天,原来是固件文件末尾有一个换行符在作怪。Windows和Linux的换行符不同,导致哈希值不同。从那以后,我每次计算哈希前都会明确指定「二进制模式」读取文件。

// SHA-256 计算示例
#include "mbedtls/sha256.h"

uint8_t firmware_data[1024]; // 假设固件数据
uint8_t hash[32];            // SHA-256输出

mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0); // 0表示SHA-256,1表示SHA-224
mbedtls_sha256_update(&ctx, firmware_data, sizeof(firmware_data));
mbedtls_sha256_finish(&ctx, hash);
mbedtls_sha256_free(&ctx);

// 现在hash数组里就是固件的数字指纹

避坑指南:我曾经在项目中遇到一个问题:SHA-256计算出来的哈希值,在设备端和服务器端总是不一致。后来发现是字节序的问题。ARM Cortex-M默认是小端序,而服务器可能是大端序。所以,在传输哈希值时,一定要统一字节序。我通常使用网络字节序(大端序)作为标准。

3.4 三者如何配合?

你可能会问:这三种算法到底怎么用在一起?我给你画个简单的流程图:

  1. 固件发布时:先用SHA-256计算固件哈希,然后用ECC私钥对这个哈希值签名。固件 + 签名一起发布。
  2. 设备启动时:先用SHA-256计算收到的固件哈希,然后用ECC公钥验证签名。如果签名验证通过,说明固件是官方发布的、没有被篡改。
  3. 固件解密时:用AES-256-GCM解密固件内容。AES密钥由RSA或ECC密钥交换协议安全传输。

你看,三种算法各司其职:SHA-256保证完整性,ECC/RSA保证真实性,AES保证机密性。缺一不可。

总结一下:密码学不是魔法,它只是工具。你不需要成为数学家才能用好它。记住三点:AES用来加密数据,RSA/ECC用来签名和交换密钥,SHA-256用来校验完整性。下次我们讲如何把这些算法真正部署到智能音箱的启动流程中。

嗯,今天就到这里。如果你在实际项目中遇到什么问题,欢迎在课程群里交流。我见过太多因为密码学使用不当导致的安全事故,希望你能少走弯路。