嵌入式如何加密固件

AI助手已提取文章相关产品:

嵌入式如何加密固件:从实战出发,构建真正可信的系统

你有没有遇到过这样的情况?产品刚上市没几个月,市面上就出现了“高仿版”,功能一模一样,价格却低了三成。拆开一看,Flash芯片被读走了,你的固件原封不动地躺在对手的板子上运行着——而这一切,仅仅是因为 固件没加密

这并不是危言耸听。在物联网爆发的今天,每一块跑在智能门锁、工业PLC、医疗设备上的MCU,都是潜在的攻击目标。攻击者不需要破解算法,只要物理接触设备,就能通过JTAG或SPI读取Flash内容,再用IDA Pro逆向分析,核心逻辑瞬间暴露无遗。

更可怕的是,有些厂商以为加个校验和、改个文件头就叫“防抄”,殊不知这些手段连入门级黑客都挡不住。真正的固件保护,必须建立在 硬件信任根 + 密码学机制 + 安全生命周期管理 三位一体的基础上。

那么问题来了:我们到底该怎么给嵌入式固件上锁?不是贴个标签式的“已加密”,而是让攻击者即使拿到二进制文件,也像面对一本用未知语言写成的天书?


为什么传统思路行不通?

先泼一盆冷水:如果你还在想着“我把代码混淆一下”或者“我用自定义格式存固件”,那基本等于裸奔。

原因很简单:

  • 资源受限 ≠ 安全天然
    很多人觉得MCU性能弱、工具链封闭,所以难被攻击。错!正因如此,攻击者反而更愿意花时间研究这类系统——一旦突破,回报极高。而且现在的逆向工具(如Ghidra)对ARM Cortex-M支持极好,符号恢复、控制流还原都不是问题。

  • 软件层防护容易绕过
    比如你在启动时做一次CRC校验?攻击者直接跳过这段代码就行。你在内存中解密后再执行?那就dump内存呗。没有硬件支撑的“安全”都是空中楼阁。

  • 密钥存在代码里 = 白送
    把AES密钥硬编码在源码中?静态分析几分钟就能扒出来。就算你用字符串拼接、异或隐藏,现代反汇编器也能自动识别常量并重建表达式。

所以,真正有效的固件加密,必须满足三个铁律:

机密性 :即使Flash被完整读出,也无法获取原始程序
完整性 :任何篡改都会导致验证失败
真实性 :只能运行由合法发布者签名的固件

这三个目标,缺一不可。


AES vs RSA:别再问“哪个更好”了

说到加密,很多人第一反应是:“该用AES还是RSA?”——这个问题本身就错了。它们根本不是互斥选项,而是 分工合作的好搭档

先说结论:你要的是“RSA签名 + AES加密”的混合模式

就像保险柜+摄像头的组合:AES负责把数据锁起来(防看),RSA负责确认谁有开门资格(防冒充)。

🔐 对称加密之王:AES-128-GCM

为什么选它而不是DES、RC4或其他?

因为它是目前最适合嵌入式的AEAD(Authenticated Encryption with Associated Data)方案。什么意思?简单说就是: 一次操作,同时完成加密和防篡改校验

举个例子:

mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_GCM_ENCRYPT,
    ilen, iv, 12,
    NULL, 0, input, output,
    16, tag);

你看,输入明文 → 输出密文 + 一个16字节的MAC标签。这个标签就像是封条,哪怕只改了一个bit,解密时就会报错。

关键优势在哪?

  • 🚀 硬件加速普遍支持:STM32、GD32、ESP32等主流MCU都有专用AES引擎,吞吐量轻松破50MB/s
  • 💡 功耗极低:比纯软件实现快几十倍,CPU可以更快进入休眠
  • 🔁 支持流式处理:适合大固件分块解密加载

但注意!GCM模式有个致命坑: IV(又称Nonce)绝对不能重复使用 。否则可能泄露密钥 😱

所以强烈建议:
- 每台设备烧录唯一随机IV(可通过TRNG生成)
- 或者每次OTA升级更新IV,并记录在安全区域

