小白训练营|Android应用签名介绍
1、签名的原理
对应用进行签名,可以标识应用的创作者,防止应用被伪造、冒充、篡改等问题。Android 系统要求所有APK必须先使用证书进行数字签名,然后才能安装到设备上。
在介绍Android签名机制之前,我们先简要概述一下数字签名的原理
首先需要将签名的文件计算出摘要信息,摘要信息是通过单向哈希算法计算出来的,安全的哈希算法无法找到两个不同文件计算出来的摘要值相同(虽然理论上是存在的),由此可通过比较摘要值的不同来反向推导源文件是否相同,即可以用来验证文件的完整性。
数字签名就是将摘要信息使用非对称加密算法(如RSA)的私钥进行加密,与被签名的文件一起发送给接收者,接收者用发送者的公钥解密被加密的摘要信息,再用相同的哈希算法计算出一个摘要信息,两者进行比对,如果相同,则说明文件没有被篡改。
由于非对称加密算法的私钥不在网络中传输,很难被获取到,因此使用数字签名技术对文件进行完整性校验是非常可信的。
签名和验签流程
(图片来自维基百科)
但是,这里也是存在风险的,就是你获取的公钥必须是真的发送者的公钥才行。否则如果接收到的是攻击者的公钥,再接收到攻击者伪装成发送者发送的文件,则数字签名验证也是通过的。
Q:那如何解决这个问题呢?这时就需要找一个大家都信任的第三方来签发证书,这个第三方就是CA(Certificate Authentication)机构,CA机构会给使用者颁发数字证书,证书中包含证书所有人的公钥信息、所有人的名称、证书的有效期、证书所使用的签名算法、CA机构对证书的数字签名等。
2、签名的作用
Android签名的作用除了可以防止APK包被篡改之外,还具有以下作用:
1防止其他应用(不同签名)使用相同的包名覆盖安装原来的应用,根据这个特性也可以用来对应用进行升级,相同包名的应用必须有相同的签名才能覆盖安装
2
应用还可以在“signature”保护级别声明安全权限,以便只有使用同一个密钥签名的应用可以获得此权限,同时让这些应用可以各自维持单独的 UID 和应用沙盒;
3
应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序,此时就可以把我们的应用程序以模块的方式进行部署。
4
代码或者数据共享:Android提供了基于签名的权限机制,一个应用程序可以为另一个以相同证书签名的应用程序公开自己的功能。
3、Android签名演进
Android签名发展到现在一共经历了3个版本:v1方案、v2方案和v3方案。
v1方案(JAR签名)
由于apk和jar文件都是基于ZIP的文件格式,v1方案直接使用了jar包的签名方案。
在对应用签名后,apk包中会增加一个名为META-INF的文件夹,里面包含3个文件:MANIFEST.MF、CERT.SF和CERT.RSA。
MANIFEST.MF:该文件中保存的内容其实就是逐一遍历 APK 中的所有条目,如果是目录就跳过,如果是一个文件,就用 SHA1(或者 SHA256)消息摘要算法提取出该文件的摘要然后进行 BASE64 编码,作为
“SHA1-Digest”属性的值写入到 MANIFEST.MF 文件中的一个块中。该块有一个“Name”属性, 其值就是该文件在 APK 包中的路径,如下图所示:
CERT.SF:首先对MANIFEST.MF文件计算消息摘要,然后再用 Base64 编码。然后对 MANIFEST.MF 的各个条目做 SHA1(或者 SHA256)后再用 Base64 编码,如下图所示:
CERT.RSA:把CERT.SF文件用私钥计算出签名,然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。
APK包在安装时会执行签名的验证过程,验证过程大致分三步:
第一步:首先检查是否有签名,然后根据CERT.RSA文件中包含的公钥对数字签名解密,验证CERT.SF没有被篡改过。
第二步:使用签名文件CERT.SF校验MANIFEST.MF文件没有被篡改过。
第三步:使用MANIFEST.MF文件校验所有文件没有被篡改过。
v1签名不保护 APK 的某些部分,例如META-INF 文件夹。验证程序需要处理大量不可信(尚未经过验证)的数据结构,然后会舍弃不受签名保护的数据,会增大攻击面。此外,验证程序必须解压所有已压缩的条目,需要花费较多时间和内存。为了解决这些问题,Android 7.0 中引入了v2签名方案。
v2方案
从Android 7.0开始,Android支持了v2签名方案,该方案是一种全文件签名方案,会对 APK 的内容进行哈希计算和签名,然后将生成的APK签名分块插入到APK中。该方案能够发现对APK受保护部分进行的所有更改,有助于提高验证速度并增强完整性保护。
原理:
使用v2方案进行签名时,会在APK文件中插入一个签名块,该签名块插入位置如下图红色标识所示。签名、摘要、签名算法和签名者身份信息等都会存储在该签名块中。因此使用v2方案签名后的APK包含4个部分:
1. ZIP条目内容
2. APK签名块
3. ZIP中央目录
4. ZIP中央目录结尾
v2签名方案负责保护第 1、3、4 部分的完整性,以及第2部分中的 signed data 分块的完整性。
v2签名验证机制
v3方案
Android 9支持 APK密钥轮替,这使得应用能够在APK更新过程中更改其签名密钥。为了支持密钥轮替,谷歌将 APK签名方案从v2更新为v3,以允许使用新旧密钥。
v3方案可以说是v2方案的升级版,APK签名分块格式相同,v3在分块中添加了支持的 SDK 版本和proof-of-rotation结构信息。
proof-of-rotation 结构允许应用轮替其签名证书,而不会使这些证书在与这些应用通信的其他应用上被屏蔽。为此,应用签名需包含两个新数据块:
(1) 告知第三方应用的签名证书可信(只要其先前证书可信)的断言
(2)应用的旧签名证书(应用本身仍信任这些证书)
签名数据部分中的 proof-of-rotation 属性包含一个单链表,其中每个节点都包含用于为之前版本的应用签名的签名证书。该单链表按版本排序,最旧的签名证书对应于根节点。在构建 proof-of-rotation 数据结构时,系统会让每个节点中的证书为列表中的下一个证书签名,从而为每个新密钥提供证据来证明它应该与旧密钥一样可信。
在Android 9及更高版本中,可以根据 APK 签名方案 v3、v2 或 v1 验证 APK。较旧的平台会忽略 v3 签名而尝试验证 v2 签名,然后尝试验证 v1 签名。其验证流程如下:
v3签名验证机制
4、Android签名的安全风险
谷歌在设计Android应用签名时做了简化,去掉了CA验证,提供了使用自签名证书进行代码签名的功能,开发者无需外部协助或许可即可生成自签名证书,然后对自己的应用签名,这样在安装时,系统会认为某个包名的应用第一次安装时的证书就是合法的。因此,安卓签名只能验证应用程序的完整性,并不能验证发布者的真实身份,这也为Android应用存在二次打包、盗版、破解、篡改功能等提供了基础。
v1签名方案出现多起漏洞导致签名保护失效:
1、Janus漏洞(CVE-2017-13156))
该漏洞可以让攻击者绕过v1签名机制,在不改变原签名的情况下任意修改APP中的代码逻辑。由于安卓签名机制是其他安全机制的基础,所以该漏洞影响非常大。受该漏洞影响的版本为:Android系统5.1.1 - 8.0。
利用过程:
1、攻击者可以向APK文件的开始位置放置一个攻击的DEX文件A;
2、安卓系统在安装时用ZIP的读取机制从末尾开始进行文件的读取,读取到了原始的APK内容,并且以V1的方式进行校验,认为这个文件是正常的,没有篡改,APK安装成功;
3、在运行时,Android的ART虚拟机从文件头开始读取,发现是一个DEX文件,直接执行,攻击文件A被最终执行。
2、其他“MasterKey”系列漏洞
在2013年Black Hat上,Bluebox的安全团队公布了一个“MasterKey”漏洞。该漏洞影响包括当时最新的安卓6.0系统及以下所有系统。
具体漏洞原理是基于APK(ZIP文件格式)里面的多个ZipEntry实现的,具体如下:
1)向原始的App APK的前部添加一个攻击的classes.dex文件(A);
2)安卓系统在校验时计算了A文件的hash值,并以“classes.dex”字符串作为key保存;
3) 然后安卓计算原始的classes.dex文件(B),并再次以”classes.dex”字符串做为key保存,这次保存会覆盖掉A文件的hash值,导致Android系统认为APK没有被修改,完成安装;
4.)APK程序运行时,系统优先以先找到的A文件执行,忽略了B文件,导致漏洞的产生。
该漏洞爆出没多久,其他安全团队又发现了“bug 9695860”和“bug 9950697”漏洞。
以上的一系列漏洞全部出在v1签名方案,谷歌也意识到了这套机制的缺陷,所以,发布了重新设计的V2签名机制。
参考链接:
https://source.android.com/security/apksigning
https://zhuanlan.zhihu.com/p/89126018
https://blog.fengsq.com/post/ApkSignature.html
https://blog.csdn.net/heppyshow_myself/article/details/108283602
https://upload.wikimedia.org/wikipedia/commons/2/2b/Digital_Signature_diagram.svg