← ブログ一覧へ
FPGAZynq安全启动Secure BooteFuseAESRSABootgenFSBLPetaLinux

Zynq 实战 19|安全启动:FSBL 加密 + RSA 签名 + eFuse 编程

この記事は中国語で書かれ、Google 翻訳で自動翻訳されています。
中国語の原文を見る →

Zynq 实战 19|安全启动:FSBL 加密 + RSA 签名 + eFuse 编程

这是《Zynq FPGA 嵌入式系统设计实战》系列的第 19 篇。 板子:Pynq-Z2(XC7Z020)。工具链:Vivado / Vitis / PetaLinux 2023.2。 上一篇:《Zynq 实战 18|OpenAMP 多核:Linux + 裸机同时跑》


0. 这一篇要解决什么问题

前 18 篇我们把 Zynq 的功能做全了——PL 硬件加速、Linux 驱动、以太网通信、双核 AMP。现在问题来了:任何人拿到你的设备,插一张 SD 卡就能启动自己的固件,或者用 JTAG 把 DDR 里的内容 dump 出来

本篇要做的事:

  • 弄清楚 Zynq-7000 的 硬件信任根(HWROT) 是什么、能防什么
  • Bootgen 生成 AES-256 密钥和 RSA-2048 公私钥对
  • .bif 文件,对 FSBL / bitstream / U-Boot 做加密 + RSA 签名
  • PPK hash 烧进 eFuse,锁定唯一信任根(不可逆,本篇会反复提醒)
  • 验证完整 secure boot 链:BootROM → FSBL → U-Boot → kernel
  • 调试踩坑:bbram 暂存测试、hash 算错救砖、JTAG 锁定

本篇不覆盖:TrustZone 隔离、Secure OS(OP-TEE)、固件更新(FWU)——那些是另外的体系。

🚧 高优先级警告:eFuse 是一次性熔断的,烧错了不可恢复。本篇所有 eFuse 操作步骤都有”先用 bbram 测试”的前置步骤,请认真对待。


1. 威胁模型:能防什么、不能防什么

在投入精力之前,先想清楚保护目标:

威胁Zynq Secure Boot 能防备注
插 SD 卡启动第三方固件✅ 签名验证失败,BootROM 拒绝启动前提:eFuse 已烧 PPK hash
JTAG dump 固件(加密后)✅ 固件密文,无密钥无法解密需要同时 disable JTAG(eFuse)
运行时 JTAG 调试(生产)✅ 可通过 eFuse 禁用 JTAG开发阶段不要禁
固件篡改(中间人修改 flash)✅ RSA 签名检测到篡改,拒绝启动
物理芯片解封取密钥❌ 硬件攻击,eFuse 存储可被侧信道攻击超出 Zynq 防护范围
运行时软件漏洞(提权)❌ Secure Boot 只管启动链,不管运行时需要 SELinux / TrustZone
供应链攻击(出厂前植入)❌ 需要安全生产流程,非芯片功能

结论:Zynq Secure Boot 的价值在于 “离线设备防物理攻击 + 防固件篡改”,不是全栈安全方案。


2. Zynq-7000 安全机制架构

Zynq-7000 Secure Boot 链:BootROM → FSBL → U-Boot → Kernel BootROM (芯片内置,只读) 读 eFuse PPK hash RSA-2048 验 FSBL 签名 AES-256 解密 FSBL 通过 FSBL (Flash / SD 加载) AES 解密 bitstream RSA 验 U-Boot 签名 配置 PL bitstream 通过 U-Boot (第二阶段引导) 验 kernel FIT image secure_boot_verify_sig 加载 kernel + DTB 通过 Linux Kernel (PetaLinux 2023.2) IMA / dm-verity 运行时完整性检测 (可选,超出本篇) 验证失败 → BootROM 停机 eFuse OTP(一次性编程,不可逆) PPK Hash (256 bit) RSA 公钥 SHA-384 摘要 AES Key (256 bit) 或从 BBRAM 读取(可擦) Security Bits RSA_EN / AES_EN / JTAG_DIS USER bits 设备 ID / 版本标记 PUF KEK PUF 加密密钥 密钥存储方案对比(开发 vs 生产) BBRAM(Battery-Backed RAM) 可擦写,适合开发 / 测试 断电 / 电池耗尽后密钥丢失 eFuse AES Key(不可擦) 生产环境,一次性写入 写前三思,推荐 HSM 托管 PUF(Physical Unclonable Function) 由芯片物理特征派生,每片唯一 Zynq UltraScale+ 支持,7000 不支持
图 1. Zynq-7000 Secure Boot 链与 eFuse 信任根架构

