相关文章:
Android A/B System 分析系列
Android A/B System OTA 分析(一)概览Android A/B System OTA 分析(二)系统image的生成Android A/B System OTA 分析(三)主系统和bootloader的通信Android A/B System OTA 分析(四)系统的启动和升级Android A/B System OTA 分析(五)客户端参数Android A/B System OTA 分析(六)如何获取 payload 的 offset 和 size
Android Update Engine 分析系列
Android Update Engine 分析(一)MakefileAndroid Update Engine 分析(二)Protobuf和AIDL文件Android Update Engine 分析(三)客户端进程Android Update Engine 分析(四)服务端进程Android Update Engine 分析(五)服务端核心之Action机制Android Update Engine 分析(六)服务端核心之Action详解Android Update Engine 分析(七)DownloadAction之FileWriterAndroid Update Engine 分析(八)升级包制作脚本分析Android Update Engine 分析(九)delta_generator 工具的 6 种操作Android Update Engine 分析(十)生成 payload 和 metadata 的哈希Android Update Engine 分析(十一) 更新 payload 签名Android Update Engine 分析(十二) 验证 payload 签名
在《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》中提到了,delta_generator 工具提供的 6 种操作,分别是:
- 生成 payload 和 metadata 数据的哈希值更新 payload 和 metadata 数据的签名使用公钥验证 payload 和 metadata 数据的签名提取 payload 文件的 properties数据对 old image 打 delta 补丁生成 payload 数据
上两篇详细分析了 metadata 和 payload 的哈希如何生成,如何签名,如何更新到 payload 文件中,本篇继续分析代码是如何验证 payload 签名的,包括代码中验证签名的流程和签名在命令行的手动验证。
本篇主要内容有三点:
- 如何提供公钥和验证签名总结 payload 的处理流程手动在命令行提取 payload 的数据进行验证
如果只想知道验证的公钥是如何生成的,请参考 “1.2 如何生成验证的公钥”。如果只想简单看下 payload 和 metadata 签名的调用流程,请参考 “4.1 验证 payload 和 metadata 签名的流程”。如果只想知道如何在命令行提取和验证 payload 和 metadata 签名,请参考 “4.2 手动签名验证总结”
1. 使用公钥验证 payload 签名 1.1 使用公钥验证签名本文涉及的Android代码版本:android‐7.1.1_r23 (NMF27D)
上一篇《Android Update Engine 分析(十一) 更新 payload 签名》的第 1 节又一次总结了 payload 数据的处理流程,这里不再重复,直接进入本文的重点。
密码学上,签名和验证使用非对称密钥,其中公开的密钥叫公钥,没有公开的密钥叫私钥。签名时使用私钥,验证签名(验签)时使用公钥,即所谓的私钥签名,公钥验证。
如果调用 delta_generator 工具时提供了 --public_key 参数,则会使用这个 key 来验证 payload 文件的签名,例如:
$ delta_generator --in_file=payload.bin --public_key=key-pub.key [0121/095347:INFO:generate_delta_main.cc(172)] Verifying signed payload. [0121/095348:INFO:payload_verifier.cc(93)] signature blob size = 264 [0121/095348:INFO:payload_verifier.cc(112)] Verified correct signature 1 out of 1 signatures. [0121/095348:INFO:payload_verifier.cc(93)] signature blob size = 264 [0121/095348:INFO:payload_verifier.cc(112)] Verified correct signature 1 out of 1 signatures. [0121/095348:INFO:generate_delta_main.cc(178)] Done verifying signed payload.
关于关于参数的传递和解析,这里不再详细分析,不清楚的请转到《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》 第 2.2 节,查看参数到底是如何解析的。
1.2 如何生成验证的公钥这里验证的公钥到底是怎么来的?
在对 payload 进行签名时,如果没有提供签名用的 key,则默认从 build/target/product/security/testkey.pk8 文件中导出一个 RSA 的私钥用于签名,如下:
# 如果没有指定签名使用的 key, 则基于 testkey.pk8 生成一个临时 key 用于签名 openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt -out /tmp/key-temp.key
Android 默认附带的 test key 文件 build/target/product/security/testkey.pk8 是一个按照 PKCS#8 格式的存储的私钥,而签名时需要的是 PKCS#1 格式的密钥,因此这里的 openssl pkcs8 命令就是将 PKCS#8 格式的私钥转换成 PKCS#1 格式的私钥。
这里导出的 PKCS#1 格式的私钥可用于签名,如果要验证签名的话,还需要从私钥中提取 PEM 格式的公钥:
$ openssl rsa -in key-temp.key -pubout -outform PEM -out key-pub.key $ cat key-pub.key -----BEGIN PUBLIC KEY----- MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA1pMZBN7GCySx7cdi4NnY JT4+zWzrHeL/Boyo6LyozWvTeG6nCqds5g67D5k1Wf/ZPnepQ+foPUtkuOT+otPm VvHiZ6gbv7IwtXjCBEO+THIYuEb1IRWG8DihTonCvjh/jr7Pj8rD2h7jMMnqk9Cn w9xK81AiDVAIBzLggJcX7moFM1nmppTsLLPyhKCkZsh6lNg7MQk6ZzcuL2QSwG5t QvFYGN/+A4HMDNRE2mzdw7gkWBlIAbMlZBNPv96YySh3SNv1Z2pUDYFUyLvKB7ni R1UzEcRrmvdv3uzMjmnnyKLQjngmIJQ/mXJ9PAT+cpkdmd+brjigshd/ox1bav7p HwIBAw== -----END PUBLIC KEY-----
说下这里的参数:
-in key-temp.key, 指定用于处理的私钥文件-pubout, 即 public key out,输出公钥文件-outform PEM,输出格式为 PEM (有多种格式可选,比如 PEM,DER 等,这里使用最常用的 PEM 格式)-out key-pub.key, 指定输出的公钥文件名字 1.3 关于公钥 brillo-update-payload-key.pub.pem
仔细观察下 update_engine 代码的目录结构,发现系统在 update_payload_key 下提供了一个公钥:
$ ls -lh update_payload_key/ total 8.0K -rw-r--r-- 1 rg935739 users 138 Mar 31 2017 README -rw-r--r-- 1 rg935739 users 451 Mar 31 2017 brillo-update-payload-key.pub.pem $ cat update_payload_key/brillo-update-payload-key.pub.pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAxPqfII4vIe3cqKzdvl gwjBhj9kyF+6ig73yZq0o4wLOq3nsRUToaIOtQmcjr1G+hhSXBU3WTbfZLlm07Fb B535o2zhYghs8Br7xobjX+gikEnxnFuTtB2sB4Gpan4hKwU+BuZhJDSl1oZwUJJ4 eiGJpH5xJswbyO/bA81BCMjU3rm+G6SzOLQTK0YEnhn7bB69UucM57GM7l+dCl8r RhKjbpP7E1fVtgX++BGs6pKciPLxYfXVup0MgH0h8VdSDMiHkshIXYvcCV1KOBFX 9GrYvXLtq41Hm5hC5l48mwLi0ALdIfbPQ5oHLl2u+etLmGwbMpzhybTCZQA/SgEl HwIDAQAB -----END PUBLIC KEY-----
这里的 brillo-update-payload-key.pub.pem 是 PEM 格式的公钥, 在 Makefile 中是这样引用的:
# Update payload signing public key. # ======================================================== ifdef BRILLO include $(CLEAR_VARS) LOCAL_MODULE := brillo-update-payload-key LOCAL_MODULE_CLASS := ETC LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/update_engine LOCAL_MODULE_STEM := update-payload-key.pub.pem LOCAL_SRC_FILES := update_payload_key/brillo-update-payload-key.pub.pem LOCAL_BUILT_MODULE_STEM := update_payload_key/brillo-update-payload-key.pub.pem include $(BUILD_PREBUILT) endif # BRILLO
只不过我们在《Android Update Engine 分析(一)Makefile》分析过,Android 系统中 BRILLO 没有被定义,所以这个公钥不会被使用。忙活了半天,空欢喜一场。
2. payload 签名验证流程上一节演示了如何使用命令 delta_generator --in_file=payload.bin --public_key=key-pub.key 来验证签名,本节从代码角度分析整个签名验证的过程。
2.1 Main 函数调用 VerifySignedPayload在 generate_delta_main.cc 文件的 Main 函数中,检测到提供了 --public_key 参数,则调用 VerifySignedPayload 去验证签名:
// 文件: system/update_engine/payload_generator/generate_delta_main.cc
if (!FLAGS_public_key.empty()) {
LOG_IF(WARNING, FLAGS_public_key_version != -1)
<< "--public_key_version is deprecated and ignored.";
// 需要提供 "--in_file" 和 "--public_key" 参数
VerifySignedPayload(FLAGS_in_file, FLAGS_public_key);
return 0;
}
Main 函数直接将接收到的 --in_file 和 --public_key 参数转发给 VerifySignedPaload 函数验证签名:
// 文件: system/update_engine/payload_generator/generate_delta_main.cc
void VerifySignedPayload(const string& in_file,
const string& public_key) {
// 再次检查 in_file 和 public_key 参数
LOG(INFO) << "Verifying signed payload.";
LOG_IF(FATAL, in_file.empty())
<< "Must pass --in_file to verify signed payload.";
LOG_IF(FATAL, public_key.empty())
<< "Must pass --public_key to verify signed payload.";
// 转到 PayloadSigner 执行具体的验证
CHECK(PayloadSigner::VerifySignedPayload(in_file, public_key));
LOG(INFO) << "Done verifying signed payload.";
}
从上面可见,最终执行验证操作的是静态函数 PayloadSigner::VerifySignedPayload。
2.2 PayloadSigner::VerifySignedPayload 函数PayloadSigner::VerifySignedPayload 函数完成了整个签名验证操作。
// 文件: system/update_engine/payload_generator/payload_signer.cc
bool PayloadSigner::VerifySignedPayload(const string& payload_path,
const string& public_key_path) {
DeltaArchiveManifest manifest;
uint64_t metadata_size;
uint32_t metadata_signature_size;
//
// 步骤 1. 准备工作: 解析 payload 的 metadata 数据找到签名的位置和内容
//
// 1a. 解析 payload 文件的 metadata 部分, 包括:
// - magic
// - file_format_version
// - manifest_size
// - metadata_signature_size
// - manifest
TEST_AND_RETURN_FALSE(LoadPayloadmetadata(payload_path,
nullptr,
&manifest,
nullptr,
&metadata_size,
&metadata_signature_size));
brillo::Blob payload;
// 加载 payload 文件内容到 payload 缓存中
TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
// 确保解析得到的 manifest 数据包含 signatures_offset 和 signature_size, 这两个成员指示了 payload 签名的位置和大小
TEST_AND_RETURN_FALSE(manifest.has_signatures_offset() &&
manifest.has_signatures_size());
// 计算 payload 签名的起始位置
uint64_t signatures_offset = metadata_size + metadata_signature_size +
manifest.signatures_offset();
CHECK_EQ(payload.size(), signatures_offset + manifest.signatures_size());
brillo::Blob payload_hash, metadata_hash;
// 1b. 计算 payload 文件的 metadata 数据和 payload 数据的哈希, 分别存放到 payload_hash 和 metadata_hash 中
TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
metadata_size,
metadata_signature_size,
signatures_offset,
&payload_hash,
&metadata_hash));
//
// 步骤 2. 验证 payload 数据的签名
//
// 2a. 提取 payload 数据的签名到 signature_blob 中
brillo::Blob signature_blob(payload.begin() + signatures_offset,
payload.end());
// 2b. 对 payload 数据的 SHA256 的哈希结果进行填充,使其满足 PKCS1-v1_5 填充格式格式
// 填充格式是固定的,如下:
// 0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH
// |--------------205-----------||----19----||----32----|
// 这里填充的是前面 205 + 19 字节的部分
TEST_AND_RETURN_FALSE(PayloadVerifier::PadRSA2048SHA256Hash(&payload_hash));
// 2c. 验证 payload 数据的签名,操作原理是:
// 第 1 步. 使用 public key 对提取的 signature_blob 数据进行解密
// 第 2 步. 理论上步骤 1 解密的结果和这里填充后的 payload_hash 应该一样
// 第 3 步. 比较步骤 1 中得到的哈希和 payload_hash,如果一样则签名验证成功,否则验证失败
TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature(
signature_blob, public_key_path, payload_hash));
//
// 步骤 3. 验证 metadata 数据的签名
//
// 3a. 提取 metadata 数据的签名到 signature_blob 中
if (metadata_signature_size) {
signature_blob.assign(payload.begin() + metadata_size,
payload.begin() + metadata_size +
metadata_signature_size);
// 3b. 对 metadata 数据的 SHA256 的哈希结果进行填充,使其满足 PKCS1-v1_5 填充格式格式
TEST_AND_RETURN_FALSE(
PayloadVerifier::PadRSA2048SHA256Hash(&metadata_hash));
// 3c. 验证 metadata 数据的签名,其操作和验证 payload 数据签名一样
TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature(
signature_blob, public_key_path, metadata_hash));
}
return true;
}
总结下 PayloadSigner::VerifySignedPayload 函数的操作:
- 解析 payload 头部的 metadata, 找到 payload 和 metadata 签名的位置和内容,同时根据文件内容重新计算 payload 和 metadata 的哈希使用公钥验证 payload 签名
对 payload 哈希按照签名格式进行填充使用公钥解密 payload 签名数据, 将解密的签名数据和 payload 哈希填充内容进行比较 使用公钥验证 metadata 签名
对 metadata 哈希按照签名格式进行填充使用公钥解密 metadata 签名数据, 将解密的签名数据和 payload 哈希填充内容进行比较
填充函数 PayloadVerifier::PadRSA2048SHA256Hash
签名验证时,第一步是计算哈希值并按照要求进行填充,我们来看下是如何填充的。
// 文件: system/update_engine/payload_consumer/payload_verifier.cc
bool PayloadVerifier::PadRSA2048SHA256Hash(brillo::Blob* hash) {
TEST_AND_RETURN_FALSE(hash->size() == 32);
// 将预先准备好的填充数据添加到哈希数据的前面
hash->insert(hash->begin(),
reinterpret_cast(kRSA2048SHA256Padding),
reinterpret_cast(kRSA2048SHA256Padding +
sizeof(kRSA2048SHA256Padding)));
TEST_AND_RETURN_FALSE(hash->size() == 256);
return true;
}
因为用 RSA 私钥签名时采用 PKCS-v1_5 格式的填充,使用的哈希算法为 SHA256。
哈希算法和填充格式确定后,填充的内容(包括长度和数据)就固定了:
0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH |--------------205-----------||----19----||----32----|
所以这里预先准备好填充数据 kRSA2048SHA256Padding,直接和计算的哈希值连接在一起。
签名验证函数 PayloadVerifier::VerifySignature
// 文件: system/update_engine/payload_consumer/payload_verifier.cc
bool PayloadVerifier::VerifySignature(const brillo::Blob& signature_blob,
const string& public_key_path,
const brillo::Blob& hash_data) {
TEST_AND_RETURN_FALSE(!public_key_path.empty());
// 从 protobuf 中格式的数据中还原出原始的 signature 数据到 signatures 中
Signatures signatures;
LOG(INFO) << "signature blob size = " << signature_blob.size();
TEST_AND_RETURN_FALSE(signatures.ParseFromArray(signature_blob.data(),
signature_blob.size()));
if (!signatures.signatures_size()) {
LOG(ERROR) << "No signatures stored in the blob.";
return false;
}
std::vector tested_hashes;
// Tries every signature in the signature blob.
// 逐个验证每个签名数据
for (int i = 0; i < signatures.signatures_size(); i++) {
const Signatures_Signature& signature = signatures.signatures(i);
// 提取签名的原始数据
brillo::Blob sig_data(signature.data().begin(), signature.data().end());
brillo::Blob sig_hash_data;
// GetRawHashFromSignature 使用公钥解密签名数据(因为签名数据是用私钥加密生成的,只能使用公钥解密)
if (!GetRawHashFromSignature(sig_data, public_key_path, &sig_hash_data))
continue;
// 将解密的数据,和传入的经过填充后的哈希数据相比较,如果一样,则签名验证通过
if (hash_data == sig_hash_data) {
LOG(INFO) << "Verified correct signature " << i + 1 << " out of "
<< signatures.signatures_size() << " signatures.";
return true;
}
tested_hashes.push_back(sig_hash_data);
}
// 签名验证失败,输出计算得到的哈希值
LOG(ERROR) << "None of the " << signatures.signatures_size()
<< " signatures is correct. Expected:";
utils::HexDumpVector(hash_data);
LOG(ERROR) << "But found decrypted hashes:";
for (const auto& sig_hash_data: tested_hashes) {
utils::HexDumpVector(sig_hash_data);
}
return false;
}
签名验证函数 PayloadVerifier::VerifySignature 的操作简单总结为:
- 从 payload 文件中根据 probuf 格式还原出签名数据用公钥解密签名数据(理论上: 解密后的签名数据应该和哈希数据填充后的内容一样)将填充后的哈希数据和解密的签名数据比较,如果一样,则签名验证通过;否则提示签名验证失败。
相关资料:
关于使用私钥签名,公钥验证的更多细节,请参考《OpenSSL和Python实现RSA Key数字签名和验证》
底层的解密函数 GetRawHashFromSignature
使用 RSA 签名时,先计算哈希,然后对哈希值按照要求进行填充,再使用私钥加密得到签名。
因此,这里使用公钥解密签名,得到的就是填充后的数据。
GetRawHashFromSignature 函数用指定的公钥解密签名,得到填充后的数据:
bool PayloadVerifier::GetRawHashFromSignature(
const brillo::Blob& sig_data,
const string& public_key_path,
brillo::Blob* out_hash_data) {
TEST_AND_RETURN_FALSE(!public_key_path.empty());
// The code below executes the equivalent of:
//
// openssl rsautl -verify -pubin -inkey |public_key_path|
// -in |sig_data| -out |out_hash_data|
// 从传入的密钥文件中提取公钥数据
// Loads the public key.
FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
if (!fpubkey) {
LOG(ERROR) << "Unable to open public key file: " << public_key_path;
return false;
}
char dummy_password[] = { ' ', 0 }; // Ensure no password is read from stdin.
RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, nullptr, nullptr, dummy_password);
fclose(fpubkey);
TEST_AND_RETURN_FALSE(rsa != nullptr);
unsigned int keysize = RSA_size(rsa);
if (sig_data.size() > 2 * keysize) {
LOG(ERROR) << "Signature size is too big for public key size.";
RSA_free(rsa);
return false;
}
// 使用公钥解密签名数据
// Decrypts the signature.
brillo::Blob hash_data(keysize);
int decrypt_size = RSA_public_decrypt(sig_data.size(),
sig_data.data(),
hash_data.data(),
rsa,
RSA_NO_PADDING);
RSA_free(rsa);
TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
decrypt_size <= static_cast(hash_data.size()));
hash_data.resize(decrypt_size);
// 将解密数据存放到 out_hash_data 中返回
out_hash_data->swap(hash_data);
return true;
}
3. 手动使用命令行工具验证签名
3.1 准备验签的公钥
前面 1.2 节有提到如何生成验签的公钥,这里以 Android 默认的使用的 testkey 为例:
# 1. 将 PKCS#8 格式的私钥转换成 PKCS#1 格式的私钥 openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt -out /tmp/key-temp.key # 2. 从私钥中提取公钥 openssl rsa -in key-temp.key -pubout -outform PEM -out key-pub.key3.2 从 payload.bin 中提取 metadata 签名数据
在《Android Update Engine 分析(十)生成 payload 和 metadata 的哈希》的第 4 节(“4. 命令行手工计算 payload 和 metadata 的哈希”) 详细演示过如何通过命令行提取 metadata 和 payload 的哈希,因此这里直接提取 metadata 和 payload 的签名数据进行验证。
payload 数据的头部 128 字节如下:
$ hexdump -Cv -n 128 payload.bin
00000000 43 72 41 55 00 00 00 00 00 00 00 02 00 00 00 00 |CrAU............|
00000010 00 00 52 de 00 00 01 08 18 80 20 20 cb c2 e7 7d |..R....... ...}|
00000020 28 88 02 60 03 6a d1 07 0a 04 62 6f 6f 74 32 27 |(..`.j....boot2'|
00000030 08 80 c0 a3 09 12 20 b5 58 f2 3f 83 f2 01 ad 53 |...... .X.?....S|
00000040 09 88 ac 77 d3 48 1a bd 1f 76 6e 15 b4 8a d5 cf |...w.H...vn.....|
00000050 ec 3f 56 b8 ef c7 90 3a 27 08 80 c0 a3 09 12 20 |.?V....:'...... |
00000060 7c ea 74 e8 96 57 ac bd 01 a1 fc eb 65 dc 1e 5e ||.t..W......e..^|
00000070 a1 c7 7b 28 d0 c5 94 97 5c e9 84 aa db 82 71 41 |..{(.........qA|
00000080
$
这里直接说头部 metadata 数据分析的结论:
offset 0: 4 字节的 magic [00 00 00 00 00 00 00 02] “CrAU”offset 4: 8 字节的 file_format_version,[00 00 00 00 00 00 00 02], 大端格式,其值为 2offset 12: 8 字节的 manifest_size,[00 00 00 00 00 00 52 de], 大端格式,其值为 0x52de,即十进制的 21214offset 20: 4 字节的 metadata_signature_size,[00 00 01 08], 大端格式,其值为 0x0108,即十进制的 264
根据 payload.bin 文件的布局图,可以知道 metadata 数据的签名 metadata_signature_message 数据信息:
起始地址: header(24 bytes) + manifest(21214 bytes) = 21238大小: 264 bytes
有了起始地址和大小,我们就可以从 payload.bin 中提取 metadata 的签名了:
# 提取 metadata 签名 $ dd if=payload.bin skip=21238 bs=1 count=264 of=metadata-signature.bin # 查看签名数据 $ xxd -g 1 metadata-signature.bin 00000000: 0a 85 02 08 01 12 80 02 98 23 7f ea e1 24 9f c0 .........#...$.. 00000010: 6f 43 07 f1 52 de bb 96 61 29 4c 5e 6c ef 24 c9 oC..R...a)L^l.$. 00000020: a1 f0 00 6b 05 2d b7 90 36 a9 ce a2 08 20 b5 4e ...k.-..6.... .N 00000030: 78 6b 05 69 6c b0 72 db 97 83 5d ee ec ea db 79 xk.il.r...]....y 00000040: 5f 60 5e 2e 02 f7 e6 23 37 ab 17 ab dc d8 19 62 _`^....#7......b 00000050: c8 e2 48 7d 5a 5e af 54 c1 23 a6 16 8e 59 df f0 ..H}Z^.T.#...Y.. 00000060: 35 47 0a a4 5e 9e 0a 3a 94 04 9e c7 a0 71 d5 91 5G..^..:.....q.. 00000070: 15 24 31 1b 80 a6 6f 42 8d 94 b4 30 45 1c a7 e8 .$1...oB...0E... 00000080: 75 5c cb ee 81 1f 40 76 ed ac 52 6c f9 4c 32 f9 u....@v..Rl.L2. 00000090: 36 be 55 a5 e5 76 dc 81 a8 2a 0d 1a 78 5f 04 de 6.U..v...*..x_.. 000000a0: a9 5e d8 32 47 33 26 0f d2 c0 34 79 25 6b 06 bd .^.2G3&...4y%k.. 000000b0: 3e fc 72 43 af 7d c6 4d 32 4b 00 58 ba 0b 96 27 >.rC.}.M2K.X...' 000000c0: 0c d4 61 30 1b ea c6 1d c9 e0 f1 19 b8 51 95 12 ..a0.........Q.. 000000d0: 45 f7 8a f6 96 e3 12 73 ba 08 58 ab 11 8d f1 f5 E......s..X..... 000000e0: 16 04 fb 65 36 0e 38 c5 44 81 48 4e 78 d0 89 23 ...e6.8.D.HNx..# 000000f0: a7 97 fa 02 4c 78 5d 53 35 30 89 57 ab db 0b 49 ....Lx]S50.W...I 00000100: 49 dc db f4 e6 8c d5 e8 I.......3.3 使用 protobuf 工具还原 metadata 签名数据
对于采用 SHA256 哈希的 RSA 签名,其签名数据大小为 256 bytes, 但 payload 头部数据却显示为 264 bytes,这是为什么呢?我们使用 264 字节提取的签名数据应该怎么验证呢?
payload 文件的数据是按照 protobuf 格式组织,所以从 payload 中截取的签名 metadata-signature.bin 也就是 probobuf 编码格式,这里需要从 protobuf 格式中提取原始的签名数据。
关于 ProtoBuf 数据, 网上有很多文章介绍 ProtoBuf,我参考了这两篇:
深入 ProtoBuf - 简介深入 ProtoBuf - 编码
对于签名数据,其 protobuf 格式在 system/update_engine/update_metadata.proto 文件中是这样定义的:
message Signatures {
message Signature {
optional uint32 version = 1;
optional bytes data = 2;
}
repeated Signature signatures = 1;
}
将这里的 Signatures 消息定义存储到 signature.proto 文件中,然后使用 out/host/linux-x86/bin 工具进行逆向解析还原:
$ ./out/host/linux-x86/bin/aprotoc --version
libprotoc 2.6.1
$
$ cat metadata-signature.bin | out/host/linux-x86/bin/aprotoc --decode=Signatures signature.proto
signatures {
version: 1
data: "230#177352341$237300oC 07361R336273226a)L^l357$311241360 00k 05-2672206251316242 10 265Nxk 05il260r333227203]356354352333y_`^. 02367346#7253 27253334330 31b310342H}Z^257T301#246 26216Y3373605Gn244^236n:224 04236307240q325221 25$1 33200246oB2152242640E 34247350u\313356201 37@v355254Rl371L23716276U245345v334201250*r 32x_ 04336251^3302G3&

