1. 创建最小测试镜像
首先创建一个全零的最小测试镜像文件:
dd if=/dev/zero of=test.img bs=28 count=1
说明:原始镜像最小需要 28 字节,因为
avbtool.py需要读取前 28 字节来判断是否为 sparse image。
2. 计算分区大小
MAX_VBMETA_SIZE = 64 * 1024
MAX_FOOTER_SIZE = 4096
PARTITION_SIZE = 64 * 1024 + 4096 + 28 = 69660
首次尝试(失败)
python avbtool.py add_hash_footer \
--partition_size 69660 \
--partition_name test \
--image test.img \
--key rsa2048.pem \
--algorithm SHA256_RSA2048 \
--rollback_index 1612483200
报错信息:
avbtool.py: Partition size of 69660 is not a multiple of the image block size 4096.
修正后(4K 对齐)
# 4K 对齐后分区大小 = 73728 (4096 × 18)
python avbtool.py add_hash_footer \
--partition_size 73728 \
--partition_name test \
--image test.img \
--key rsa2048.pem \
--algorithm SHA256_RSA2048 \
--rollback_index 1612483200
3. 镜像整体结构
add_hash_footer 处理后的镜像布局:
┌────────────────────────────┐
│ raw data (28 byte) │
│ + 0x00 padding 4096 byte │ ← 补齐到 block_size 的整数倍
├────────────────────────────┤
│ vbmeta_blob │ ← VBMeta 元数据块
├────────────────────────────┤
│ footer_blob_with_padding │ ← 尾部元数据(64 字节 + 填充)
└────────────────────────────┘
4. vbmeta_blob 整体布局
offset 0 ┌──────────────────────────────┐
│ Header (256 byte) │ ← AvbVBMetaHeader
offset 256 ├──────────────────────────────┤
│ Authentication Block │ ← hash + signature (64 字节对齐)
├──────────────────────────────┤
│ Auxiliary Block │ ← descriptors + pub_key (64 字节对齐)
└──────────────────────────────┘
4.1 Header(头部,固定 256 字节)
对应 AvbVBMetaHeader,使用大端序 。
| 字段 | 类型 | 字节 | 说明 |
|---|---|---|---|
magic | 4s | 4 | "AVB0" (0x41564230) |
required_libavb_version_major | L | 4 | 主版本号 |
required_libavb_version_minor | L | 4 | 次版本号 |
authentication_data_block_size | Q | 8 | 认证块大小 |
auxiliary_data_block_size | Q | 8 | 辅助块大小 |
algorithm_type | L | 4 | 算法类型 |
hash_offset / hash_size | 2Q | 16 | Hash 偏移和大小 |
signature_offset / signature_size | 2Q | 16 | 签名偏移和大小 |
public_key_offset / public_key_size | 2Q | 16 | 公钥偏移和大小 |
public_key_metadata_offset / public_key_metadata_size | 2Q | 16 | 公钥元数据偏移和大小 |
descriptors_offset / descriptors_size | 2Q | 16 | 描述符偏移和大小 |
rollback_index | Q | 8 | 回滚索引 |
flags | L | 4 | 标志位 |
reserved0 | 4x | 4 | 保留 |
release_string | 47sx | 48 | 版本字符串 |
reserved | 80x | 80 | 保留 |
| 合计 | 256 |
Header 二进制分析
$ xxd test.img
# 从偏移 4096 开始的 Header 数据
0001000: 4156 4230 0000 0001 0000 0000 0000 0000 AVB0............
0001010: 0000 0140 0000 0000 0000 0300 0000 0001 ...@............
0001020: 0000 0000 0000 0000 0000 0000 0000 0020 ...............
0001030: 0000 0000 0000 0020 0000 0000 0000 0100 ....... ........
0001040: 0000 0000 0000 00c8 0000 0000 0000 0208 ................
0001050: 0000 0000 0000 02d0 0000 0000 0000 0000 ................
0001060: 0000 0000 0000 0000 0000 0000 0000 00c8 ................
0001070: 0000 0000 601c 8a80 0000 0000 0000 0000 ....`...........
0001080: 6176 6274 6f6f 6c20 312e 312e 3000 0000 avbtool 1.1.0...
字段解析:
| 字段 | 十六进制值 | 解析结果 |
|---|---|---|
magic | 41 56 42 30 | "AVB0" |
required_libavb_version_major | 00 00 00 01 | 1 |
required_libavb_version_minor | 00 00 00 00 | 0 |
authentication_data_block_size | 00 00 00 00 00 00 01 40 | 320 (0x140) |
auxiliary_data_block_size | 00 00 00 00 00 00 03 00 | 768 (0x300) |
algorithm_type | 00 00 00 01 | 1 (SHA256_RSA2048) |
hash_offset | 00 00 00 00 00 00 00 00 | 0 |
hash_size | 00 00 00 00 00 00 00 20 | 32 |
signature_offset | 00 00 00 00 00 00 00 20 | 32 |
signature_size | 00 00 00 00 00 00 01 00 | 256 |
public_key_offset | 00 00 00 00 00 00 00 C8 | 200 (0xC8) |
public_key_size | 00 00 00 00 00 00 02 08 | 520 (0x208) |
public_key_metadata_offset | 00 00 00 00 00 00 02 D0 | 720 (0x2D0) |
public_key_metadata_size | 00 00 00 00 00 00 00 00 | 0 |
descriptors_offset | 00 00 00 00 00 00 00 00 | 0 |
descriptors_size | 00 00 00 00 00 00 00 C8 | 200 (0xC8) |
rollback_index | 00 00 00 00 60 1C 8A 80 | 1612483200 |
flags | 00 00 00 00 | 0 |
release_string | 61 76 62 74 6F 6F 6C 20 31 2E 31 2E 30 00... | "avbtool 1.1.0" |
注意:offset/size 字段是相对于各自所在 block 起点的偏移,而非相对于整个 blob。
4.2 Authentication Block(认证块)
布局:
┌──────────────────────┐
│ Hash (32 byte) │ ← SHA256 哈希值
├──────────────────────┤
│ Signature (256 byte)│ ← RSA 签名
├──────────────────────┤
│ Padding (0x00) │ ← 补齐到 64 字节倍数
└──────────────────────┘
二进制数据:
# Hash (32 字节) - 偏移 4352 (0x1100)
0001100: c309 88de 75e2 2ffb c20a 7630 bf9b f096 ....u./...v0....
0001110: 55f2 ef4b 9cc1 f9b3 2b02 f016 6f4c 11a2 U..K....+...oL..
# Signature (256 字节) - 偏移 4384 (0x1120)
0001120: ca12 5a82 ef95 16c6 58f9 8590 6306 dd29 ..Z.....X...c..)
# ... (中间省略)
0001210: 8054 7ed5 0e14 5173 3791 cbf9 f8b3 0ded .T~...Qs7.......
# Padding - 偏移 4640 (0x1220)
0001220: 0000 0000 0000 0000 0000 0000 0000 0000 ................
关键计算:
hash_offset = 0,signature_offset = hash_size(紧跟 hash 之后)authentication_data_block_size = round_to_multiple(hash_size + signature_size, 64) = 320
Hash 计算:
hash = SHA256(Header + Auxiliary) # 不含 Authentication 自身
手动验证Hash:
# 提取 Header 和 Auxiliary 数据
$ dd if=test.img of=header_data_blob.bin skip=4096 ibs=1 count=256
$ dd if=test.img of=aux_data_blob.bin skip=4672 ibs=1 count=768 # 4096+256+320=4672
$ cat header_data_blob.bin aux_data_blob.bin > header_aux.bin
# 验证 Hash
$ sha256sum header_aux.bin
c30988de75e22ffbc20a7630bf9bf09655f2ef4b9cc1f9b32b02f0166f4c11a2 header_aux.bin
签名计算:
Signature = RSA_sign(PKCS1-v1_5_Padding || Hash)
SHA256_RSA2048 的 padding 格式:
padding = [
0x00, 0x01] + [0xFF] * 202 + [0x00] + [
0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
]
手动验证签名:
# 生成 padding + hash
$ printf '\x00\x01' > padding.bin
$ dd if=/dev/zero bs=1 count=202 2>/dev/null | tr '\000' '\377' | \
dd of=padding.bin bs=1 seek=2 conv=notrunc
$ printf '\x00\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20' >> padding.bin
$ sha256sum header_aux.bin | cut -d' ' -f1 | xxd -r -p > hash.bin
$ cat padding.bin hash.bin > padding_and_hash.bin
# 签名
$ openssl rsautl -sign -inkey rsa2048.pem -raw < padding_and_hash.bin > signature.bin
# 验证签名
$ xxd signature.bin
0000000: ca12 5a82 ef95 16c6 58f9 8590 6306 dd29 ..Z.....X...c..)
......
00000f0: 8054 7ed5 0e14 5173 3791 cbf9 f8b3 0ded .T~...Qs7.......
注意:
padding + hash总长度等于 RSA 模数字节数 (256)。
4.3 Auxiliary Block(辅助块)
布局:
+--------------------------+ aux offset 0
| Descriptors | descriptors_offset=0, descriptors_size=N
+--------------------------+
| Public Key | public_key_offset=N
+--------------------------+
| Public Key Metadata | public_key_metadata_offset=N+key_size
+--------------------------+
| Padding (\0) | 补齐到 64 字节倍数
+--------------------------+
Descriptors 详细分析
此处的 Descriptor 类型为 AvbHashDescriptor。
固定头部 (132 字节),使用大端序:
| 偏移 | 字段 | 格式 | 字节 | 说明 |
|---|---|---|---|---|
| 0 | tag | Q | 8 | = 2,标识 descriptor 类型 |
| 8 | num_bytes_following | Q | 8 | 后续总字节数(不含 tag 和自身) |
| 16 | image_size | Q | 8 | 镜像数据大小 |
| 24 | hash_algorithm | 32s | 32 | 算法名,NUL 填充 |
| 56 | partition_name_len | L | 4 | 分区名长度 |
| 60 | salt_len | L | 4 | Salt 长度 |
| 64 | digest_len | L | 4 | Digest 长度 |
| 68 | flags | L | 4 | 标志位 |
| 72 | reserved | 60s | 60 | 保留,全 0 |
| 合计 | 132 |
# 从偏移 4672 (0x1240) 开始
0001240: 0000 0000 0000 0002 0000 0000 0000 00b8 ................
0001250: 0000 0000 0000 001c 7368 6132 3536 0000 ........sha256..
0001260: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0001270: 0000 0000 0000 0000 0000 0004 0000 0020 ...............
0001280: 0000 0020 0000 0000 0000 0000 0000 0000 ... ............
字段解析:
| 字段 | 十六进制值 | 解析结果 |
|---|---|---|
tag | 00 00 00 00 00 00 00 02 | 2 (HashDescriptor) |
num_bytes_following | 00 00 00 00 00 00 00 B8 | 184 |
image_size | 00 00 00 00 00 00 00 1C | 28 (原始镜像大小) |
hash_algorithm | 73 68 61 32 35 36 00... | "sha256" |
partition_name_len | 00 00 00 04 | 4 |
salt_len | 00 00 00 20 | 32 |
digest_len | 00 00 00 20 | 32 |
flags | 00 00 00 00 | 0 |
变长尾部(紧接固定头之后,偏移 4804 / 0x12C4):
拼接顺序:partition_name + salt + digest + padding
ret = desc + encoded_name + self.salt + self.digest + padding
# 从 0x12C4 开始
00012c0: 0000 0000 7465 7374 f8c7 3426 02a2 c0c2 ....test..4&....
00012d0: 4dbb eaf7 465c be86 c82a 946d ed8b 1a8c M...F\...*.m....
00012e0: 7959 2e33 6c27 0d0e 6f09 c32f 7494 e149 yY.3l'..o../t..I
00012f0: bb37 c2d8 df2c 98c0 005e 157b 677f bbf2 .7...,...^.{g...
| 段 | 数据 | 长度 | 说明 |
|---|---|---|---|
partition_name | 74 65 73 74 | 4 | "test" |
salt | f8c7 3426 02a2 c0c2 4dbb eaf7 465c be86 c82a 946d ed8b 1a8c 7959 2e33 6c27 0d0e | 32 | 盐值 |
digest | 6f09 c32f 7494 e149 bb37 c2d8 df2c 98c0 005e 157b 677f bbf2 38e7 84d3 4a46 5c0e | 32 | SHA256(salt + origin_data) |
Digest 验证:
# 提取 salt
$ dd if=test.img of=salt.bin skip=4808 ibs=1 count=32 # 0x12C8
# 拼接 salt + 原始数据
$ cat salt.bin <(dd if=/dev/zero bs=28 count=1 2>/dev/null) > salt_origin.bin
# 验证 digest
$ sha256sum salt_origin.bin
6f09c32f7494e149bb37c2d8df2c98c0005e157b677fbbf238e784d34a465c0e salt_origin.bin
Public Key
# public_key_offset = 0xC8,从 0x1308 开始
0001300: 38e7 84d3 4a46 5c0e 0000 0800 4cbb a51d 8...JF\.....L...
# ... (520 字节)
0001500: e6d0 7b84 e708 8d5a 80de d3f6 f533 c17e ..{....Z.....3.~
Public Key Metadata
# public_key_metadata_offset = 0x2D0,从 0x1510 开始
# public_key_metadata_size = 0,此处为空
0001510: 0000 0000 0000 0000 0000 0000 0000 0000 ................
5. footer_blob_with_padding
布局说明
add_hash_footer 会在镜像末尾预留 4096 字节用于存储 AvbFooter,其中:
- 前 4032 字节 (4096 - 64):全 0 填充
- 后 64 字节:
AvbFooter结构体
AvbFooter 结构体 (64 字节)
使用大端序 (!),格式串 '!4s 2L Q Q Q 28x':
| 偏移 | 字段 | 字节 | 说明 |
|---|---|---|---|
| 0 | magic | 4 | 'AVBf' (0x41564266) |
| 4 | version_major | 4 | 主版本号 |
| 8 | version_minor | 4 | 次版本号 |
| 12 | original_image_size | 8 | 原始镜像大小 (28 字节) |
| 20 | vbmeta_offset | 8 | VBMeta blob 偏移 (0x1000) |
| 28 | vbmeta_size | 8 | VBMeta blob 大小 (0x540) |
| 36 | reserved | 28 | 全 0 填充 |
| 合计 | 64 |
二进制数据
# 文件末尾 4096 字节区域
# 偏移 0x1000-0x1FBF: 4032 字节的 0x00 填充
# 偏移 0x1FC0: AvbFooter 起始位置
# 4032 字节的 0x00 填充
0011000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0011010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
... (中间省略) ...
0011fa0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0011fb0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0011fc0: 4156 4266 0000 0001 0000 0000 0000 0000 AVBf............ ← 64 字节 AvbFooter 起始
0011fd0: 0000 001c 0000 0000 0000 1000 0000 0000 ................
0011fe0: 0000 0540 0000 0000 0000 0000 0000 0000 ...@............
0011ff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0012000: ← 文件结束 (73728 字节)
字段解析
| 偏移 | 十六进制数据 | 解析结果 |
|---|---|---|
| 0x1FC0 | 41 56 42 66 | "AVBf" (magic) |
| 0x1FC4 | 00 00 00 01 | 主版本号 = 1 |
| 0x1FC8 | 00 00 00 00 | 次版本号 = 0 |
| 0x1FCC | 00 00 00 00 00 00 00 1C | 原始镜像大小 = 28 |
| 0x1FD4 | 00 00 00 00 00 00 10 00 | vbmeta_offset = 4096 (0x1000) |
| 0x1FDC | 00 00 00 00 00 00 05 40 | vbmeta_size = 1344 (0x540) |
| 0x1FE4 | 00... (28 字节) | reserved = 全 0 |
6. 完整布局总结
偏移 0x0000
┌─────────────────────────────────┐
│ 原始数据 (28 字节) │
│ + 0x00 填充到 4096 byte │ ← 原始镜像分区
├─────────────────────────────────┤
│ vbmeta_blob (0x540 byte) │ ← 包含 Header + Authentication + Auxiliary
│ - Header: 256 byte │
│ - Authentication: 320 byte │
│ - Auxiliary: 768 byte │
├─────────────────────────────────┤
│ 0x00 padding │ ← 填充至 4096 字节边界前的剩余空间
├─────────────────────────────────┤
│ Footer (4096 byte) │
│ ├─ 0x00 padding (4032 byte) │ ← 前 4032 字节全 0
│ └─ AvbFooter (64 byte) │ ← 最后 64 字节
│ ├─ magic: "AVBf" │
│ ├─ version: 1.0 │
│ ├─ original_image_size: 28 │
│ ├─ vbmeta_offset: 0x1000 │
│ ├─ vbmeta_size: 0x540 │
│ └─ reserved: 0 │
└─────────────────────────────────┘
偏移 0x12000 ← 文件结束 (73728 字节 = 4096 × 18)
7. 关键要点
- 分区大小必须 4K 对齐:
partition_size必须是 4096 的倍数 - Authentication Block 包含 Hash 和 Signature,其大小为 320 字节(64 字节对齐)
- Hash 计算范围:
Header + Auxiliary,不含 Authentication 自身 - 签名机制:对
PKCS1-v1_5_Padding + Hash进行 RSA 签名 - Footer 固定在文件末尾 64 字节,包含 VBMeta 位置信息
- 原始数据 在
vbmeta_blob之前,已被填充到 4096 字节边界