小技巧:可以把设备UID的一部分作为IV种子,结合版本号哈希生成最终IV,既保证唯一性又避免存储压力。

🪪 非对称信任锚点:RSA-2048(或ECDSA)

AES解决了“怎么锁”的问题,但没解决“钥匙给谁”的问题。

想象一下:你把AES密钥存在Flash里,攻击者照样能读出来;你把它藏在代码逻辑里?静态分析照样能找到调用点。

怎么办?答案是: 根本不要传输密钥本身 ,而是用非对称加密来验证身份。

典型流程如下:

  1. 开发者用自己的私钥对固件哈希值进行签名
  2. 设备出厂前,把对应的公钥固化进芯片(比如OTP区)
  3. 启动时,设备用公钥验证签名是否有效
  4. 只有验证通过才允许继续执行

这样一来,私钥永远不出HSM(硬件安全模块),攻击者即便拿到固件镜像,也无法伪造签名。

为什么不直接用RSA加密整个固件?

  • 太慢!RSA-2048加密1KB数据要几毫秒,而AES不到1微秒
  • 有长度限制:RSA只能加密小于模数的数据(通常<245字节)

所以聪明的做法是: 用RSA保护AES密钥,或干脆只用来签名

✅ 推荐做法:固件头部包含SHA-256摘要 + RSA-2048签名,主体部分用AES-GCM加密。双保险!


安全启动:信任链是如何一步步建立起来的?

光有算法还不够。如果Bootloader可以被替换成恶意程序,那后面的验证全是徒劳。

这就是为什么需要 安全启动(Secure Boot) ——它不是某个功能,而是一整套 信任传递机制

信任从哪里开始?答案是:BootROM

所有安全的起点,必须是一个 无法修改的硬件信任根(Root of Trust) 。对于大多数MCU来说,这就是片内的一段只读代码——BootROM。

它的职责非常明确:
1. 上电后第一条指令从此处执行
2. 加载外部Flash中的Bootloader
3. 验证其数字签名
4. 如果失败,停机或进入恢复模式

这个过程就像海关查护照:你可以说自己是张三,但我得拿官方数据库比对指纹才行。

实际结构长什么样?

来看一个典型的带签名的固件头部设计:

typedef struct {
    uint32_t magic;           // 0x5048434D ("PHCM") 标识符
    uint32_t image_len;       // 固件体长度
    uint8_t  hash[32];        // SHA-256摘要
    uint8_t  signature[256];  // RSA-2048签名
    uint32_t version;         // 版本号,防降级
    uint8_t  nonce[12];       // GCM解密用IV
    uint8_t  reserved[44];
} firmware_header_t;

当BootROM读到这段头信息后,会做这几件事:

  1. 检查 magic 字段是否正确(防止误加载)
  2. 使用内置公钥对 hash 字段进行签名验证
  3. 成功后,用片内密钥+ nonce 解密后续固件至RAM
  4. 跳转执行

⚠️ 注意:公钥必须写死在芯片里!可以通过eFUSE一次性烧录,之后禁止读回或修改。

多级验证才是真·安全

别以为验证一次就够了。高级系统往往采用 多阶段信任链

BootROM → 验证 Bootloader
         ↓
Bootloader → 验证 Application
             ↓
Application → 验证 OTA包 / 外部插件

每一环都独立签名,形成链条。哪怕中间某一级被攻破,也不会影响上游。

比如NXP的i.MX RT系列就支持三级CA证书体系,甚至允许OEM厂商自建PKI。


片上安全元件(SE)真的值得吗?算笔账就知道了

现在越来越多高端MCU开始集成 安全元件(Secure Element) 或类似TPM的模块(如STM32H7B3的HSM)。有人会觉得:“我又不做金融支付,搞这么重的安全是不是过度设计?”

其实不然。当你面临以下任一场景时,SE的价值立刻显现:

  • 产品单价 > $50,担心被大规模仿制
  • 涉及用户隐私数据(如健康监测)
  • 支持远程升级(OTA),怕被植入后门
  • 需要符合CE/FCC/UL等认证要求

