问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

APK签名方案v1、v2和v3详解

创作时间:
作者:
@小白创作中心

APK签名方案v1、v2和v3详解

引用
简书
1.
https://www.jianshu.com/p/6a4be5a8ae0e

在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应用签名的传统方式,主要通过以下步骤完成:

  1. 计算并写入META-INF/MANIFEST.MF清单文件
  2. 计算并写入META-INF/*.SF签名文件
  3. 计算并写入META-INF/*.RSA签名结果文件
  4. 写入除.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的主要目标是:

  1. 检测对APK的任何未经授权的修改
  2. 启用更快的签名和完整性验证

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签名验证过程。

  1. 寻找APK Signing Block,如果能够找到,则进行验证,验证成功则继续进行安装,如果失败了则终止安装
  2. 如果未找到APK Signing Block,则执行原来的签名验证机制,也是验证成功则继续进行安装,如果失败了则终止安装

最后,通过验证在已经被V2应用签名方案签名后的APK中添加自定义的ID-value,是不需要再次经过签名就能安装的。详见请见:新一代开源Android渠道包生成工具Walle

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号