固件升级包分两层。第一层是共用包:固件只加密一次,一份包文件, 所有设备共用。第二层是包密钥包:包密钥只包 32 字节,按设备各包一遍, 设备用自己的私钥解出包密钥,再解固件、验签。两层合起来,一份固件 只出一份包文件,不存 1000 个包文件;推送时也不现打整包,只现包 32 字节的密钥。
包 = 312 字节包头 + 加密包体。包体是固件明文经 AES-256-GCM 加密 的结果,密钥是包密钥,随机数是包头里的包体随机数,附加认证数据 为空;密文后附 16 字节认证标签。所以包总长 = 312 + 明文长 + 16。
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 4 | 魔数 GWPK(ASCII) |
| 4 | 4 | 格式,u32,恒 3 |
| 8 | 100 | 型号,ASCII 零填充 |
| 108 | 100 | 固件名,ASCII 零填充 |
| 208 | 2 | 版本 major,u16 |
| 210 | 2 | 版本 minor,u16 |
| 212 | 2 | 版本 patch,u16 |
| 214 | 8 | 构建日期,ASCII YYYYMMDD |
| 222 | 6 | 构建时间,ASCII HHMMSS(UTC,秒级) |
| 228 | 4 | 明文长度,u32 |
| 232 | 4 | 标志,u32,恒 3 |
| 236 | 12 | 包体随机数(AES-256-GCM 随机数) |
| 248 | 64 | 签名,Ed25519 |
字符串规则(型号、固件名):100 字节 ASCII 零填充,首个零字节后
必须全零,100 字节填满时须全 ASCII;空字段拒绝。型号只认
docs/models.md 注册表,当前唯一 stm32h743vi;固件名唯一
gateway。型号、固件名、版本、日期、时间只从签名包头或签名版本块
读,绝不从文件名解析;文件名只当人的标签。
标志:bit0 = 已加密,bit1 = 已签名,恒 3。其他位出现、或任一位 缺失,设备拒绝。
包文件命名:{固件名}-{完整型号}-release-v{版本}-{日期}-{时间}.pkg,
落在输入 bin 旁边;块型号 stm32h743vi 在文件名里拼完整订货型号
stm32h743vit6。解包默认输出为块生成名加 -out.bin。
Ed25519,签名范围 = 包头 [0..248] + 固件明文(顺序拼接,签名本身 不参与)。型号、固件名、版本、日期、时间都在签名范围内,所以身份 字段有签名认证;改其中任一字节,验签失败。
签名密钥是云端签名密钥(全局一对,长久不动);每版签名值不同, 因为包内容不同。签名密钥轮换走备用机制(见第 4 节),不随版本变。
包密钥包与共用包分开传输,80 字节,无魔数头,长度本身就是格式:
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 32 | 临时 X25519 公钥(每包 fresh,永不复用) |
| 32 | 48 | 包装后包密钥 = 32 密文 + 16 标签 |
包装方法:临时 X25519 对每包 fresh;共享秘密 = 临时私钥 × 设备静态 公钥;包装密钥 = SHA-256(共享秘密);AES-256-GCM 加密包密钥,随机数 固定 12 字节零、附加认证数据为空。零随机数安全理由:包装密钥永不 重复(临时对每包 fresh,共享秘密永不重复)。
同一包密钥给同一设备包两次,结果不同(临时对不同),但两个包密钥包 都能解出同一包密钥。包密钥包损坏(标签错、长度错)在解包时安全拒绝, 不触碰包体。
任一步失败即拒,绝不写备用区、绝不切换启动:
不要混淆两个版本号:固件 bin 偏移 0x1000 处的版本块是格式 2
(228 字节,FWVR 魔数,型号、固件名、版本、日期、时间,秒级),
见 docs/firmware-version-block.md;包格式是 v3(312 字节包头)。
包头身份字段来自版本块(版本唯一来源是固件的 Cargo.toml),
但包格式版本与版本块格式版本是两回事。更新规则:更新才装、
相等跳过、更旧拒绝。
79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664。493e82fc74464a59268817623d2053c5eb8e2cc4a988b4fee179ec6b010d531d。(i * 7 + 3) & 0xff,带格式 2 版本块
(型号 stm32h743vi、固件名 gateway、版本 1.2.3、日期 20251031、
时间 171000)。测试覆盖:共用包往返逐字节一致;包密钥包往返(两次包装结果不同、 都能解出同一密钥);错设备密钥解包密钥包失败;包密钥包改一字节 (临时公钥区或包装区)失败;包密钥包长度错失败;改包体失败; 改签名区或身份字段失败;坏魔数、未知格式(2、1)、未知标志拒绝; 错验证公钥失败;包名与解包默认名按块生成。
pack --bin <bin> --sign-key <种子>,出共用包,不再要设备
加密密钥;传 --encrypt-key 显式拒绝。包密钥打印一次(只示一次,
类似生成密钥与调用凭证 secret 的既有模式),服务端路径绝不打印。wrap-key --package-key <hex> --encrypt-key <hex> --out <文件>,
出 80 字节包密钥包;--out 在此为必填(包密钥包没有可派生的生成名)。verify / unpack --pkg <pkg> --verify-key <hex>
--decrypt-key <hex> --key-package <文件>,缺 --key-package 报错。info --pkg 无密钥时只看包头;有 --verify-key +
--decrypt-key + --key-package 三者齐全时才解密看内嵌块。gen-keys、cmp、derive-pub 保持:derive-pub --kind ed25519
输出 verify key,--kind x25519 输出 encrypt key;cmp 一致打印
PASS: identical 退 0,不同报首个不同字节退 1;pack / unpack
不接受 --out,unpack 不接受 --expect(传了报显式拒绝,
提示改用 cmp)。