第1章:安全存储——把密钥锁进芯片的“保险柜”

做嵌入式安全这么多年,我见过太多产品因为密钥泄露而“裸奔”。

你想想看,一把剃须刀要是密钥被人读走了,那跟没锁门有什么区别?

今天咱们聊聊怎么把密钥和证书,安全地存进MCU内部。

1.1 为什么不能把密钥放Flash里?

很多工程师习惯把密钥定义成一个const数组,直接编译进固件。

嗯,这其实是个大坑。

Flash里的数据,用个编程器就能读出来。我见过有人用J-Link直接dump整个Flash,密钥一览无余。

说白了,Flash是“可读”的。只要你能访问调试接口,或者通过漏洞执行任意代码,密钥就没了。

⚠️ 注意:

千万不要把明文密钥放在Flash或外部EEPROM里。这是最基本的安全红线。

1.2 OTP和eFuse是什么?

OTP,全称One-Time Programmable,一次性可编程。

eFuse,电子熔丝,本质也是一种OTP技术。

它们的特点很直接:

  • 只能写一次——烧进去就改不了
  • 物理不可读——普通调试接口读不到
  • 掉电不丢失——跟Flash一样持久

我习惯把OTP/eFuse比作芯片内部的“保险柜”。

你把密钥放进去,锁上门,钥匙交给硬件。

软件想用密钥?可以,但只能通过硬件加密引擎间接使用,不能直接拿到明文。

1.3 实际怎么用?

以STM32为例,它内部有OTP区域,一般是512字节或1KB。

烧录流程大概是这样的:

// 伪代码示例:烧录密钥到OTP
void burn_key_to_otp(uint8_t* key, uint32_t len) {
    // 1. 解锁OTP
    HAL_FLASHEx_OTP_Unlock();
    
    // 2. 写入密钥
    for (uint32_t i = 0; i < len; i++) {
        HAL_FLASHEx_OTP_Program(OTP_BASE + i, key[i]);
    }
    
    // 3. 锁定OTP,防止再写
    HAL_FLASHEx_OTP_Lock();
    
    // 4. 验证写入
    uint8_t verify = *(uint8_t*)(OTP_BASE + 0);
    if (verify != key[0]) {
        // 烧录失败,需要报废芯片
        Error_Handler();
    }
}
💡 经验之谈:

烧录OTP之前,一定要先做校验。一旦烧错,这颗芯片就废了。我曾经因为烧录脚本写错,一次报废了50颗芯片……从那以后,我每次烧OTP前都会做三次确认。

1.4 设备证书怎么存?

设备证书通常比密钥大,可能几百字节甚至几KB。

OTP空间有限,怎么办?

我的做法是:

  1. 证书指纹存OTP——只存SHA256哈希,32字节就够了
  2. 证书本体存加密Flash——用OTP里的密钥加密后存到Flash
  3. 运行时解密验证——先解密证书,再比对指纹

这样既节省了OTP空间,又保证了证书的完整性。

1.5 避坑指南

我踩过不少坑,挑几个典型的说说:

  • OTP空间不够用——提前规划好,密钥、证书指纹、设备序列号,哪个更重要?
  • 烧录后无法测试——OTP烧了就不能改,生产测试要放在烧录之前
  • 读保护没开——OTP虽然硬件不可读,但如果你没开读保护,调试接口还是能读到
🔑 核心要点:

OTP/eFuse不是万能的。它只是增加了攻击成本,不是绝对安全。但相比Flash存储,安全性提升了几个数量级。

1.6 总结

安全存储这件事,说白了就是“把鸡蛋放在硬件保险柜里”。

OTP和eFuse是目前嵌入式设备里最靠谱的方案。

我建议你在设计初期就把密钥存储方案定下来,别等产品量产了再回头改,那代价就大了。

下一章,咱们聊聊怎么用硬件加密引擎,让密钥“用而不见”。