2、密码学基础(上):对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA256)在嵌入式中的应用

各位同学,咱们今天聊聊嵌入式安全里最基础、也最绕不开的东西——密码学。说实话,我刚入行那会儿,觉得密码学就是数学家的玩具,跟我一个写C的固件工程师有啥关系?直到有一次,我负责的一个IoT产品被爆出通信数据被截获,客户直接投诉到老板那里……嗯,从那以后,我再也不敢小看这几个算法了。

嵌入式环境跟PC服务器不一样。你想想看,我们的MCU可能只有几十KB的RAM,主频才几十兆赫兹。在这种资源受限的环境下,怎么把AES、RSA、ECC、SHA256这些“大块头”算法塞进去,还得跑得稳、跑得快?这就是今天要啃的硬骨头。

2.1 对称加密:AES在MCU上的“轻装上阵”

对称加密,说白了就是加密和解密用同一把钥匙。就像你家门锁,用这把钥匙锁上,还得用同一把钥匙打开。在嵌入式里,最常用的对称加密算法就是AES。

AES的三种模式,我建议你记牢:

  • ECB模式:最简单,但最不安全。同样的明文块会加密成同样的密文块。我在一个门禁项目里见过有人用ECB,结果攻击者通过分析密文模式直接猜出了开门指令。别用ECB,除非你只想做个演示。
  • CBC模式:最常用。每个明文块先跟上一个密文块异或,再加密。需要初始化向量(IV)。我个人习惯用CBC,安全性够用,实现也不复杂。
  • CTR模式:把AES当流密码用。可以并行计算,适合对性能要求高的场景。但要注意,密钥和计数器绝对不能重复使用,否则加密就形同虚设。

核心要点:在嵌入式里,AES-128已经足够安全。AES-256虽然更安全,但计算量大了将近一倍,对电池供电的设备不太友好。我一般选AES-128-CBC,性价比最高。

下面是一个在STM32上使用硬件AES加速的代码片段。注意,很多MCU都有硬件AES模块,千万别用软件去算,那太慢了。

// 使用STM32硬件AES加密一个块
// 假设你已经初始化了AES外设
void aes_encrypt_block(uint8_t *plaintext, uint8_t *key, uint8_t *iv, uint8_t *ciphertext) {
    // 设置密钥
    HAL_CRYP_SetKey(&hcryp, key, CRYP_KEY_SIZE_128);
    // 设置IV
    HAL_CRYP_SetInitVector(&hcryp, iv);
    // 执行CBC加密
    HAL_CRYP_AESCBC_Encrypt(&hcryp, plaintext, 16, ciphertext, 1000);
}

避坑指南:我曾经在一个项目中,直接用了库函数默认的AES实现,结果发现每次加密耗时超过100毫秒。后来换成硬件AES,耗时直接降到微秒级。所以,能用硬件就别用软件,这是嵌入式密码学的第一铁律。

2.2 非对称加密:RSA与ECC的“公私分明”

非对称加密,就是加密和解密用不同的钥匙。一把公钥公开,一把私钥自己藏着。这玩意儿在嵌入式里主要用来做密钥交换和数字签名。

RSA:老牌劲旅,但有点“重”

RSA基于大整数分解的数学难题。安全性靠密钥长度保证,现在至少要用2048位。但问题来了——2048位的RSA在MCU上做一次签名,可能要几百毫秒甚至几秒。我做过一个测试,在Cortex-M4上,RSA-2048签名耗时约1.2秒。这对实时性要求高的设备来说,基本不可接受。

ECC:后起之秀,轻量级选手

ECC基于椭圆曲线离散对数难题。它的最大优势是:同等安全强度下,密钥长度短得多。比如,256位的ECC就能达到3072位RSA的安全等级。密钥短,意味着计算快、存储小、功耗低。这对嵌入式来说,简直是天选之子。

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

我的建议:新项目优先选ECC,特别是NIST P-256曲线。它已经被广泛验证,硬件支持也越来越多。RSA虽然成熟,但在嵌入式里越来越力不从心。

不过,ECC也有坑。比如,曲线参数的选择非常敏感。选错了曲线,或者实现时没做侧信道防护,密钥分分钟被侧信道攻击提取出来。我见过一个团队,ECC实现没做常数时间处理,结果攻击者通过分析功耗曲线,直接还原了私钥。

警告:千万不要自己实现ECC或RSA算法。数学细节太容易出错了。用mbedTLS、OpenSSL(裁剪版)或者厂商提供的加密库。相信我,你写的ECC实现大概率跑不过侧信道攻击。

2.3 哈希函数:SHA256的“数字指纹”

哈希函数,就是把任意长度的数据,压缩成一个固定长度的摘要。这个摘要就像数据的“指纹”——内容变了,指纹一定变。在嵌入式里,SHA256主要用来做固件完整性校验和数字签名中的哈希步骤。

SHA256输出256位(32字节)的哈希值。它的计算过程涉及大量的移位、异或和逻辑运算。在MCU上,纯软件实现SHA256的速度还可以,但如果你要频繁计算(比如OTA升级时校验每个数据包),还是建议用硬件加速。

SHA256在嵌入式中的典型应用场景:

  • 固件完整性校验:启动时计算固件哈希,跟存储的哈希值比对。不一致就拒绝启动。
  • 数字签名验证:先用SHA256对固件做哈希,再用RSA或ECC对哈希值签名。验证时同样先算哈希,再验签。
  • 密钥派生:用SHA256对主密钥和上下文信息做哈希,派生出会话密钥。

下面是一个用mbedTLS计算SHA256哈希的例子:

#include "mbedtls/sha256.h"

void compute_sha256(uint8_t *data, size_t len, uint8_t output[32]) {
    mbedtls_sha256_context ctx;
    mbedtls_sha256_init(&ctx);
    mbedtls_sha256_starts(&ctx, 0); // 0表示SHA-256,1表示SHA-224
    mbedtls_sha256_update(&ctx, data, len);
    mbedtls_sha256_finish(&ctx, output);
    mbedtls_sha256_free(&ctx);
}

小技巧:在计算大块数据的哈希时(比如整个固件),不要一次性把所有数据都喂给SHA256。用mbedtls_sha256_update分块处理,这样能节省RAM。我习惯每次喂1KB,既不会太慢,也不会占用太多内存。

2.4 实战中的“组合拳”:混合加密方案

在实际的嵌入式安全启动中,很少只用一种算法。通常是“混合加密”——用非对称加密传递对称密钥,用对称加密加密实际数据,用哈希做完整性校验。

举个例子,安全启动的典型流程:

  1. 设备出厂时,烧录公钥(ECC公钥)和固件的哈希值。
  2. 启动时,Bootloader用SHA256计算当前固件的哈希。
  3. 用ECC公钥验证固件签名(签名是厂商用ECC私钥对固件哈希做的)。
  4. 验证通过后,用AES密钥解密固件(固件在传输或存储时是加密的)。
  5. 跳转到解密后的固件执行。

你看,这里ECC、SHA256、AES全用上了。每种算法各司其职,谁也离不开谁。

总结一下:

  • AES:加密数据,速度快,适合大量数据。
  • RSA/ECC:加密密钥或签名,安全性高,但速度慢。
  • SHA256:完整性校验,确保数据没被篡改。

这三者组合起来,才能构建一个相对安全的嵌入式系统。别指望单一算法能解决所有问题。

好了,这一章的内容就到这儿。下一章我们会深入具体的“安全启动”流程,看看这些算法是怎么在Bootloader里落地实现的。到时候我会拿一个实际项目的代码来拆解,保证干货满满。