3. 准备工作:生成密钥

3.1 工具链要求

本篇使用 Vitis 2023.2 自带的 bootgen 工具,无需额外安装:

# 确认 bootgen 可用
which bootgen
# 期望:/opt/Xilinx/Vitis/2023.2/bin/bootgen

bootgen -help | head -5
# Bootgen 2023.2

同时需要 OpenSSL(生成 RSA 密钥):

openssl version
# OpenSSL 3.0.x  (Ubuntu 22.04 自带)

3.2 生成 RSA-2048 密钥对(PPK / SPK)

Zynq Secure Boot 使用两级 RSA 密钥体系:

  • PPK(Primary Public Key):hash 烧进 eFuse,是信任根
  • SPK(Secondary Public Key):用 PPK 签名,实际签 image 用的是 SPK
mkdir -p ~/secure_boot_keys && cd ~/secure_boot_keys

# 生成 PPK(Primary Public Key)私钥,4096 位,实际上 Zynq 用 2048/4096 均可
# Zynq-7000 支持 RSA-2048(Vivado 2023.2 推荐 RSA-4096 用于 UltraScale+,Zynq-7000 用 2048)
openssl genrsa -out ppk.pem 2048
openssl rsa -in ppk.pem -pubout -out ppk_pub.pem

# 生成 SPK 私钥
openssl genrsa -out spk.pem 2048
openssl rsa -in spk.pem -pubout -out spk_pub.pem

# 生成 AES-256 密钥(用于加密)
bootgen -generate_keys aes -arch zynq -o ./aes_key.nky
# aes_key.nky 文件格式:
# Device xc7z020
# Key 0 <32字节十六进制>
# IV  0 <12字节十六进制>

ls -la
# ppk.pem  ppk_pub.pem  spk.pem  spk_pub.pem  aes_key.nky

🚧 避坑ppk.pem(私钥)绝对不能放进 git 仓库或上传到任何地方。一旦泄露,攻击者可以用它签名任意固件并绕过你的 secure boot。建议存入 HSM 或加密后放到离线介质。开发阶段用的测试密钥和生产密钥必须分开——开发机上的密钥不能是生产密钥。


4. .bif 文件:加密 + 签名配置

.bif(Boot Image Format)文件是告诉 bootgen 如何打包和保护每个组件的配置文件。

4.1 仅签名(不加密,适合开发验证)

/* sign_only.bif — 仅 RSA 签名,不加密,用于开发验证 */
the_ROM_image:
{
    /* BootROM header,包含 PPK 公钥 */
    [ppkfile] /path/to/ppk_pub.pem

    /* SPK(Secondary Public Key),由 PPK 签名 */
    [spkfile] /path/to/spk_pub.pem

    /* FSBL:RSA 签名 */
    [bootloader, authentication=rsa]
        /path/to/fsbl.elf

    /* PL bitstream:RSA 签名 */
    [destination_device=pl, authentication=rsa]
        /path/to/design_1_wrapper.bit

    /* U-Boot:RSA 签名 */
    [destination_cpu=a9-0, authentication=rsa]
        /path/to/u-boot.elf
}

生成命令:

bootgen -image sign_only.bif -arch zynq -o BOOT_signed.bin -w on

# 查看生成的 bin 里包含的组件
bootgen -image sign_only.bif -arch zynq -o BOOT_signed.bin -w on -log debug 2>&1 | \
    grep -E "(ppk|spk|rsa|aes|Adding)"

