Android APK签名机制
应用签名主要是避免外部恶意解压、破解或者反编译修改内容,签名的本质是:
认证:Android 平台上运行的每个应用都必须有开发者的签名。在安装应用时,软件包管理器会验证 APK 是否已经过适当签名,安装程序会拒绝没有获得签名就尝试安装应用。
验证完整性:软件包管理器在安装应用前会验证应用摘要,如果破解者修改了 apk 里的内容,那么摘要就不再匹配,验证失败。
image(1)应用签名方案类型
截止到Android12,Android支持三种应用签名方案:
v1:基于jar签名v2:提高验证性能&覆盖范围(Android 7.0 Nougat引入)v3:支持密钥轮换(Android 9.0 Pie引入)
为了提高兼容性,必须按照v1,v2,v3的先后顺序采用签名方案,低版本平台会忽略高版本的签名方案在APK中添加额外数据,具体流程图如下:
image<1>签名方案v1
最基本的签名方案,是基于Jar的签名。
v1签名后会增加META-INF文件夹,其中会有如下三个文件:
imagev1签名流程:
image① 计算每个文件的 SHA-1 摘要,进行 BASE64 编码后写入摘要文件,即 MANIFEST.MF 文件;
② 计算整个 MANIFEST.MF 文件的 SHA-1 摘要,进行 BASE64 编码后写入签名文件,即*.SF 文件;
③ 计算 MANIFEST.MF 文件中每一块摘要的 SHA-1 摘要,进行 BASE64 编码后写入 签名文件,即*.SF 文件;
④ 计算整个 *.SF 文件的数字签名(先摘要再私钥加密);
⑤ 将数字签名和 X.509 开发者数字证书(公钥)写入 *.RSA 文件;
验证流程:
image主要包括验证签名、校验完整性两个步骤:
步骤1:验证签名步骤
① 取出*.RSA 中包含的开发者证书,并校验其合法性
② 用证书中的公钥解密*.RSA中包含的签名
③ 用证书中的公钥计算*.SF的签名
④ 对比(2)和(3)的签名是否一致
步骤2:验证完整性
① 检查 APK 中包含的所有文件,对应的摘要值与 MANIFEST.MF 文件中记录的值一致
② 使用证书文件(RSA 文件)检验签名文件(SF 文件)没有被修改过
③ 使用签名文件(SF 文件)检验 MF 文件没有被修改过
上面任何一个步骤验证失败,则整个APK验证失败。
问题:
覆盖范围不足:Zip 文件中部分内容不在验证范围,例如 META-INF 文件夹;
验证性能差:验证程序必须解压所有压缩的条目,这需要花费更多时间和内存
存在Janus漏洞:恶意开发人员可以通过Janus漏洞去绕过Android 的v1签名验证机制。
<2>签名方案v2
Android7.0 中开始引入了APK签名方案v2,一种全文件签名方案,该方案能够发现对APK的受保护部分进行所有更改,相比v1来说校验速度更快,覆盖的范围也更广。但是考虑到版本兼容的问题,所以一般常见了v1+v2的混合签名模式。
我们由上文知道Zip文件主体分为:源文件数据存储区、中心目录区、中心目录结束标识。EoCD中记录了中央目录的起始位置,在源文件数据存储区和中心目录区插入其他数据不会影响Zip的解压。
因此v2签名后会在源文件数据存储区和中心目录区插入APK 签名分块(APK Signing Block)。
如下图所示。从左到右边,我们定义为区块 1~4。
imagev2签名块(APK Signing Block)本身又主要分成三部分:
SignerData(签名者数据):主要包括签名者的证书,整个APK完整性校验hash,以及一些必要信息。
Signature(签名):开发者对SignerData部分数据的签名数据。
PublicKey(公钥):用于验签的公钥数据。
签名流程:
相比v1签名方案,v2签名方案不再以文件为单位计算摘要,而是以1MB为单位将文件拆分为多个连续的快(chunk),每个分区的最后一个快可能会小于1MB。v2签名流程如下:
image① 对区块 1、3、4,按照 1MB 大小分割为多个块(chunk)
② 计算每个块的摘要
③ 计算(2)中所有摘要的签名
④ 添加X.509开发者数字证书(公钥)
验证流程:
image因为v2签名机制是在Android 7.0上版本才支持,因此对于Android 7.0以及以上版本,在安装过程中,如果v2 签名块,则必须走 v2 签名机制,不能绕过。否则降级走 v1 签名机制。
v1 和 v2 签名机制是可以同时存在的,其中对于 v1 和 v2 版本同时存在的时候,v1 版本的 META_INF 的 .SF 文件属性当中有一个 X-Android-APK-Signed 属性:
X-Android-APK-Signed: 2
v2签名本身的验证过程:
image① 利用PublicKey解密Signature,得到SignerData的hash明文。
② 计算SignerData的hash值。
③ 两个值进行比较,如果相同则认为APK没有被修改过,解析出SignerData中的证书。否则安装失败。
④ 如果是第一次安装,直接将证书保存在应用信息中。
⑤ 如果是更新安装,即设备中原来存在这个应用,验证之前的证书是否与本次解析的证书相同。若相同,则安装成功,否则失败。
<3>签名方案v3
Android 9.0中引入了新的签名方式v3,v3签名在v2的基础上,仍然采用检查整个压缩包的校验方式。不同的是在签名部分增可以添加新的证书(Attr块)。在这个新块中,会记录我们之前的签名信息以及新的签名信息, 支持密钥轮换,即以密钥转轮的方案,来做签名的替换和升级。这意味着,只要旧签名证书在手,应用能够在 APK 更新过程中更改其签名密钥。
v3 签名新增的新块(attr)存储了所有的签名信息,由更小的 Level 块,以链表的形式存储。
签名流程:
v3版本签名块也分成同样的三部分,与v2不同的是在SignerData部分,v3新增了attr块,其中是由更小的level块组成。每个level块中可以存储一个证书信息。前一个level块证书验证下一个level证书,以此类推。最后一个level块的证书,要符合SignerData中本身的证书,即用来签名整个APK的公钥所属于的证书。从v2到v3的过渡:
image签名校验:
Android 的签名方案的升级都需要确保向下兼容。因此,在引入 v3 方案后会根据 APK 签名方案,v3 -> v2 -> v1 依次尝试验证 APK。而较旧的平台会忽略 v3 签名并尝试 v2 签名,最后才去验证 v1 签名。如下图所示:
image注意:对于覆盖安装的情况,签名校验只支持升级而不支持降级。即一个使用 V1 签名的 Apk,可以使用 V2 签名的 Apk 进行覆盖安装,反之则不允许。
v3签名自身的校验:
image① 利用PublicKey解密Signature,得到SignerData的hash明文。
② 计算SignerData的hash值。
③ 两个值进行比较,如果相同则认为APK没有被修改过,解析出SignerData中的证书。否则安装失败。
④ 逐个解析出level块证书并验证,并保存为这个应用的历史证书。
⑤ 如果是第一次安装,直接将证书与历史证书一并保存在应用信息中。
⑥ 如果是更新安装,验证之前的证书与历史证书,是否与本次解析的证书或者历史证书中存在相同的证书,其中任意一个证书符合即可安装。
<4>三种签名的比较和校验时机
v2、v3的比较如下图所示:
image- v1签名方案:基于 Jar 的签名方案,但存在的问题:完整性覆盖范围不足 & 验证性能差。
- v2签名方案:通过条目内容区、中央目录区之间插入APK 签名分块(APK Signing Block)对v1签名进行了优化。
- v3签名方案:支持密钥轮换,新增的新块(attr)存储了所有的签名信息,对v2签名进行了优化。
验证签名的时机主要要了解Android安装应用的方式:
系统应用安装:开机时完成,没有安装界面。
网络下载的应用安装:通过市场应用完成,没有安装界面。
ADB工具安装:没有安装界面。
第三方应用安装:通过packageinstall.apk应用安装,有安装界面。
但是其实无论通过哪种方式安装都要通过PackageManagerService来完成安装的主要工作,最终在PMS中会去验证签名信息,如v3验证方式一样。
image以上就是Android程序员在apk开发中的签名学习;关于更多Android安全性能优化及进阶技术可以参考:《Android核心技术手册》里面记录了三十几个技术板块。
最后
APK签名可以带来以下好处
- 应用程序升级 如果想无缝升级一个应用,Android系统要求应用程序的新版本与老版本具有相同的签名与包名。若包名相同而签名不同,系统会拒绝安装新版应用。
- 应用程序模块化 Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序。此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块。
- 代码或数据共享 Android提供了基于签名的权限机制,一个应用程序可以为另一个以相同证书签名的应用程序公开自己的功能与数据,同时其它具有不同签名的应用程序不可访问相应的功能与数据。
- 应用程序的可认定性 签名信息中包含有开发者信息,在一定程度上可以防止应用被伪造。例如网易云加密对Android APK加壳保护中使用的“校验签名(防二次打包)”功能就是利用了这一点。
网友评论