4、固件烧录与校验:使用J-Link/ST-Link烧录固件,校验Hash与签名

好,咱们接着聊。上一节我们把加密固件准备好了,那怎么把它塞进加密狗里呢?这节我就讲讲烧录和校验的实操。

烧录这件事,说白了就是把编译好的二进制文件,通过调试器写进芯片的Flash里。但这里有个关键——你烧进去的东西对不对?有没有被篡改?这就得靠Hash和签名来验证了。

4.1 烧录工具的选择:J-Link vs ST-Link

我个人习惯,做加密狗项目时首选J-Link。为什么?因为它稳定,支持的芯片多,而且速度确实快。ST-Link呢,便宜,调试STM32系列够用,但遇到一些冷门芯片就有点力不从心了。

特性 J-Link ST-Link
支持芯片范围 ARM Cortex全系列 + 其他 主要支持STM32系列
烧录速度 快(最高可达50 MHz SWO) 中等(通常4 MHz)
命令行工具 JLinkExe / JFlash STM32_Programmer_CLI
价格 较贵(教育版便宜些) 便宜(甚至板载免费)
加密狗项目推荐 ★★★★★ ★★★☆☆

嗯,这里要注意:如果你用的是国产芯片,比如GD32、AT32,J-Link的兼容性反而更好。ST-Link有时候会报一些莫名其妙的IDCODE错误。

4.2 烧录流程:从命令行到一键脚本

我不太喜欢用IDE自带的烧录功能,太慢了,而且不好集成到自动化流程里。我建议直接用命令行工具。

拿J-Link举例,烧录一个加密固件通常分三步:

  1. 连接目标芯片:确认SWD接口正常,读取芯片ID
  2. 擦除Flash:注意要整片擦除,别留旧数据
  3. 写入固件:指定起始地址,写入.hex或.bin文件

下面是我常用的烧录脚本,你直接拿去改改就能用:

#!/bin/bash
# J-Link 烧录加密固件脚本
# 用法: ./flash.sh firmware.bin

TARGET_DEVICE=STM32F407VG
INTERFACE=SWD
SPEED=4000
ADDR=0x08000000

JLinkExe -device $TARGET_DEVICE -if $INTERFACE -speed $SPEED -autoconnect 1 \
    -CommanderScript << EOF
    r
    h
    erase
    loadfile $1 $ADDR
    r
    g
    exit
EOF

echo "烧录完成!"

这段脚本里,r是复位,h是暂停,erase擦除整片Flash,loadfile写入固件。最后rg是复位并运行。

注意: 如果你烧录的是加密固件,记得先关闭读保护(比如STM32的RDP级别)。否则烧录完一上锁,你自己都读不出来,那就尴尬了。我曾经有一次忘了关RDP,结果烧完发现芯片锁死了,只能重新擦除再来一遍。

4.3 烧录后的Hash校验

固件烧进去了,怎么知道它跟原始文件一模一样?最直接的办法就是算Hash。

我一般会在烧录完成后,从芯片里把固件读出来,然后跟原始文件比对SHA256值。这样能确保Flash里的每一个字节都是对的。

具体做法:

  1. 用J-Link读取指定地址范围的Flash内容,保存为dump.bin
  2. 在PC端计算原始固件的SHA256
  3. 再计算dump.bin的SHA256
  4. 两个值比对,一致则通过

命令行操作示例:

# 读取Flash内容(从0x08000000开始,读取256KB)
JLinkExe -device STM32F407VG -if SWD -speed 4000 -autoconnect 1 \
    -CommanderScript << EOF
    r
    h
    savebin dump.bin 0x08000000 0x40000
    exit
EOF

# 计算原始固件的SHA256
sha256sum firmware.bin

# 计算读出内容的SHA256
sha256sum dump.bin

如果两个SHA256值完全一致,恭喜你,烧录成功。如果不一致,嗯,先检查一下SWD接线是不是有干扰,或者芯片的Flash是不是有坏块。

小技巧: 我习惯在烧录脚本里自动加上校验步骤。脚本最后比对Hash,不一致就直接报错退出,不给后续流程留隐患。你想想看,如果固件烧坏了,产品发出去才出问题,那返修成本可就高了。

4.4 签名校验:确保固件来源可信

Hash校验只能保证数据没传错,但保证不了这个固件是不是你公司签发的。如果有人伪造了一个固件,Hash也能算对,但内容已经被篡改了。这时候就需要签名校验。

签名校验的原理很简单:

  • 固件发布前,用私钥对固件的Hash进行签名
  • 烧录时或启动时,用公钥验证签名
  • 签名通过,说明固件确实是官方发布的,没被改过

我建议在加密狗的Bootloader里集成签名校验逻辑。每次上电启动时,先校验应用程序的签名,通过才跳转执行。这样就算有人想通过调试接口直接写Flash,也会被Bootloader拦住。

签名校验的伪代码大概长这样:

// Bootloader 签名校验流程
bool verify_firmware_signature(void) {
    // 1. 计算固件区域的SHA256
    uint8_t hash[32];
    sha256_calculate(APP_START_ADDR, APP_SIZE, hash);
    
    // 2. 从固定位置读取签名(比如Flash末尾)
    uint8_t signature[256];
    read_flash(SIGNATURE_ADDR, signature, 256);
    
    // 3. 用公钥验证签名
    if (rsa_verify(hash, signature, PUBLIC_KEY)) {
        return true;  // 签名有效
    } else {
        return false; // 签名无效,拒绝启动
    }
}
核心要点: 公钥要固化在Bootloader里,不能存储在可被用户修改的区域。私钥要保存在安全的离线环境中,每次签名操作都要有严格的权限控制。我见过有公司把私钥放在共享文件夹里,结果被内部员工泄露了,那整个产品线都得报废重来。

4.5 避坑指南:烧录校验中的常见问题

做这行久了,踩过的坑真不少。我挑几个典型的说说:

  • SWD线太长导致烧录失败:我曾经用了一根1米长的杜邦线连SWD,结果烧录到一半就报错。后来换成10厘米的排线,问题解决。SWD信号频率高,线长了干扰大。
  • 芯片读保护等级没处理好:有些芯片默认读保护等级是1,烧录前必须降级到0。但降级操作会擦除整片Flash,所以要先备份数据。
  • 签名校验放在错误的位置:有人把签名校验放在应用程序里,而不是Bootloader里。这等于把钥匙挂在门上——攻击者可以直接绕过应用程序,从Bootloader跳转到恶意代码。
  • Hash算法选得太弱:MD5就别用了,碰撞攻击太容易。至少用SHA256,有条件上SHA3更好。

嗯,这里再强调一点:烧录校验不是一次性工作。产品量产时,每片芯片都要做校验。我建议在产线上加一个自动化测试工位,烧录完自动读Hash、验签名,不合格的直接打标记返修。

好了,这一节的内容就这些。下一节我们聊聊如何给加密狗加上防篡改机制,让物理攻击也无从下手。