4.2 加密 + 签名(生产用)

/* encrypt_sign.bif — AES-256 加密 + RSA 签名,生产级配置 */
the_ROM_image:
{
    /* 密钥源:bbram(开发测试)或 efuse(生产) */
    [keysrc_encryption] bbram_red_key     /* 生产改为:efuse_red_key */

    /* PPK / SPK */
    [ppkfile] /path/to/ppk_pub.pem
    [spkfile] /path/to/spk_pub.pem

    /* AES 密钥文件 */
    [aeskeyfile] /path/to/aes_key.nky

    /* FSBL:AES 加密 + RSA 签名 */
    [bootloader,
     encryption=aes,
     authentication=rsa]
        /path/to/fsbl.elf

    /* PL bitstream:AES 加密 + RSA 签名 */
    [destination_device=pl,
     encryption=aes,
     authentication=rsa]
        /path/to/design_1_wrapper.bit

    /* U-Boot:AES 加密 + RSA 签名 */
    [destination_cpu=a9-0,
     encryption=aes,
     authentication=rsa]
        /path/to/u-boot.elf
}
# 生成加密 + 签名的 BOOT.bin
bootgen -image encrypt_sign.bif -arch zynq -o BOOT_secure.bin -w on

# 文件大小参考(含签名和加密开销)
ls -lh BOOT_secure.bin
# -rw-r--r-- 1 user user 7.2M Apr 28 08:00 BOOT_secure.bin
# (相比未加密 BOOT.bin 增加约 15%,主要是 RSA 签名数据)

🚧 避坑bootgen 生成 .bif 时会把 PPK 公钥嵌入 BOOT.bin 头部——这是设计如此,BootROM 需要读取它来验签。但这同时意味着 BOOT.bin 里的 PPK 公钥是明文可读的,任何人都能提取出来(用 binutils)。不要把安全假设建立在”攻击者不知道公钥”上——RSA 的安全性来自私钥保密,公钥可以公开。

4.3 关键的 bootgen 输出验证

# 检查 PPK hash(这个值将烧进 eFuse)
bootgen -image encrypt_sign.bif -arch zynq -efusefile ppk_hash.txt -w on
cat ppk_hash.txt
# 输出格式:
# ppk0_sha3_384 = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# (96 个十六进制字符 = 384 bit)

把这个 hash 值记录下来并备份三份——后面烧 eFuse 用的就是它。


5. 第一阶段测试:用 BBRAM 验证流程

eFuse 烧了不能擦,所以必须先用 BBRAM(Battery-Backed RAM) 做全量测试,验证整个 secure boot 链正常后,再考虑 eFuse。

BBRAM 是一块由纽扣电池供电的 256-bit SRAM,可以随时改写,断电/电池耗尽后密钥消失。

5.1 通过 JTAG 把 AES 密钥写入 BBRAM

# program_bbram.tcl — 用 Vitis XSCT 或 vivado -mode tcl 执行
# 把 aes_key.nky 里的密钥写进 BBRAM

# 连接目标(确保 JTAG 已连接)
connect
target 1

# 读取 .nky 里的 AES 密钥(32字节十六进制字符串)
set aes_key "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
# ↑ 替换为 aes_key.nky 里实际的 Key 0 值

# 写入 BBRAM
jtag targets
device program {BOOT_secure.bin}

# 使用 program_flash 或专用 BBRAM 编程命令
# 在 Vitis 2023.2 里用 Xilinx BBRAM 编程工具:
# Xilinx → Program Device → Program BBRAM

更简洁的方式是用 Vivado TCL:

# 在 Vivado TCL Console 里执行
open_hw_manager
connect_hw_server
open_hw_target

# 写 BBRAM(需要 Vivado 2023.2+)
set_property PROGRAM.AES_KEY_FILE {/path/to/aes_key.nky} [get_hw_devices xc7z020_1]
program_hw_bbram [get_hw_devices xc7z020_1]

