APK 签名方案 v3
1. 具有密钥轮转的 APK 签名方案
Android 9
新增了对 APK Signature Scheme v3
的支持。该架构提供的选择可以在其签名块中为每个签名证书加入一条轮转证据记录。 利用此功能,应用可以通过将 APK
文件过去的签名证书链接到现在签署应用时使用的证书,从而使用新签名证书来签署应用。
注:运行 Android 8.1(API 级别 27)或更低版本的设备不支持更改签名证书。 如果应用的
minSdkVersion
为27
或更低,除了新签名之外,可使用旧签名证书来签署应用。
基本使用:
轮替签名证书世系或新签名序列的语法如下:
$ apksigner rotate --in /path/to/existing/lineage \
--out /path/to/new/file \
--old-signer --ks old-signer-jks \
--new-signer --ks new-signer-jks
详细了解如何使用 apksigner 轮转密钥。
2. APK 签名方案 v3
Android 9
支持 APK 密钥轮替,这使应用能够在 APK 更新过程中更改其签名密钥。为了实现轮替,APK
必须指示新旧签名密钥之间的信任级别。为了支持密钥轮替,我们将 APK 签名方案从 v2
更新为 v3
,以允许使用新旧密钥。v3
在 APK
签名分块中添加了有关受支持的 SDK
版本和 proof-of-rotation
结构的信息。
注意:在 Android 11 发布之前,建议不要使用 APK 密钥轮替。开发者可以选择针对新安装应用使用 Google Play 的密钥升级。注册了 Play 应用签名的开发者可以通过 Play 管理中心请求升级。
为新安装的应用升级应用签名密钥
在某些情况下,您可以请求升级应用签名密钥。对于新安装的应用或其更新,将使用此新密钥进行签名。
对于在您的应用签名密钥升级之前便已安装该应用的用户,应用更新仍将使用您的旧版密钥来签名。
在每个应用的生命周期内,其应用签名密钥只能升级一次。
在极少数情况下,假如您有多个应用使用相同的签名密钥并且在同一进程中运行,则无法使用密钥升级功能。
以下是请求升级应用签名密钥的几种原因:
- 您需要加密强度更高的密钥。
- 您的应用签名密钥已被盗。
注意:在 Play 管理中心请求升级应用签名密钥与 Android P 及更高版本的 APK 签名方案 v3 中引入的密钥轮换机制无关。
2.1 APK 签名分块
为了保持与 v1 APK
格式的向后兼容性,v2
和 v3 APK
签名存储在“APK 签名分块”
内紧邻 ZIP Central Directory
前面。
v3 APK 签名分块
的格式与 v2 相同。APK
的 v3
签名会存储为一个“ID-值”
对,其中 ID
为 0xf05368c0
。
格式
“APK 签名分块”
的格式如下(所有数字字段均采用小端字节序):
- size of block,以字节数(不含此字段)计 (uint64)
- 带 uint64 长度前缀的“ID-值”对序列:
- ID (uint32)
- value(可变长度:“ID-值”对的长度 - 4 个字节)
- size of block,以字节数计 - 与第一个字段相同 (uint64)
- magic“APK 签名分块 42”(16 个字节)
在解析 APK
时,首先要通过以下方法找到“ZIP 中央目录”
的起始位置:在文件末尾找到“ZIP 中央目录结尾”
记录,然后从该记录中读取“中央目录”
的起始偏移量。通过 magic
值,可以快速确定“中央目录”
前方可能是“APK 签名分块”
。然后,通过 size of block
值,可以高效地找到该分块在文件中的起始位置。
在解译该分块时,应忽略 `ID` 未知的`“ID-值”`对。
2.2 APK 签名方案 v3 分块
v3
方案的设计与 v2 方案非常相似,它们采用相同的常规格式,并支持相同的签名算法 ID、密钥大小和 EC
曲线。
但是,v3
方案增添了有关受支持的 SDK
版本和 proof-of-rotation
结构的信息。
格式
“APK 签名方案 v2 分块”
存储在“APK 签名分块”
内,ID
为 0xf05368c0
。
“APK 签名方案 v3 分块”
采用 v2
的格式:
- 带长度前缀的 signer(带长度前缀)序列:
- 带长度前缀的 signed data:
- 带长度前缀的 digests(带长度前缀)序列:
- signature algorithm ID(4 个字节)
- digest(带长度前缀)
- 带长度前缀的 X.509 certificates 序列:
- 带长度前缀的 X.509 certificate(ASN.1 DER 形式)
- minSDK (uint32) - 如果平台版本低于此数字,应忽略该签名者。
- maxSDK (uint32) - 如果平台版本高于此数字,应忽略该签名者。
- 带长度前缀的 additional attributes(带长度前缀)序列:
- ID (uint32)
- value(可变长度:附加属性的长度 - 4 个字节)
- ID - 0x3ba06f8c
- value - Proof-of-rotation 结构
- 带长度前缀的 digests(带长度前缀)序列:
- minSDK (uint32) - 签名数据部分中 minSDK 值的副本 - 用于在当前平台不在相应范围内时跳过对此签名的验证。必须与签名数据值匹配。
- maxSDK (uint32) - 签名数据部分中 maxSDK 值的副本 - 用于在当前平台不在相应范围内时跳过对此签名的验证。必须与签名数据值匹配。
- 带长度前缀的 signatures(带长度前缀)序列:
- signature algorithm ID (uint32)
- signed data 上带长度前缀的 signature
- 带长度前缀的 public key(SubjectPublicKeyInfo,ASN.1 DER 形式)
- 带长度前缀的 signed data:
2.3 Proof-of-rotation 和 self-trusted-old-certs 结构
proof-of-rotation
结构允许应用轮替其签名证书,而不会使这些证书在与这些应用通信的其他应用上被屏蔽。为此,应用签名需包含两个新数据块:
- 告知第三方应用的签名证书可信(只要其先前证书可信)的断言
- 应用的旧签名证书(应用本身仍信任这些证书)
签名数据部分中的 proof-of-rotation
属性包含一个单链表,其中每个节点都包含用于为之前版本的应用签名的签名证书。此属性旨在包含概念性 proof-of-rotation
和 self-trusted-old-certs
数据结构。该单链表按版本排序,最旧的签名证书对应于根节点。在构建 proof-of-rotation
数据结构时,系统会让每个节点中的证书为列表中的下一个证书签名,从而为每个新密钥提供证据来证明它应该与旧密钥一样可信。
在构造 self-trusted-old-certs
数据结构时,系统会向每个节点添加标记来指示它在组中的成员资格和属性。例如,可能存在一个标记,指示给定节点上的签名证书可信,可获得 Android
签名权限。此标记允许由旧证书签名的其他应用仍被授予由使用新签名证书签名的应用所定义的签名权限。由于整个 proof-of-rotation
属性都位于 v3 signer
字段的签名数据部分中,因此用于为所含 APK
签名的密钥会保护该属性。
此格式排除了多个签名密钥的情况和将不同祖先签名证书收敛到一个证书的情况(多个起始节点指向一个通用接收器)。
格式
proof-of-rotation
存储在“APK 签名方案 v3 分块”
内,ID
为 0x3ba06f8c
。其格式为:
- 带长度前缀的 levels(带长度前缀)序列:
- 带长度前缀的 signed data(由上一个证书签名 - 如果存在上一个证书)
- 带长度前缀的 X.509 certificate(ASN.1 DER 形式)
- signature algorithm ID (uint32) - 上一级证书使用的算法
- flags (uint32) - 这些标记用于指示此证书是否应该在 self-trusted-old-certs 结构中,以及针对哪些操作。
- signature algorithm ID (uint32) - 必须与下一级签名数据部分中的相应 ID 一致。
- 上述 signed data 上带长度前缀的 signature
- 带长度前缀的 signed data(由上一个证书签名 - 如果存在上一个证书)
多个证书
Android
目前将使用多个证书签名的 APK
视为具有与所含证书不同的签名身份。因此,签名数据部分中的 proof-of-rotation
属性构成了一个有向无环图,最好将其视为单链表,其中给定版本的每组签名者都表示一个节点。这为 proof-of-rotation
结构(下面的多签名者版本)带来了额外的复杂性。排序成为一个特别突出的问题。更重要的是,无法再单独为 APK
签名,因为 proof-of-rotation
结构必须让旧签名证书为新的证书集签名,而不是逐个签名。
例如,如果希望由两个新密钥 B
和 C
签名的 APK
是由密钥 A
签名的,则它不能让 B
签名者仅包含 A
或 B
的签名,因为这是与 B
和 C
不同的签名身份。这意味着签名者必须在构建此类结构之前进行协调。
多个签名者 proof-of-rotation 属性
- 带长度前缀的 sets(带长度前缀)序列:
- signed data(由上一组证书签名 - 如果存在上一组证书)
- 带长度前缀的 certificates 序列
- 带长度前缀的 X.509 certificate(ASN.1 DER 形式)
- signature algorithm IDs (uint32) 序列 - 上一组证书中的每个证书对应一个序列,且采用相同顺序。
- 带长度前缀的 certificates 序列
- flags (uint32) - 这些标记用于指示这组证书是否应该在 self-trusted-old-certs 结构中,以及针对哪些操作。
- 带长度前缀的 signatures(带长度前缀)序列:
- signature algorithm ID (uint32) - 必须与签名数据部分中的相应 ID 一致
- 上述 signed data 上带长度前缀的 signature
- signed data(由上一组证书签名 - 如果存在上一组证书)
proof-of-rotation 结构中有多个祖先
v3
方案也无法处理两个不同密钥轮替到同一个应用的同一签名密钥的情形。这不同于收购情形,在收购情形中,收购公司希望转移收购的应用以使用其签名密钥来共享权限。收购被视为受支持的用例,因为新应用将通过其软件包名称来区分,并且可以包含自己的 proof-of-rotation
结构。不受支持的用例是,同一应用有两个不同的路径指向相同的证书,这打破了在密钥轮替设计中做出的许多假设。
2.4 验证
在 Android 9
及更高版本中,可以根据 APK
签名方案 v3、v2
或 v1
验证 APK
。较旧的平台会忽略 v3
签名而尝试验证 v2
签名,然后尝试验证 v1
签名。
APK 签名方案 v3 验证
- 找到
“APK 签名分块”
并验证以下内容:-
“APK 签名分块”
的两个大小字段包含相同的值。 -
“ZIP 中央目录结尾”
紧跟在“ZIP 中央目录”
记录后面。 -
“ZIP 中央目录结尾”
之后没有任何数据。
-
- 找到
“APK 签名分块”
中的第一个“APK 签名方案 v3 分块”
。如果v3
分块存在,则继续执行第3
步。否则,回退至使用 v2 方案验证APK
。 - 对
“APK 签名方案 v3 分块”
中的每个signer
(最低和最高 SDK 版本在当前平台的范围内)执行以下操作:
a. 从signatures
中选择安全系数最高的受支持signature algorithm ID
。安全系数排序取决于各个实现/平台版本。
b. 使用public key
并对照signed data
验证signatures
中对应的signature
。(现在可以安全地解析signed data
了。)
c. 验证签名数据中的最低和最高 SDK 版本是否与为signer
指定的版本匹配。
d. 验证digests
和signatures
中的签名算法 ID 列表(有序列表)是否相同。(这是为了防止删除/添加签名。)
e. 使用签名算法所用的同一种摘要算法计算 APK 内容的摘要。
f. 验证计算出的摘要是否与digests
中对应的digest
一致。
g. 验证certificates
中第一个certificate
的 SubjectPublicKeyInfo 是否与public key
相同。
h. 如果signer
存在 proof-of-rotation 属性,验证结构是否有效,以及此signer
是否为列表中的最后一个证书。 - 如果在当前平台范围内仅找到了一个
signer
,并且对该signer
成功执行第 3 步,则验证成功。
注意:如果第 3 步或第 4 步失败,则不得使用 v1 或 v2 方案验证 APK。
如需测试您的设备能否正确支持 v3,
请运行 cts/hostsidetests/appsecurity/src/android/appsecurity/cts/ 中
的 PkgInstallSignatureVerificationTest.java CTS 测试。
Final:
转载自:APK 签名方案 v3