今天下载Android Studio 4.2 Canary16,本意是想了解和使用一下Jetpack Compose,但是在新特新中看到支持V3和V4签名格式。瞬间有时代脱节的感觉,要不先来了解一下签名格式的变动好了。
然后在官方文档我又看到了
Android Gradle 插件 4.2 现在支持 v3 和 v4 APK 签名格式。如需在 build 中启用这两种格式之一或同时启用这两种格式,请将以下属性添加到模块级 build.gradle 或 build.gradle.kts 文件中:
??? build.gradle.kts
这是啥摸不着头脑Kotlin DSL。哎,继续了解吧。
KOTLIN
// build.gradle.kts
android {
...
signingConfigs {
config {
...
enableV3Signing(true)
enableV4Signing(true)
}
}
}
GROOVY
// build.gradle
android {
...
signingConfigs {
config {
...
enableV3Signing true
enableV4Signing true
}
}
}
Android签名的方案
- v1方案:基于JAR签名
- v2方案:Android 7.0加入
- v3方案:Android 9.0加入
一、v1签名的过程
解压一个APK会在里面看见一个META-INF的文件夹。
目录结构大致如下:
xxxxx/
|- META-INF/
| |- CERT.RSA
| |- CERT.SF
| |- MANIFEST.MF
| ......
MANIFEST.MF
Manifest-Version: 1.0
Built-By: Signflinger
Created-By: Android Gradle 4.1.1
Name: AndroidManifest.xml
SHA-256-Digest: Z7PLvLjebGnXnpd9lKFoEprfDAZblEKKT5lDH3sRDL8=
Name: DebugProbesKt.bin
SHA-256-Digest: q21I1dhoxzPNAzRfiquuPZBb1vD8ZhEXqL3+psUbknQ=
Name: META-INF/androidx.activity_activity.version
SHA-256-Digest: WYVJhIUxBN9cNT4vaBoV/HkkdC+aLkaMKa8kjc5FzgM=
文件里面储存了APK中所有Name:文件的路径和SHA-256-Digest的值
SHA-256-Digest计算方式
shasum -a 256 xxx.xx
然后计算base64的值
CERT.SF
Signature-Version: 1.0
Created-By: Android Gradle 4.1.1
SHA-256-Digest-Manifest: Wkyr/WgaCc7qm2F+eEfAQkGPoFUxZk/Y8Ix5qAmZN3w=
Name: AndroidManifest.xml
SHA-256-Digest: v5I/yMuPghplj0AMllLy3ono40APuAf1p/G28Bg6QuU=
Name: DebugProbesKt.bin
SHA-256-Digest: lGNfZfenjO/ig36HyxY3o4SsvfGGgP0dK+OSY20ejcw=
Name: META-INF/androidx.activity_activity.version
SHA-256-Digest: Yu1eiqd7wti3kPabgLC0lsO+1ns/UAhiPGUExHOxH/w=
SF文件的内容和MF文件非常相似,同样包含了APK的所以文件摘要。
区别:
- SF文件记录了整个MF文件的摘要
- SF文件其余部分记录MF相应条目的摘要
CERT.RSA
CERT.REA里面是二进制内容,里面保存了签名的证书信息,以及对CERT.SF文件的签名。
通过下面的命令可以查看证书里面的信息
$ openssl pkcs7 -inform DER -in XXX/META-INF/CERT.RSA -text -noout -print_certs
内容如下:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1715685340 (0x664347dc)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=1, ST=1, L=1, O=1, OU=11, CN=1
Validity
Not Before: Apr 7 05:55:19 2020 GMT
Not After : Apr 1 05:55:19 2045 GMT
Subject: C=1, ST=1, L=1, O=1, OU=11, CN=1
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:9d:65:5c:da:fd:50:3c:81:b7:70:45:47:bb:94:
73:07:06:cc:96:76:c8:a1:86:1d:6a:ee:f9:ff:1e:
59:1a:8d:0f:21:e1:b0:7a:c8:c3:11:32:c3:9d:6f:
85:e3:51:74:35:aa:16:df:52:97:1d:a5:c7:26:6a:
a3:96:92:e2:89:31:76:5e:62:10:2a:46:a6:70:dd:
40:48:72:cb:33:70:fc:e0:de:5c:fd:59:5c:ce:bc:
ca:c4:8a:68:da:4d:40:ac:49:1e:21:dc:a8:51:7c:
56:9b:6e:ae:93:67:2c:9d:ed:ca:2d:03:6a:19:84:
0d:7c:3d:28:b1:bf:a3:86:38:ca:cb:18:29:2d:77:
bb:6e:ee:10:70:af:e7:ff:67:36:a6:3c:dc:06:63:
b8:27:be:7f:c0:2f:98:66:00:cd:69:5f:24:7f:24:
3f:d6:21:36:7b:7c:2b:57:ea:2a:4b:8d:d4:43:bb:
ae:c7:91:56:83:3e:f8:61:fa:e8:89:76:89:ad:ca:
a8:9d:c6:d9:db:89:69:5b:6f:56:3d:7f:c9:91:51:
ee:bf:2e:76:94:5b:a8:4e:da:99:b5:bd:00:ac:ee:
c0:72:02:0d:ae:14:9c:53:30:5f:cc:55:07:1c:a6:
eb:e3:b6:b6:dc:01:1e:8a:7e:1e:01:a1:95:bb:21:
02:a5
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
E1:FD:95:4D:84:C9:20:E8:BA:17:68:42:9A:F4:B9:C3:80:1F:15:7E
Signature Algorithm: sha256WithRSAEncryption
23:09:e0:4e:85:bb:7e:ac:9b:3e:20:21:9d:7d:85:d7:f9:6b:
49:d3:02:cd:3e:13:af:bc:24:34:98:1f:a9:fc:22:7e:5a:dd:
35:60:33:e6:9b:e5:58:b4:fe:cf:84:1b:50:8c:83:b0:74:8e:
a8:44:fc:cf:20:28:20:d1:d2:76:26:d7:0f:56:29:55:34:8e:
bd:8a:84:68:09:5c:3c:da:3f:44:f7:e0:2b:17:13:e6:72:65:
b4:07:b2:e3:57:99:2a:e4:26:12:20:74:3c:21:b5:b6:f5:49:
8b:9d:6d:25:0b:e3:d0:02:10:f8:33:95:3f:e7:76:87:57:57:
30:5c:f5:1e:6b:b3:a5:2d:96:0e:1d:9a:29:2b:44:67:ae:e3:
cf:13:7f:be:c3:e6:ec:6d:e0:3d:b3:10:d4:a3:d0:6f:c1:7b:
17:16:5c:09:a7:e8:d0:aa:4c:dd:ec:7f:25:eb:f4:0d:bd:a6:
0e:20:94:7f:53:ec:89:39:06:70:3b:f2:31:45:6f:d9:30:92:
85:df:ec:9f:14:ec:d2:57:c1:45:51:f6:ec:78:71:cb:48:94:
80:ae:3e:24:a1:6f:7a:ee:dd:1d:4c:23:72:ff:89:0a:51:93:
7e:23:d0:b6:a6:f7:b2:82:b2:85:5d:f2:4a:c0:26:6b:3b:bb:
60:f9:69:9d
APK校验过程简述
- 首先校验CERT.SF文件的签名
- 校验MINIFEST.MF文件的完整性
- 校验APK中每个文件的完整性
- 校验签名的一致性(app升级时)
V1签名机制的劣势
-
签名校验速度慢
校验过程中需要对所有文件进行摘要计算,apk资源越多机器性能越差安装速度越慢
-
完整性保障不够
META-INF没有计入签名校验过程中,可以随意在这个目录中添加文件(比如可以添加渠道标识)
为了解决这些问题从Android 7.0开始,Android支持了一套新的v2签名机制。
二、V2签名机制
前言:
了解v2签名的机制之前需要先简单了解一下zip文件的组成
压缩文件数据区+压缩源文件目录区+压缩源文件目录结束标志
- 数据区(压缩文件数据区)
包含zip文件中所有文件的记录,一个列表,每条记录包含:文件名、压缩前后size、压缩后的数据等 - 中央目录(压缩源文件目录区)
存放目录信息,一个列表,每条记录包含:文件名、压缩前后size、本地文件头的起始偏移量等。通过本地文件头的起始便宜量即可找到压缩后的数据。 - 中央目录结尾记录(压缩源文件目录结束标记)
标识压缩文件的目录结尾,包含:中央目录条目数、size、起始偏移量、zip文件注释内容等。
通过压缩源文件中央目录起始偏移量可和size可以定位到中央目录,遍历文件目录条目,根据本地文件头的起始偏移量即可在数据区中找到相应的压缩数据。
2.1 V2签名原理
- jar签名:是在apk文件中添加META-INF目录,即需要修改
数据区
、中央目录
中插入数据,添加文件后会导致中央目录大小和偏移量发生变化,还需要修改目录结束标记。 - v2签名方案:在数据区和中央目录
中间
插入数据,一个APK签名分块
,从而保证原始zip(apk)数据的完整性。
v2签名块负责保护数据区、中央目录、中央目录结尾记录部分的完整性,以及
APK 签名方案 v2分块
中的singed data
分块的完整性。
APK 签名方案 v2分块
是一个签名序列,包含三个内容:
- 带长度前缀的
signed data
包含了一系列算法计算的摘要列表、证书信息,以及extra信息(可选) - 带长度前缀的
signatures序列
通过一系列算法对singed data
的签名列表。签名时使用了多个签名算法,在签名校验时吗,选择系统支持的安全系数最大的签名进行校验。 - 证书公钥
2.2 保证完整性摘要的计算过程
v2签名块负责保护数据区、中央目录、中央目录结尾记录部分的完整性,以及
APK 签名方案 v2分块
中的signed data
分块的完整性
保证完整性打的具体措施:
- 通过
内容摘要
的方式保护数据区、中央目录、中央目录结尾记录的完整性->保存在singed data
分块中 - 通过
签名
来保证signed data
分块的完整性
摘要的计算过程
- 拆封chunk
将每个部分拆分成多个大小为1MB大小的chunk,最后的可能小于1MB。分块是为了通过并行计算摘要加快计算速度。 - 计算chunk摘要
计算方式:字节0xa5
+ 块的长度(字节数)+ 块的内容 计算摘要 - 计算整体摘要
计算放肆:字节0x5a
+ chunk数 + 块的摘要的连接(按块在APK中的顺序)进行计算
注意:中央目录结尾记录
中包含了中央目录
的起始偏移量,插入APK签名分块
后,中央目录
的起始偏移量将发生变化。故在校验签名计算摘要时,需要把中央目录
的起始偏移量当做APK签名分块
的起始偏移量。
v2验证过程
- 找到
APK签名分块
并验证以下内容:
a.APK签名分块
的两个大小字段包含相同的值。
b.ZIP中央目录结尾
紧跟在ZIP中央目录记录
后面
c.ZIP中央目录结尾
之后没有任何数据 - 找到
APK签名分块
中的第一个APK签名方案v2分块
。
存在v2分块则执行第三步,否则使用v1方案验证APK - 对
APK签名方案v2分块
中的每个signer执行以下操作:
a. 从signatures中选择安全系数最高的受支持signature algorithm ID.安全系数排序取决于各个实现/平台版本。
b. 使用public key并对照signed data
验证signatures中对应的signature。
c. 验证digests和signatures中的签名算法ID列表(有序列表)是否相同,防止删除/添加签名。
d. 使用签名算法所用的同一种摘要算法计算APK内容的摘要。
e. 验证计算出的摘要是否与digests中对应的digest相同。
f. 验证certificates中第一个certificate中对应的digest相同. - 如果找到了至少一个signer,并且对于每个找到的signer,第3步都取得了成功,APK验证将会成功。
兼容机制
v2签名机制是在Android 7.0引入的,为了使用v2签名的APK可以在Android 7.0以下版本中安装,应该先用JAR签名对APK进行签名,再用v2方案进行签名。注意先用JAR签名再v2签名,因为JAR签名会修改zip文件数据区和中央目录的内容破坏v2签名的完整性。
防回滚机制
- v2签名的APK中同时带有JAR签名,攻击者可能直接删除APK的v2签名吗,使系统只校验JAR签名。为了防止此类攻击,v2方案规定。
措施:
v2签名的APK如果还带JAR签名,META-INF/.SF文件的首部必须包含X-Android-APK-Signed属性。
[图片上传失败...(image-4d0a01-1606978403300)]
- 攻击者还肯能试图删除APK签名方案v2分块中安全系数较高的签名,使系统验证安全系数较低的签名。
措施:
对APK进行签名时使用的签名算法ID列表会储存在
signed data
分块中。
v2签名的优势
- 只需进行一次hash校验,速度快
- 保证了APK内个文件的完整性
三、v3签名机制
3.1 简述
Android 9.0中加入了v3签名方案,新增了对APK Signature Scheme v3的支持。
使应用能够在APK更新过程中更改其签名秘钥。
为了实现轮替,APK必须指示新旧签名秘钥之间的信任级别。为了支持秘钥轮替,APK签名方案从v2更新为v3,以允许使用新旧秘钥。
v3在APK签名分块中添加了有关受支持的SDK版本和proof-of-rotation结构的信息。
官方提示:
注意:在 Android 11 发布之前,建议不要使用 APK 密钥轮替。开发者可以选择针对新安装应用使用 Google Play 的密钥升级。注册了 Play 应用签名的开发者可以通过 Play 管理中心请求升级。
轮替签名密钥
轮替签名证书世系或新签名序列的语法如下:
$ apksigner rotate --in /path/to/existing/lineage \
--out /path/to/new/file \
--old-signer --ks old-signer-jks \
--new-signer --ks new-signer-jks
3.2 v3分块
v3方案的设计与v2方案非常相似,采用相同的常规格式,并支持相同的签名算法ID、秘钥的大小和EC曲线。
区别:
v3方案增添了有关受支持的SDK版本和profo-of-rotation结构的信息。
Proof-of-rotation和self-trusted-certs结构
proof-of-rotation结构允许应用轮替其签名证书,而不会使这些证书在与这些应用通信的其他应用上被屏蔽。
为此应用签名需要包含两个新数据块:
- 告知第三方应用的签名证书可信(只要其先前证书可信)的断言
- 应用的旧签名证书(应用本身仍信任这些证书)
签名部分中的proof-of-rotation属性包含一个单链表,其中每个节点都包含用于为之前版本的应用签名的签名证书。此属性旨在包含概念性proof-of-rotation和self-trusted-old-certs数据结构。
单链表按照版本排序,最旧的签名证书对应于根节点。
在构建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 形式)
- 带长度前缀的 X.509
-
minSDK
(uint32) - 如果平台版本低于此数字,应忽略该签名者。 -
maxSDK
(uint32) - 如果平台版本高于此数字,应忽略该签名者。 - 带长度前缀的
additional attributes
(带长度前缀)序列:-
ID
(uint32) -
value
(可变长度:附加属性的长度 - 4 个字节) ID - 0x3ba06f8c
-
value -
Proof-of-rotation 结构
-
-
minSDK
(uint32) - 签名数据部分中 minSDK 值的副本 - 用于在当前平台不在相应范围内时跳过对此签名的验证。必须与签名数据值匹配。 -
maxSDK
(uint32) - 签名数据部分中 maxSDK 值的副本 - 用于在当前平台不在相应范围内时跳过对此签名的验证。必须与签名数据值匹配。 - 带长度前缀的
signatures
(带长度前缀)序列:-
signature algorithm ID
(uint32) -
signed data
上带长度前缀的signature
-
- 带长度前缀的
public key
(SubjectPublicKeyInfo,ASN.1 DER 形式)
- 带长度前缀的
v3验证
在Android 9.0及更高版本中,可以根据APK签名方案v3、v2或v1验证APK。比较旧的平台会忽略v3签名而尝试验证v2,然后尝试验证v1签名。
- 找到APK签名分块并验证一下内容
a.APK签名分块
的两个大小字段包含相同的值
b.ZIP中央目录结尾
紧跟在ZIP中央目录记录
后面
c.ZIP中央目录结尾
之后没有任何数据 - 找到
APK签名分块
中的第一个APK 签名方案 v3分块
。如果存在v3分块,则继续执行第3步。否则,回退至使用v2方案验证APK。 - 对
APK签名方案 v3分块
中的每个signer
(minSDK和maxSDK范围内)执行以下操作:
a. 从signatures
中选择安全系数最高的受支持signature algorithm ID
。安全系数取决于各个实现/平台版本。
b. 使用public key
并对照signed data
验证signatures
中对应。
c. 验证签名数据中的最低和最高SDK版本是否与为signer
指定的版本匹配。
d. 验证digests
和signatures
中的签名算法ID列表是否相同(防止删除/添加签名。)
e. 使用签名算法所用的同一种摘要算法计算APK内容的摘要。
f. 验证计算出的摘要是否与digests
中对应的digest
一致。
g. 验证certificates
中第一个certificate
的SubjectPublicKeyInfo是否与public key
相同。
h. 如果signer
存在proof-of-rotation属性,验证结构是否有效,以及此singer
是否为列表中的最后一个证书。 - 如果在当前平台范围内仅找到了一个
signer
,并且对该signer
成功执行第3步,则验证成功。
v3签名的优势
- v3签名支持秘钥轮替,可最大限度减少秘钥丢失的影响。
v4签名
Android 11 添加了对
APK 签名方案 v4
的支持。
此方案会在单独的文件(apk-name.apk.idsing
)中生成一种新的签名,但在其他方面与v2和v3类似。没有对APK进行任何更改。此方案支持ADB 增量APK安装
,这样会加快APK安装速度。
优势:
- 通过APK v4签名,可以使用Android 11中的ADB 增量APK安装快速部署大型APK。