3. 密码学基础:对称加密(AES)、非对称加密(RSA/ECC)、哈希函数(SHA-256)在嵌入式中的实现
好,咱们直接切入正题。这一章聊的是密码学在嵌入式里的落地。说实话,很多工程师觉得密码学是数学家的活儿,跟我们搞硬件的没关系。我以前也这么想,直到有一次产品送审被打了回来——合规报告上写着「数据存储未加密,存在泄露风险」。嗯,从那以后,我再也不敢小看这几行加密代码了。
在医疗贴片这种资源受限的设备上,密码学不是「要不要用」的问题,而是「怎么用才不把MCU跑死」的问题。你想想看,一个Cortex-M0内核,主频才几十兆,Flash和RAM都抠抠搜搜的,你还想跑完整的OpenSSL?不现实。所以,咱们得挑最合适的算法,用最省资源的方式去实现。
3.1 对称加密:AES在MCU上的轻量实现
对称加密,说白了就是加密和解密用同一把钥匙。在嵌入式里,AES是绝对的主流。我个人习惯用AES-128,为什么?因为AES-256虽然更安全,但在低端MCU上,密钥扩展和轮数计算会多耗不少时间。对于医疗贴片这种数据量不大、实时性要求高的场景,AES-128完全够用。
我在项目中遇到过一个问题:直接用软件实现AES,一个128位的数据块加密要花掉好几毫秒。这在心电数据采集里是不能接受的——你这边还在加密,那边新的采样点已经来了。后来我换成了硬件AES加速器,很多MCU(比如STM32、NXP的LPC系列)都内置了这个外设。配置好之后,加密一个块只需要几十个时钟周期。
这里给一个简单的硬件AES初始化示例(伪代码):
// 假设MCU有AES外设
void aes_hw_init(void) {
// 使能AES时钟
RCC->AHBENR |= RCC_AHBENR_AESEN;
// 配置密钥(128位)
AES->KEYR0 = key[0];
AES->KEYR1 = key[1];
AES->KEYR2 = key[2];
AES->KEYR3 = key[3];
// 选择模式:ECB或CBC
AES->CR |= AES_CR_MODE_ENCRYPT | AES_CR_CHMOD_ECB;
// 启动
AES->CR |= AES_CR_EN;
}
void aes_hw_encrypt(uint32_t *plaintext, uint32_t *ciphertext) {
// 写入明文
AES->DINR = plaintext[0];
AES->DINR = plaintext[1];
AES->DINR = plaintext[2];
AES->DINR = plaintext[3];
// 等待完成
while(!(AES->SR & AES_SR_CCF));
// 读取密文
ciphertext[0] = AES->DOUTR;
ciphertext[1] = AES->DOUTR;
ciphertext[2] = AES->DOUTR;
ciphertext[3] = AES->DOUTR;
}
3.2 非对称加密:RSA与ECC的取舍
非对称加密,就是公钥加密、私钥解密。在医疗贴片里,它的主要用途是密钥交换和数字签名。RSA和ECC是两大主流,但说实话,在嵌入式里,我强烈推荐ECC。
为什么?我给你算笔账。RSA-2048的密钥长度是2048位,而ECC-256(比如secp256r1曲线)提供相同的安全强度,密钥长度只有256位。在MCU上,这意味着更少的存储空间、更少的传输带宽、更快的计算速度。我曾经在一个项目里对比过:同样的签名操作,RSA-2048用了2.3秒,ECC-256只用了0.4秒。在医疗贴片这种电池供电的设备上,这差距就是能不能用的区别。
不过,ECC的实现比RSA复杂得多。你想想看,椭圆曲线上的点加、点乘、模逆运算,每一步都要小心溢出和时序攻击。我个人习惯用mbedTLS或者MicroECC这样的成熟库,而不是自己手写。自己写的ECC,十有八九有侧信道漏洞。
这里展示一个ECC密钥生成的简化流程:
// 使用MicroECC库生成密钥对
uint8_t private_key[32]; // 256位私钥
uint8_t public_key[64]; // 256位公钥(x和y坐标各32字节)
// 生成随机数作为私钥
trng_get_random_bytes(private_key, 32);
// 计算公钥:公钥 = 私钥 * G(基点)
ecc_make_key(public_key, private_key);
// 注意:私钥必须安全存储,比如写入MCU的OTP区域或安全元件
3.3 哈希函数:SHA-256的固件完整性校验
哈希函数,说白了就是「数字指纹」。SHA-256能把任意长度的数据,压缩成一个256位的固定长度摘要。在医疗贴片里,它的主要用途是固件完整性校验和消息认证。
我记得有一次,客户要求我们的贴片在每次启动时都要校验固件是否被篡改。实现方式很简单:在固件烧录时,计算整个固件镜像的SHA-256哈希值,存储在Flash的末尾。每次启动时,Bootloader重新计算哈希,跟存储的值比对。如果不一致,说明固件被改了,直接拒绝启动。
SHA-256在嵌入式里的实现,我建议用硬件加速器(如果有的话),或者用经过优化的软件实现。很多MCU厂商都提供了硬件SHA引擎,比如STM32的HASH外设。如果没有硬件支持,mbedTLS的软件实现也足够快——在72MHz的Cortex-M3上,处理1KB数据大约需要1-2毫秒。
一个简单的固件校验流程:
// Bootloader中的固件校验
bool verify_firmware(void) {
uint8_t expected_hash[32];
uint8_t computed_hash[32];
// 从Flash末尾读取预存的哈希值
memcpy(expected_hash, (uint8_t*)(FIRMWARE_END - 32), 32);
// 计算固件区域的SHA-256
sha256_start(&ctx);
sha256_update(&ctx, (uint8_t*)FIRMWARE_START, FIRMWARE_SIZE - 32);
sha256_finish(&ctx, computed_hash);
// 比对
if (memcmp(expected_hash, computed_hash, 32) == 0) {
return true; // 固件完整
} else {
return false; // 固件被篡改
}
}
3.4 三种算法的协同使用
在实际的医疗贴片产品里,这三种算法不是孤立使用的,而是协同工作。我给你画一个典型的场景:
- 设备配对阶段: 使用ECC生成密钥对,通过非对称加密交换AES的会话密钥。
- 数据传输阶段: 使用AES-CBC或AES-GCM加密患者的心电数据。
- 固件升级阶段: 使用SHA-256校验固件包的完整性,再用ECC签名验证固件包的来源。
你想想看,如果只用AES,密钥怎么安全地分发给设备?如果只用ECC,加密速度又太慢。所以,混合使用才是王道。我习惯把ECC叫做「钥匙管理员」,AES叫做「数据搬运工」,SHA-256叫做「数据检验员」。各司其职,效率最高。
好了,这一章的内容就到这里。密码学在嵌入式里的实现,说白了就是「在资源受限的条件下,找到安全与性能的平衡点」。下一章我们会聊到安全启动和固件签名,到时候会用到今天讲的哈希和ECC知识。嗯,先消化一下这些内容吧。