签名
V1签名
签名原理
对一个APK文件签名之后,APK文件根目录下会增加META-INF目录,该目录下增加三个文件:
MANIFEST.MF
NETEASE.RSA
NETEASE.SF
其中.RSA文件还可能是.DSA文件,RSA与SF文件的文件名可以更改,但是它们的命名必须一样。
MANIFEST.MF中保存了APK里所有文件的SHA1校验值的BASE64编码,格式如下(一个文件对应一条记录):
Name: res/anim/abc_fade_in.xml
SHA1-Digest: ohPEA4mboaFUu9LZMUwk7FmjbPI=
Name: res/anim/abc_fade_out.xml
SHA1-Digest: MTJWZc22b5LNeBboqBhxcQh5xHQ=
SF文件里保存了MANIFEST.MF文件的SHA1校验值的BASE64编码,同时还保存了MANIFEST.MF中每一条记录的SHA1检验值BASE64编码,格式如下:
SHA1-Digest-Manifest: ZRhh1HuaoEKMn6o21W1as0sMlaU=
Name: res/anim/abc_fade_in.xml
SHA1-Digest: wE1QEZhFkLBWMw4TRtxPdsiMRtA=
Name: res/anim/abc_fade_out.xml
SHA1-Digest: MfCV1efdxSKtesRMF81I08Zyvvo=
RSA文件则包含了签名的公钥、签名所有者等信息,还保存了用SHA1withRSA签名算法对SF文件的签名结果信息。
Android系统就是根据这三个文件的内容对APK文件进行签名检验的。
签名方法
apksign
一般的签名过程可以由apksign.jar或者jarsinger.jar完成。apksign.jar由Android SDK提供,使用方法如下:
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
它接受一个PEM公钥文件,PK8私钥文件,对update.apk进行签名,签名后的文件保存到update_signed.apk。
jarsinger
jarsinger是由JDK提供,使用方法如下:
jarsigner -verbose -keystore d:\\debug.keystore -signedjar update_signed.apk update.apk androiddebugkey -digestalg SHA1 -sigalg MD5withRSA -keypass android -storepass android
V2签名
Android 7.0(Nougat)引入一项新的应用签名方案APK Signature Scheme v2(简称V2),它是一个对全文件进行签名的方案,能提供更快的应用安装时间、对未授权APK文件的更改提供更多保护,在默认情况下,Android Gradle 2.2.0插件会使用APK Signature Scheme v2和传统签名方案来签署应用。
目前该方案并不是强制方案,在build.gradle
添加v2SigningEnabled false
即可使用V1方案来签署应用,如下所示:
android {
...
defaultConfig { ... }
signingConfigs {
release {
storeFile file("releasekey.keystore")
storePassword "password"
keyAlias "ReleaseKeyAlias"
keyPassword "KeyPassword"
v2SigningEnabled false
}
}
}
使用V2签名进行签署的同时也会对APK进行V1签名,这样就有着比较好的兼容性,能完全兼容低于Android 7.0(Nougat)的版本。对比旧签名方案,它有更快的验证速度和更安全的保护,因此新的应用签名方案可能会被采纳成一个强制配置。
签名原理
V2签名除了V1签名的功能外,还包含以下工作,下图是V2签名后和未签名apk文件的一个对比:
V2签名方案会在ZIP文件格式的Central Directory
区块所在文件位置的前面添加一个APK Signing Block区块,下面按照ZIP文件的格式来分析V2签名方案签名后的APK包。
整个APK(ZIP文件格式)会被分为以下四个区块: 1. Contents of ZIP entries(from offset 0 until the start of APK Signing Block) 2. APK Signing Block 3. ZIP Central Directory 4. ZIP End of Central Directory
V2签名方案的签名信息会被保存在区块2(APK Signing Block)中, 而区块1(Contents of ZIP entries
)、区块3(ZIP Central Directory
)、区块4(ZIP End of Central Directory
)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。
V3签名
Android 9 支持APK 密钥轮转,这使应用能够在 APK 更新过程中更改其签名密钥。为了实现轮转,APK 必须指示新旧签名密钥之间的信任级别。为了支持密钥轮转,Google将APK 签名方案从 v2 更新为 v3,以允许使用新旧密钥。v3 方案的设计与v2 方案非常相似,采用相同的常规格式,并支持相同的签名算法 ID、密钥大小和 EC 曲线。v3 在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 结构的信息。
SDK版本
V3签名中会保存minSDK与maxSDK版本,如果平台版本不符合minSDK与maxSDK的要求则拒绝安装。
proof-of-rotation
proof-of rotation 结构允许应用轮转其签名证书,就是当我们的签名证书快过期或者需要更换时,打包时提前将新证书打包信息打包进apk文件中,后续版本升级时即可使用新证书签名。
验证
在 Android 9 及更高版本中,可以根据 APK 签名方案 v3、v2 方案或 v1 方案验证 APK。较旧的平台会忽略 v3 签名并尝试验证 v2 签名,然后验证 v1。
渠道包方案
APK文件追加末尾内容方案
android 5.0之前可以直接往apk的后边追加数据,由于这个方法太鲁莽,直接往 apk 文件后边追加数据也不安全。在android 5.0 后开始校验 apk 数据格式合法性了。所以这种粗鲁的办法不能用了。
ZIP File Comment 区域写入方案
从第一张图中我们了解到ZIP文件分为了3块分别是:Contents of ZIP entries(压缩的文件内容源数据
)、ZIP Central Directory(压缩的目录源数据
)、ZIP End of Central Directory(目录结束标识结构
)。这里我们只关心最后一块目录结束标识结构,目录结束标识存在于整个归档包的结尾,用于标记压缩的目录数据的结束。结构如下:
Offset | Bytes | Description | 译 |
---|---|---|---|
0 | 4 | End of centraldirectory signature =0x06054b50 | 核心目录结束标记(0x06054b50) |
4 | 2 | Number of this disk | 当前磁盘编号 |
6 | 2 | Disk where central directory starts | 核心目录开始位置的磁盘编号 |
8 | 2 | Number of central directory records on this disk | 该磁盘上所记录的核心目录数量 |
10 | 2 | Total number of central directory records | 核心目录结构总数 |
12 | 4 | Size of central directory (bytes) | 核心目录的大小 |
16 | 4 | Offset of start of central directory, relative to start of archive | 核心目录开始位置相对于archive开始的位移 |
20 | 2 | Comment length (n) | 注释长度 |
22 | n | Comment | 注释内容 |
apk文件默认情况下没有comment,所以 comment length的short 两个字节为 0,我们需要把这个值修改为我们的comment的长度,然后把comment追加到后边即可。
追加comment会导致区块4(ZIP End of Central Directory
)产生变化,上文中也说道V2签名方案的签名信息会被保存在区块2(APK Signing Block)中, 而区块1(Contents of ZIP entries
)、区块3(ZIP Central Directory
)、区块4(ZIP End of Central Directory
)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。因此此方案在V2签名上不可用。
META-INF目录下添加空文件方案
V1签名后apk文件多了META-INF这个文件夹,里面包含了三个文件,MANIFEST.MF、CERT.SF、CERT.RSA
MANIFEST.MF
MANIFEST.MF文件列出了apk的所有文件,以及这些文件内容所对应的base64-encoded SHA1 哈希值,例如,
Name: AndroidManifest.xml
SHA1-Digest: 7lLs5fV2H4ttapcDEdtJRTQOzpk=
上述表示AndroidManifest.xml这个文件的SHA1的哈希值为7lLs5fV2H4ttapcDEdtJRTQOzpk=
CERT.SF
CERT.SF和MANIFEST.MF很相似,但是它描述的不是文件内容的hash值,而是列出了MANIFEST.MF这个文件中每条信息的hash值,举例会明白些:
Name: AndroidManifest.xml
SHA1-Digest-Manifest: 8CVc0D8U2qQKRD+7Fw7+Jmb6Qos=
上面这条hash值‘8CVc0D8U2qQKRD+7Fw7+Jmb6Qos=’对应的是MANIFEST.MF中下面这几行字符串的hash值
Name: AndroidManifest.xml
SHA1-Digest: 7lLs5fV2H4ttapcDEdtJRTQOzpk=
CERT.RSA
这个文件里面其实包含了对CERT.SF文件的数字签名以及签名时所用数字证书公钥等数字证书的信息。
在安装APK时,会先检查CERT.SF文件,确认签名正确,而后检查SF文件,再通过SF去检查apk文件下各个文件,这样一步步环环相扣确保了每个文件都是有效的,确保了文件的完整和安全性,但是对于好事者还是发现了其中的漏洞,美团有个方案就是通过在META-INF下添加一个空文件来代表渠道,这个空文件不会被检查,他和任何文件没有关联(CERT.SF、CERT.RSA和MANIFEST.MF他们是相关关联的)。
添加空文件会导致apk文件区块1(Contents of ZIP entries
) 产生变化,上文中也说道V2签名方案的签名信息会被保存在区块2(APK Signing Block)中, 而区块1(Contents of ZIP entries
)、区块3(ZIP Central Directory
)、区块4(ZIP End of Central Directory
)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。因此此方案在V2签名上不可用。
Flavor方案
配置productFlavors的方式替换AndroidManifest文件,首先在AndroidManifest.xml
文件中定义一个meta-data
<meta-data
android:name="CHANNEL"
android:value="${CHANNEL_VALUE}" />
然后在gradle文件中设置一下productFlavors
android {
productFlavors {
huawei {
manifestPlaceholders = [CHANNEL_VALUE: "1"]
}
xiaomi {
manifestPlaceholders = [CHANNEL_VALUE: "2"]
}
oppo {
manifestPlaceholders = [CHANNEL_VALUE: "3"]
}
vivo {
manifestPlaceholders = [CHANNEL_VALUE: "4"]
}
}
}
然后执行gradle任务即可打出全部配置的渠道包,由于每个渠道都会导致一些gradle任务重新执行,然后再重新生成apk文件再签名,因此这种方案是最慢的。
区块2扩展方案
上面说过,V2签名后的4个区块,1、3、4都会被保护,而区块2签名信息区块缺不受保护,于是就有好事者发现了其中漏洞,那么我们先看看区块2的格式是什么样的吧。
Offset | Bytes | Description |
---|---|---|
0 | 8 | 这个Block的长度,以字节数(不含此字段) |
8 | n | 一组ID-value |
-24 | 8 | 这个Block的长度(和第一个字段一样值) |
-16 | 16 | 魔数 “APK Sig Block 42” |
我们重点来看一下这个ID-value,区块2中包含多个“ID-值”对,所采用的封装方式有助于更轻松地在 APK 中找到该分块。APK 的 v2 签名会存储为一个“ID-值”对,其中 ID 为 0x7109871a。在APK安装时会解译该分块,Android会忽略 ID 未知的“ID-值”对。
那么在区块2中增加一个”ID-值“对即可实现快速的渠道包生成,这就是美团walle的渠道包方案。
修改内置文件二次签名方案
APK文件中放置一个文件写入渠道号,通过脚本修改渠道号文件内容,然后通过官方签名工具(apksigner、jarsigner)进行签名,此种方案是比较简单、非常安全当然速度也比较慢。
总结
首先看一下各个签名方案的对比:
APK文件追加 | Comment写入 | META-INF空文件 | Flavor配置 | 区块2 | 二次签名 | |
---|---|---|---|---|---|---|
是否支持V1 | 是 | 是 | 是 | 是 | 是 | 是 |
是否支持V2 | 否 | 否 | 否 | 是 | 是 | 是 |
是否支持V3 | 否 | 否 | 否 | 是 | 是 | 是 |
速度 | 快 | 快 | 快 | 慢 | 快 | 中 |
安全性 | 不安全 | 不安全 | 不安全 | 安全 | 一般 | 安全 |
每一种方案都有其优点与缺点,随着V2与V3签名的普及,后面渠道包的方案可能会越来越少,当然这也有利有弊,好处就是我们的apk被修改渠道植入后门的风险降低了,那么坏处就是对于渠道太多的APP来说每一次发版都需要喝上几杯咖啡的时间来打渠道包。
参考
https://source.android.google.cn/security/apksigning/v2
https://source.android.google.cn/security/apksigning/v3
https://tech.meituan.com/2017/01/13/android-apk-v2-signature-scheme.html
https://tech.meituan.com/2014/06/13/mt-apk-packaging.html
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。