5.2 SD 卡启动测试

BOOT_secure.bin 拷到 SD 卡根目录,板子 Boot Mode 拨码开关设为 SD(01):

# 开机串口输出(成功情况)
Xilinx First Stage Boot Loader
Release 2023.2   Dec  7 2023
Bootmode: SD_MODE
...
Authentication Enabled
Encryption Enabled
Authentication of Partition 0 succeeded
Decryption of Partition 0 succeeded
Loading bitstream...
Authentication of Partition 1 succeeded
Decryption of Partition 1 succeeded
...
U-Boot 2023.01 (Apr 28 2026)
Zynq> 

如果出现 Authentication Failed

# 常见原因 1:.bif 里的密钥路径写错,公钥不一致
# 验证:重新运行 bootgen,检查输出里 ppk hash 是否和 bbram 里一致

# 常见原因 2:keysrc_encryption 设置和实际密钥存储不一致
# 验证:bbram_red_key 对应 BBRAM,efuse_red_key 对应 eFuse AES key

# 常见原因 3:FSBL 没有开启 secure boot 支持
# 解决:在 Vitis BSP 配置里确认 FSBL 的 XFSBL_RSA 和 XFSBL_AES 宏已定义

6. 烧写 eFuse:把 PPK Hash 锁定为硬件信任根

再次确认:eFuse 烧写不可逆,BBRAM 测试通过后再继续。

6.1 生成 program_efuse.tcl

# program_efuse.tcl — 通过 JTAG 烧 PPK hash 到 eFuse
# 在 Vivado 2023.2 TCL Console 里执行
# 前提:JTAG 已连接,板子上电

open_hw_manager
connect_hw_server -host localhost -port 3121
open_hw_target

# 选择设备
set device [lindex [get_hw_devices] 0]
current_hw_device $device
refresh_hw_device $device

# ── 检查当前 eFuse 状态(只读操作,安全) ──
set fuse_obj [get_hw_cfgmem -of_objects $device]
# 读取 PPK0 hash fuse(如果已烧,值非 0)
read_hw_cfgmem -force $fuse_obj

# ── 写入 PPK hash(注意:这步不可逆) ──
# ppk_hash 来自 bootgen -efusefile 输出
set ppk_hash "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# ↑ 替换为实际 96 字符 SHA-384 hash

# 创建临时 .jtag 文件
set efuse_data [open /tmp/efuse_prog.jtag w]
puts $efuse_data "ppk0_sha3_384 = ${ppk_hash}"
puts $efuse_data "rsa_enable = 1"
# 注意:rsa_enable=1 和 ppk_hash 同时烧,BootROM 才会强制验签
# 如果只烧 ppk_hash 不烧 rsa_enable,secure boot 不会启用!
close $efuse_data

# 执行 eFuse 编程
program_hw_cfgmem -force \
    -file /tmp/efuse_prog.jtag \
    [get_hw_cfgmem -of_objects [get_hw_devices xc7z020_1]]

puts "eFuse 编程完成。请重启验证。"

🚧 避坑rsa_enable = 1ppk_hash 必须一起烧,缺一不可。如果只烧了 ppk_hash 但没烧 rsa_enable,BootROM 会忽略 PPK hash 继续启动,secure boot 等于没开。反过来,如果只烧 rsa_enable = 1 但没提供有效 ppk_hash,BootROM 会直接拒绝所有启动——设备彻底砖了。两个操作要在同一次 program_hw_cfgmem 里完成。

6.2 烧后验证

# 拔掉 JTAG,只用 SD 卡启动
# 期望:secure boot 链正常,和 BBRAM 测试结果一致

# 用 Vivado 读回 eFuse 确认(只读)
read_hw_cfgmem -force [get_hw_cfgmem -of_objects [get_hw_devices xc7z020_1]]
# 检查 ppk0_sha3_384 字段是否等于预期 hash

7. FSBL 配置:启用安全特性

PetaLinux 2023.2 生成的 FSBL 默认不开 secure boot,需要手动加宏。

