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举例,烧录一个加密固件通常分三步:
- 连接目标芯片:确认SWD接口正常,读取芯片ID
- 擦除Flash:注意要整片擦除,别留旧数据
- 写入固件:指定起始地址,写入.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写入固件。最后r和g是复位并运行。
4.3 烧录后的Hash校验
固件烧进去了,怎么知道它跟原始文件一模一样?最直接的办法就是算Hash。
我一般会在烧录完成后,从芯片里把固件读出来,然后跟原始文件比对SHA256值。这样能确保Flash里的每一个字节都是对的。
具体做法:
- 用J-Link读取指定地址范围的Flash内容,保存为dump.bin
- 在PC端计算原始固件的SHA256
- 再计算dump.bin的SHA256
- 两个值比对,一致则通过
命令行操作示例:
# 读取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是不是有坏块。
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; // 签名无效,拒绝启动
}
}
4.5 避坑指南:烧录校验中的常见问题
做这行久了,踩过的坑真不少。我挑几个典型的说说:
- SWD线太长导致烧录失败:我曾经用了一根1米长的杜邦线连SWD,结果烧录到一半就报错。后来换成10厘米的排线,问题解决。SWD信号频率高,线长了干扰大。
- 芯片读保护等级没处理好:有些芯片默认读保护等级是1,烧录前必须降级到0。但降级操作会擦除整片Flash,所以要先备份数据。
- 签名校验放在错误的位置:有人把签名校验放在应用程序里,而不是Bootloader里。这等于把钥匙挂在门上——攻击者可以直接绕过应用程序,从Bootloader跳转到恶意代码。
- Hash算法选得太弱:MD5就别用了,碰撞攻击太容易。至少用SHA256,有条件上SHA3更好。
嗯,这里再强调一点:烧录校验不是一次性工作。产品量产时,每片芯片都要做校验。我建议在产线上加一个自动化测试工位,烧录完自动读Hash、验签名,不合格的直接打标记返修。
好了,这一节的内容就这些。下一节我们聊聊如何给加密狗加上防篡改机制,让物理攻击也无从下手。