SE到底强在哪里?

普通MCU的安全边界是“芯片封装”,而SE的防护深入到晶体管级别:

防护类型 普通MCU SE
侧信道攻击(SPA/DPA) 易受攻击 内置噪声注入、随机化执行路径
物理探测(探针) 可能读出总线数据 总线加密,关键信号动态扰动
电压毛刺攻击 可能跳过验证逻辑 电压监控+自动擦除
时钟 glitch 可能中断比较循环 多时钟源检测

更重要的是: 密钥永不离开SE

什么意思?比如你要用私钥签名,外部CPU只需发送“请对这段哈希签名”的指令,SE内部完成运算后返回结果,私钥本身永远不会暴露在内存中。

这就好比银行金库:你可以委托保管箱服务帮你取东西,但管理员自己也没法打开你的箱子。

成本真的很高吗?

我们来算一笔账:

项目 普通MCU方案 带SE的MCU
单片成本 $1.5 $2.8
年产量 10万台 10万台
总成本差 —— $13万
预估防仿制收益 若被仿制损失30%市场 → $150万营收损失 减少侵权风险,维持溢价能力

看到没?多花十几万,换来的是品牌保护、法律维权底气和客户信任。尤其在汽车、医疗等行业,一次安全事故的代价可能是千万级的召回。

📌 真实案例:某国产血糖仪厂商未启用SE,产品上市半年即遭全盘复制,山寨品售价仅为原价1/3,最终被迫降价清仓。


工程落地:一套可复制的加密部署流程

理论讲完,咱们动手。下面这套流程已经在多个量产项目中验证过,适用于STM32、GD32、ESP32-C系列等主流平台。

🛠 开发阶段:打造加密固件镜像

假设你已经有一个 .bin 输出文件,接下来要做四件事:

第一步:计算哈希值
sha256sum app.bin > hash.txt
第二步:用私钥签名(推荐OpenSSL)
# 生成签名(注意是签哈希,不是整个文件)
openssl dgst -sha256 -sign private_key.pem -out app.sig hash.txt
第三步:构造固件头并合并

可以用Python脚本自动化:

import struct

def build_secure_image(fw_path, sig_path, output):
    with open(fw_path, 'rb') as f:
        fw_data = f.read()

    with open(sig_path, 'rb') as s:
        signature = s.read()  # 256 bytes for RSA-2048

    # 构造header
    header = struct.pack(
        '<II32s256sIB3s44s',
        0x5048434D,           # magic
        len(fw_data),          # length
        hashlib.sha256(fw_data).digest(),  # hash
        signature,             # signature
        1,                     # version
        12,                    # nonce len
        os.urandom(12),        # generate random nonce
        b'\x00' * 44
    )

    # 写入最终镜像
    with open(output, 'wb') as o:
        o.write(header)
        o.write(fw_data)

build_secure_image("app.bin", "app.sig", "secure_app.img")
第四步:AES加密(可选)

如果启用了加密启动模式,还需要用预置密钥加密 fw_data 部分:

// 使用硬件AES-GCM加密
aes_gcm_encrypt(key, nonce_from_header, fw_data, &cipher_text, &tag);

最终输出的就是一个“外人看不懂、改不了、仿不了”的固件包。


🚀 运行阶段:启动时的自我审查

设备上电后的流程如下:

int main(void) {
    // Step 1: 初始化硬件(时钟、RAM等)
    system_init();

    // Step 2: 从Flash读取固件头
    firmware_header_t *hdr = (firmware_header_t*)FLASH_BASE;

    // Step 3: 验证Magic Number
    if (hdr->magic != 0x5048434D) {
        enter_safe_mode();
    }

    // Step 4: 计算实际固件哈希
    uint8_t computed_hash[32];
    mbedtls_sha256_ret(flash_data_ptr, hdr->image_len, computed_hash, 0);

    if (memcmp(computed_hash, hdr->hash, 32)) {
        secure_wipe_and_lock();  // 清除敏感数据并锁定
    }

    // Step 5: 验证签名(使用内置公钥)
    if (!rsa_verify(hdr->hash, 32, hdr->signature, PUBLIC_KEY_N, PUBLIC_KEY_E)) {
        while(1);  // 永久阻塞
    }

    // Step 6: 解密固件到RAM(若启用加密)
    aes_gcm_decrypt(internal_key, hdr->nonce, cipher_text, plain_buf);

    // Step 7: 跳转执行
    jump_to_application((void*)SRAM_BASE);
}

整个过程不超过100ms(依赖硬件加速),用户体验几乎无感。


别忘了那些“看不见”的细节

再好的架构,也会毁于一个疏忽。以下是我在项目中踩过的坑,供你避雷👇

❌ 私钥放在开发电脑上?等于放门口等贼

曾经有个团队把RSA私钥存在Git仓库里,还设置了密码保护……结果密码写在README里。GitHub爬虫当天就抓到了,第二天就有第三方发布了“兼容固件”。

✅ 正确做法:
- 私钥必须在HSM中生成(如YubiHSM、Thales Luna)
- 签名操作通过API远程完成,私钥永不导出
- 每个产品线使用独立密钥对,避免一损俱损

❌ OTA不加TLS?相当于快递寄密码本

你以为本地签名就够了?错!如果下载通道是HTTP,中间人完全可以替换为恶意固件包。

✅ 必须做到:
- 下载链接使用HTTPS + 双向认证(客户端证书)
- 固件包额外携带服务器签名(可选)
- 启用断点续传校验,防止传输污染

❌ 忘记防降级攻击?老版本漏洞变后门

攻击者可能会诱导设备刷回旧版固件,利用已知漏洞获取权限。

✅ 解决方案:
- 固件头中加入 version 字段
- MCU维护一个“最小允许版本”寄存器(可通过安全命令升级)
- 低于该版本的固件一律拒绝

❌ 调试接口一直开着?等于给黑客留扇窗

JTAG/SWD在调试时很方便,但生产模式下必须关闭!

✅ 推荐策略:
- 出厂前执行 disable_debug_ports() 函数
- 启用ROP Level 2(读保护),禁用Flash读出
- 设置一次性解锁密码(如需返修)


如何测试你的防护是否靠谱?

最后一步:模拟攻击,看看防线能不能扛住。

自测清单 ✅

测试项 方法 预期结果
Flash读取 用SPI工具读取外部Flash 得到的是乱码(AES加密后)
固件篡改 修改任意一字节再烧录 启动失败,卡在BootROM
伪造签名 用自己的私钥重新签名 验证失败,因公钥不匹配
JTAG连接 接上调试器尝试halt 连接失败或返回错误状态
OTA劫持 中间人替换下载内容 HTTPS失败或本地签名不通过

💡 提示:可以用廉价FPGA搭建MITM测试环境,模拟网络攻击场景。


写在最后:安全不是功能,而是思维方式

回到开头的问题:嵌入式如何加密固件?

答案从来不是一个函数调用、一个配置选项,而是一整套贯穿产品生命周期的 安全思维

它意味着:

  • 在选型阶段就考虑RoT支持
  • 在CI/CD流水线中集成签名步骤
  • 在售后体系中建立密钥吊销机制
  • 在团队中设立“红队”定期攻防演练

未来几年,随着RISC-V生态崛起、AI模型下沉到边缘端,我们将面临更多新挑战:比如如何保护神经网络权重?如何验证动态加载的WASM模块?

但万变不离其宗: 信任必须从硬件生根,密码学是枝干,工程实践是果实

你现在写的每一行启动代码,都在决定三年后这款产品会不会出现在灰色产业链的报价单上。

所以,别再说“我们小公司不用搞那么复杂”。安全不分大小,只有真假之别。

🔐 从今天起,让你的固件穿上盔甲再出门。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

您可能感兴趣的与本文相关内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值