在 Vitis BSP 设置里,找到 FSBL 工程的 xfsbl_config.h

/* xfsbl_config.h — FSBL 安全特性开关 */

/* 启用 RSA 身份验证 */
#define XFSBL_RSA                  1

/* 启用 AES 加密解密 */
#define XFSBL_AES                  1

/* 启用 SHA3-384(用于 PPK hash 计算) */
#define XFSBL_SHA3                 1

/* 启用 PPK/SPK 选择(默认 PPK0,多密钥轮换时使用) */
#define XFSBL_RSA_KEY_ROLLOVER     0    /* 1 = 支持 PPK0/PPK1 轮换 */

/* AES 密钥来源(必须和 .bif 里 keysrc_encryption 一致) */
/* XFSBL_AES_KEY_SRC_DEV_EFUSE 或 XFSBL_AES_KEY_SRC_DEV_BBRAM */
#define XFSBL_AES_KEY_SRC  XFSBL_AES_KEY_SRC_DEV_BBRAM  /* 开发用 bbram */

重新编译 FSBL,更新 .bif 里的 fsbl.elf 路径,重新 bootgen


8. 调试踩坑手册

8.1 PPK hash 算错了会怎样?

如果你把错误的 PPK hash 烧进了 eFuse(比如 .pem 文件路径写错,或者手抄时漏了字符):

  • BootROM 会用 eFuse 里的 hash 去验证 BOOT.bin 里的 PPK 公钥
  • 对比失败,BootROM 停止启动
  • JTAG 此时还没有被禁用(只烧了 ppk_hash 和 rsa_enable,未烧 jtag_disable)
  • 救砖方法:用 JTAG 连接,烧写第二个 PPK(PPK1)并启用 ppk1_en eFuse
# 救砖:启用 PPK1
# 前提:jtag 未被锁(jtag_disable eFuse 未烧)
# 1. 重新生成一对 PPK/SPK 密钥对(ppk1.pem / spk1.pem)
# 2. 用新密钥生成 BOOT_v2.bin,ppkfile 改为 ppk1_pub.pem
# 3. 烧 PPK1 hash 和 ppk1_sha_en
set efuse_data [open /tmp/efuse_ppk1.jtag w]
puts $efuse_data "ppk1_sha3_384 = <ppk1的hash>"
puts $efuse_data "ppk1_sha_en = 1"
close $efuse_data
program_hw_cfgmem -force -file /tmp/efuse_ppk1.jtag ...

8.2 JTAG 锁定之后

如果同时烧了 jtag_disable = 1,JTAG 被永久禁用,无法通过 JTAG 调试或编程。此时:

  • BootROM 层面的调试通道关闭
  • 唯一可以”调试”的方式是串口 UART 输出的启动日志
  • 如果 secure boot 失败 + JTAG 禁用,设备是真正意义上的砖

结论:生产流程里,jtag_disable 应该是所有 eFuse 操作的最后一步,在充分测试 secure boot 通过之后才烧。

8.3 加密后 U-Boot 提示 “Authentication Failed”

# 串口输出
FSBL: Loading U-Boot...
Authentication of Partition 2 FAILED
Partition authentication failed.
FSBL: Error

排查步骤:

# 1. 确认 FSBL 里 XFSBL_RSA=1 且 XFSBL_AES=1
grep -r "XFSBL_RSA\|XFSBL_AES" <vitis-workspace>/fsbl/src/xfsbl_config.h

# 2. 确认 .bif 里 u-boot.elf 使用的 aeskeyfile 和当前 BBRAM/eFuse 里的密钥一致
# 重新 bootgen -efusefile 生成 hash,对比 eFuse 实际值

# 3. 确认 U-Boot elf 没有被重新编译(重编后内容变了,旧签名失效)
sha256sum u-boot.elf  # 和生成 BOOT.bin 时用的一致?

# 4. 如果 U-Boot 被 strip 过或 objcopy 过,签名覆盖的范围可能不对
# 建议直接签 Vitis/PetaLinux 输出的原始 elf,不要 strip

