APK签名方案v1、v2和v3详解
APK签名方案v1、v2和v3详解
在Android应用开发中,应用签名是一个非常重要的环节。它不仅可以标识应用的创作者,还可以确保应用的完整性和安全性。本文将详细介绍Android应用的三种签名方案(v1、v2和v3),帮助开发者更好地理解应用签名机制。
APK签名方案概述
Android支持以下三种应用签名方案:
- v1方案:基于JAR签名
- v2方案:APK签名方案v2(在Android 7.0中引入)
- v3方案:APK签名方案v3(在Android 9中引入)
为了最大限度地提高兼容性,建议按照v1、v2、v3的先后顺序对应用进行签名。对于Android 7.0及更高版本的设备,使用v2+方案签名的应用可以更快地安装。而更低版本的Android平台会忽略v2+签名,因此应用需要包含v1签名。
JAR签名(v1方案)
JAR签名是Android应用签名的传统方式,主要通过以下步骤完成:
- 计算并写入
META-INF/MANIFEST.MF
清单文件 - 计算并写入
META-INF/*.SF
签名文件 - 计算并写入
META-INF/*.RSA
签名结果文件 - 写入除
.MF
、.RSA
、.SF
文件之外的所有文件
然而,v1签名存在一些痛点:
- 不保护APK的某些部分,例如ZIP元数据
- APK验证程序必须解压所有已压缩的条目,这需要更多的时间和内存
APK签名方案v2和v3(v2+方案)
搭载Android 7.0及更高版本的设备支持APK签名方案v2(v2方案)及更高版本的方案。该方案会对APK的内容进行哈希处理和签名,然后将生成的“APK签名分块”插入到APK中。
在验证期间,v2+方案会将APK文件视为blob,并对整个文件进行签名检查。对APK进行的任何修改(包括对ZIP元数据进行的修改)都会使APK签名作废。这种形式的APK验证不仅速度要快得多,而且能够发现更多种未经授权的修改。
APK签名方案v2的主要目标是:
- 检测对APK的任何未经授权的修改
- 启用更快的签名和完整性验证
APK签名方案v2采用全文件签名方案,能够发现对APK的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保证。
APK签名分块格式
“APK签名分块”的格式如下(所有数字字段均采用小端字节序):
- size of block,以字节数(不含此字段)计 (uint64)
- 带uint64长度前缀的“ID-值”对序列:
- ID (uint32)
- value(可变长度:“ID-值”对的长度 - 4个字节)
- size of block,以字节数计 - 与第一个字段相同 (uint64)
- magic “APK Sig Block 42”(16个字节)
在解析APK时,首先要通过以下方法找到“ZIP中央目录”的起始位置:在文件末尾找到“ZIP中央目录结尾”记录,然后从该记录中读取“中央目录”的起始偏移量。通过magic值,可以快速确定“中央目录”前方可能是“APK签名分块”。然后,通过size of block值,可以高效地找到该分块在文件中的起始位置。
使用APK签名方案v2进行签名时,会在APK文件中插入一个APK签名分块,该分块位于“ZIP中央目录”部分之前并紧邻该部分。在“APK签名分块”内,v2签名和签名者身份信息会存储在APK签名方案v2分块中。
签名前和签名后的APK对比
该分块包含多个“ID-值”对,所采用的封装方式有助于更轻松地在APK中找到该分块。APK的v2签名会存储为一个“ID-值”对,其中ID为0x7109871a。
APK签名方案v2负责保护第1、3、4部分的完整性,以及第2部分包含的“APK签名方案v2分块”中的signed data分块的完整性。第1、3和4部分的完整性通过其内容的一个或多个摘要来保护,这些摘要存储在signed data分块中,而这些分块则通过一个或多个签名来保护。
第1、3和4部分的摘要采用以下计算方式,类似于两级Merkle树。每个部分都会被拆分成多个大小为1MB(220个字节)的连续块。每个部分的最后一个块可能会短一些。每个块的摘要均通过字节0xa5的串联、块的长度(采用小端字节序的uint32值,以字节数计)和块的内容进行计算。顶级摘要通过字节0x5a的串联、块数(采用小端字节序的uint32值)以及块的摘要的连接(按照块在APK中显示的顺序)进行计算。摘要以分块方式计算,以便通过并行处理来加快计算速度。
简单来讲,数据摘要就是对一段数据进行散列算法计算得出的一段密文数据,过程不可逆,也就是不可解密。也就是说,V2摘要签名分两级,第一级是对APK文件的1、3、4部分进行摘要,第二级是对第一级的摘要集合进行摘要,然后利用秘钥进行签名。安装的时候,块摘要可以并行处理,这样可以提高校验速度。
总的来说,APK就是先摘要,再签名。先看下摘要的定义:Message Digest:摘要是对消息数据执行一个单向Hash,从而生成一0xa5作为数据的个固定长度的Hash值,这个值就是消息摘要,至于常听到的MD5、SHA1都是摘要算法的一种。理论上说,摘要一定会有碰撞,但只要保证有限长度内碰撞率很低就可以,这样就能利用摘要来保证消息的完整性,只要消息被篡改,摘要一定会发生改变。但是,如果消息跟摘要同时被修改,那就无从得知了。
而数字签名是什么呢(公钥数字签名),利用非对称加密技术,通过私钥对摘要进行加密,产生一个字符串,这个字符串+公钥证书就可以看做消息的数字签名,如RSA就是常用的非对称加密算法。在没有私钥的前提下,非对称加密算法能确保别人无法伪造签名,因此数字签名也是对发送者信息真实性的一个有效证明。不过由于Android的keystore证书是自签名的,没有第三方权威机构认证,用户可以自行生成keystore,Android签名方案无法保证APK不被二次签名。
APK签名如何保证APK信息完整性
V1签名如何保证APK信息完整性?
V1签名主要包含三部分内容,如果狭义上说签名跟公钥的话,仅仅在.rsa文件中,V1签名的三个文件其实是一套机制。
MANIFEST.MF:摘要文件,存储文件名与文件SHA1摘要(Base64格式)键值对,格式如下,其主要作用是保证每个文件的完整性。从文件开头到第一个空行之间(图中的1-3行)是manifest文件主属性,从第5行开始就是其所包含的条目(entry)。条目是由条目名称和条目属性组成,条目名称就是Name:之后的值如下图中的AndroidManifest.xml,条目属性是一个name-value格式的map如图中的{"SHA1-Digest":"BeF7Z..."}。
*.MF文件:
如果对APK中的资源文件进行了替换,那么该资源的摘要必定发生改变,如果没有修改MANIFEST.MF中的信息,那么在安装时候V1校验就会失败,无法安装,不过如果篡改文件的同时,也修改其MANIFEST.MF中的摘要值,那么MANIFEST.MF校验就可以绕过。
CERT.SF:二次摘要文件,存储文件名与MANIFEST.MF摘要条目的SHA1摘要(Base64格式)键值对,格式如下:
*.SF文件:
a. Signature-Version是签名版本。
b. SHA-256-Digest-Manifest-Main-Attributes是MANIFEST.MF文件属性的数据指纹Base64值。
c. SHA-256-Digest-Manifest是整个MANIFEST.MF的数据指纹Base64值。
d. Created-By指明文件生成工具,例如:Created-By: 1.0 (Android)。
e. 第8行开始的各个条目,就是对MANIFEST.MF各个条目的数据指纹Base64值。
如果把MANIFEST.MF当做是对APK中各个文件的hash记录,那么.SF就是MANIFEST.MF及其各个条目的hash记录。总的来说,CERT.SF感觉有点像冗余,更像对文件完整性的二次保证,同绕过MANIFEST.MF一样,.SF校验也很容易被绕过。
可以看到CERT.RSA与CERT.SF是相互对应的,两者名字前缀必须一致!
- CERT.RSA证书(公钥)及签名文件,存储keystore的公钥、发行信息、以及对CERT.SF文件摘要的签名信息(利用keystore的私钥进行加密过)。
.RSA是PKCS#7[9]标准格式的文件,我们只关心它所保存的以下两种数据:
a. 用私钥对.SF文件指纹进行非对称加密后得到的加密数据
b. 携带公钥以及各种身份信息的数字证书
加密数据:通过openssl asn1parse格式化查看加密后的数据及其偏移量,从下图中可以看出加密后数据处在PKCS#7的最后
执行openssl asn1parse -i -inform der -in CERT.RSA得到如下ASN1格式数据:
1250是字节偏移量(十进制)
d=5表示所处PKCS#7数据结构的层级是第5层
hl=4表示头所占字节数为4个字节
l=256表示数据字节数为256(对应SHA-256指纹算法)
最后一段是加密后的16进制数据。
执行
dd if=STRANGEW.RSA of=signed-sha256.bin bs=1 skip=$[ 1115 + 4 ] count=256
把加密数据导出来到signed-sha256.bin文件。
之后查看16进制的这个signed-sha256.bin文件数据:
数字证书:执行openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_certs -text查看.RSA中保存的证书信息。截图中可以看到证书包含了签名算法、有效期、证书主体、证书签发者、公钥等信息。
看下CERT.RSA文件内容:
问题:如何把加密数据和证书放到.RSA文件中的呢?
CERT.RSA文件里面存储了证书公钥、过期日期、发行人、加密算法等信息,根据公钥及加密算法,Android系统就能计算出CERT.SF的摘要信息,其严格的格式如下:
从CERT.RSA中,我们能获的证书的指纹信息,在微信分享、第三方SDK申请的时候经常用到,其实就是公钥+开发者信息的一个签名:
除了CERT.RSA文件,其余两个签名文件其实跟keystore没什么关系,主要是文件自身的摘要及二次摘要,用不同的keystore进行签名,生成的MANIFEST.MF与CERT.SF都是一样的,不同的只有CERT.RSA签名文件。也就是说前两者主要保证各个文件的完整性,CERT.RSA从整体上保证APK的来源及完整性,不过META_INF中的文件不在校验范围中,这也是V1的一个缺点。
最后总结一下MANIFEST.MF、CERT.SF、CERT.RSA如何各司其职构成了APK的签名:
a. 解析出CERT.RSA文件中的证书、公钥,解密CERT.RSA中的加密数据
b. 解密结果和CERT.SF的指纹进行对比,保证CERT.SF没有被篡改
c. 而CERT.SF中的内容再和MANIFEST.MF指纹对比,保证MANIFEST.MF文件没有被篡改
d. MANIFEST.MF中的内容和APK所有文件指纹逐一对比,保证APK没有被篡改
V2签名如何保证APK信息完整性?
前面说过V1签名中文件的完整性很容易被绕过,可以理解单个文件完整性校验的意义并不是很大,安装的时候反而耗时,不如采用更加简单的便捷的校验方式。V2签名就不针对单个文件校验了,而是针对APK进行校验,将APK分成1M的块,对每个块计算值摘要,之后针对所有摘要进行摘要,再利用摘要进行签名。
也就是说,V2摘要签名分两级,第一级是对APK文件的1、3、4部分进行摘要,第二级是对第一级的摘要集合进行摘要,然后利用秘钥进行签名。安装的时候,块摘要可以并行处理,这样可以提高校验速度。
其他
通过上面的描述,可以看出因为APK包的区块1、3、4都是受保护的,任何修改在签名后对它们的修改,都会在安装过程中被签名校验检测失败,而区块2(APK Signing Block)是不受签名校验规则保护的,那是否可以在这个不受签名保护的区块2(APK Signing Block)上做文章呢?我们先来看看对区块2格式的描述:
区块2中APK Signing Block是由这几部分组成:2个用来标示这个区块长度的8字节+这个区块的魔数(APK Sig Block 42)+这个区块所承载的数据(ID-value)。
我们重点来看一下这个ID-value,它由一个8字节的长度标示+4字节的ID+它的负载组成。V2的签名信息是以ID(0x7109871a)的ID-value来保存在这个区块中,不知大家有没有注意这是一组ID-value,也就是说它是可以有若干个这样的ID-value来组成。
看上图:APK签名验证过程。
- 寻找APK Signing Block,如果能够找到,则进行验证,验证成功则继续进行安装,如果失败了则终止安装
- 如果未找到APK Signing Block,则执行原来的签名验证机制,也是验证成功则继续进行安装,如果失败了则终止安装
最后,通过验证在已经被V2应用签名方案签名后的APK中添加自定义的ID-value,是不需要再次经过签名就能安装的。详见请见:新一代开源Android渠道包生成工具Walle