🚧 避坑:每次重新编译任何组件(FSBL / U-Boot / kernel)之后,必须重新运行 bootgen 生成新的 BOOT.bin。签名是对 elf 二进制内容的数字签名,只要内容改了,旧的签名就失效了。这个看起来显而易见,但在 CI/CD 流水线里很容易漏掉”重新签名”的步骤。


9. 产品化建议

9.1 密钥管理

阶段建议
开发使用独立的测试密钥对(绝不使用生产密钥),BBRAM 存 AES key
预生产用 HSM(Hardware Security Module,如 AWS CloudHSM / Thales)托管 PPK 私钥
量产生产线上通过 JTAG 编程 eFuse(自动化,每块板子独立密钥更安全)
密钥轮换预留 PPK1 槽位(Zynq-7000 支持 PPK0 + PPK1),PPK0 泄露后切 PPK1

9.2 生产线烧录流程

1. 制造商生产 PCB(此时 eFuse 全 0,JTAG 打开)
2. 功能测试(JTAG 调试,无 secure boot)
3. 烧录生产固件(BOOT_prod.bin,已加密签名)
4. 通过 JTAG + TCL 脚本烧 eFuse:
   a. 烧 PPK hash
   b. 烧 AES key(或 BBRAM key)
   c. 烧 rsa_enable = 1
   d. 验证 secure boot 正常启动
   e. (可选)烧 jtag_disable = 1
5. 出厂

9.3 关键 eFuse 字段速查表

eFuse 字段作用默认值不可逆
ppk0_sha3_384PPK0 公钥 hash(384 bit)全 0(未启用)
ppk1_sha3_384PPK1 公钥 hash(备用)全 0
rsa_enable强制 RSA 签名验证0(禁用)
aes_efuse_keyAES-256 加密密钥全 0
jtag_disable禁用 JTAG0(启用)
user_fuse[0:7]用户自定义 8 字节全 0

10. 本篇 Checklist

  • 用 bbram 完整测试加密 + 签名启动链,确认串口输出 “Authentication … succeeded”
  • bootgen -efusefile 生成的 PPK hash 备份三份(不同物理位置)
  • rsa_enable = 1ppk_hash 在同一次 program_hw_cfgmem 里烧入
  • jtag_disable 是最后一步,在所有测试通过之后才考虑
  • 生产密钥和开发测试密钥严格分开,不共用
  • CI/CD 流水线里每次重编后都重新 bootgen 生成签名镜像

11. 下一篇预告

下一篇是系列 第 20 篇(收官篇)——《Zynq 实战 20|综合实战 Capstone:音频采集 → 实时 FFT → Web 频谱仪》

  • 整合前 19 篇:PL IP + AXI DMA + Linux 驱动 + WebSocket
  • 完整系统拓扑:ADAU1761 codec → I2S RX → FFT IP → DMA → 用户态 → 浏览器频谱图
  • 端到端延迟 < 30 ms 实测
  • 系列回顾:每一篇解决了什么,整个系列读完你得到了什么能力

参考资料

文档号名称用途
UG585Zynq-7000 SoC TRM第 6 章:安全机制,eFuse 存储映射,BBRAM 接口
UG1283Bootgen User Guide 2023.2.bif 文件完整语法,authentication / encryption 属性
XAPP1175Zynq-7000 AP SoC AES DecryptionAES key 烧写流程,keysrc_encryption 选项详解
UG1099Zynq-7000 SoC Device ConfigurationeFuse 编程 TCL 脚本参考,program_efuse 用法
PG150AXI EthernetLite与安全启动集成时的注意事项(网络更新场景)
Xilinx WikiSecure Boot Zynq实战配置示例,常见错误码

这是《Zynq FPGA 嵌入式系统设计实战》系列第 19 篇。 如果你在 eFuse 烧录、PPK hash 计算或 FSBL 认证失败上踩了坑,欢迎留言——这类问题通常非常具体,评论区可能帮到后来人。