APK 签名方案v1、v2 和 v3

通过应用签名,开发者可以标识应用创作者并更新其应用,而无需创建复杂的接口和权限。

在 Google Play 上,应用签名可以将 Google 对开发者的信任和开发者对自己的应用的信任联系在一起。

在 Android 上,应用签名是将应用放入其应用沙盒的第一步。在安装 APK 的时候需要校验包的完整性,同时,对于覆盖安装的场景还要检验新旧是否匹配,这两者都是通过 Android 签名机制来进行保证的。

APK 签名方案

Android 支持以下三种应用签名方案:

为了最大限度地提高兼容性,请按照 v1、v2、v3 的先后顺序采用所有方案对应用进行签名。与只通过 v1 方案签名的应用相比,还通过 v2+ 方案签名的应用能够更快速地安装到 Android 7.0 及更高版本的设备上。更低版本的 Android 平台会忽略 v2+ 签名,这就需要应用包含 v1 签名。

JAR 签名(v1 方案)

  1. 源码解析

1.1 计算并写入 META-INF/MANIFEST.MF 清单文件

总结:

**针对每个待签名 zip 包中的文件(除了 META-INF 下签名相关的如 .MF SIG- *.SF *.DSA .RSA .EC文件),计算其数据指纹并写入 META-INF/MANIFEST.MF 清单文件中

1.2 计算并写入 META-INF/*.SF 签名文件
1.3 计算并写入 META-INF/*.RSA 签名结果文件
1.4 写入除 .MF .RSA .SF 文件之外的所有文件
  1. 有何痛点?v1 签名不保护 APK 的某些部分,例如 ZIP 元数据;APK 验证程序必须解压所有已压缩的条目,而这需要花费更多时间和内存;

APK 签名方案 v2 和 v3(v2+ 方案)

搭载 Android 7.0 及更高版本的设备支持 APK 签名方案 v2(v2 方案)及更高版本的方案(在 Android 9 中,v2 方案已更新为 v3 方案,以便在签名分块中包含其他信息,但在其他方面保持相同的工作方式)。该方案会对 APK 的内容进行哈希处理和签名,然后将生成的“APK 签名分块”插入到 APK 中。

在验证期间,v2+ 方案会将 APK 文件视为 blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改。

APK 签名验证过程:
image.png

验证程序会对照存储在“APK 签名分块”中的 v2+ 签名对 APK 的全文件哈希进行验证。 该哈希涵盖除“APK 签名分块”(其中包含 v2+ 签名)之外的所有内容。在“APK 签名分块”以外对 APK 进行的任何修改都会使 APK 的 v2+ 签名作废。v2+ 签名被删除的 APK 也会被拒绝,因为 v1 签名指明相应 APK 带有 v2 签名,所以 Android 7.0 及更高版本会拒绝使用 v1 签名验证 APK。

APK 签名方案 V2

APK Signature Scheme v2 的两个主要目标是:
1. 检测对 APK 的任何未经授权的修改。这是通过使签名覆盖被签名的 APK 的每个字节来实现的。
2. 启用更快的签名和完整性验证。这是通过在验证签名之前只需要最少量的 APK 解析来实现的,从而完全绕过 ZIP 条目解压缩,并通过使用哈希树使完整性验证可并行化。

APK 签名方案 v2 是一种全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保证

首先来了解一个名词:APK 签名分块,这个知识点将在下面用到。

APK 签名分块格式:

“APK 签名分块”的格式如下(所有数字字段均采用小端字节序):

  • size of block,以字节数(不含此字段)计 (uint64)
  • 带 uint64 长度前缀的“ID-值”对序列:
    • ID (uint32)
    • value(可变长度:“ID-值”对的长度 - 4 个字节)
  • size of block,以字节数计 - 与第一个字段相同 (uint64)
  • magic“APK 签名分块 42”(16 个字节)
注:在解析 APK 时,首先要通过以下方法找到“ZIP 中央目录”的起始位置:在文件末尾找到“ZIP 中央目录结尾”记录,然后从该记录中读取“中央目录”的起始偏移量。通过 magic 值,可以快速确定“中央目录”前方可能是“APK 签名分块”。然后,通过 size of block 值,可以高效地找到该分块在文件中的起始位置。

使用 APK 签名方案 v2 进行签名时,会在 APK 文件中插入一个 APK 签名分块,该分块位于“ZIP 中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2 签名和签名者身份信息会存储在 APK 签名方案 v2 分块中。

签名前和签名后的 APK:
image.png

该分块包含多个“ID-值”对,所采用的封装方式有助于更轻松地在 APK 中找到该分块。APK 的 v2 签名会存储为一个“ID-值”对,其中 ID 为 0x7109871a。

APK 签名方案 v2 是在 Android 7.0 (Nougat) 中引入的。为了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的设备上安装,应先使用 JAR 签名功能对 APK 进行签名,然后再使用 v2 方案对其进行签名。

为了保护 APK 内容,APK 包含以下 4 个部分:

  1. ZIP 条目的内容(从偏移量 0 处开始一直到“APK 签名分块”的起始位置)
  2. APK 签名分块
  3. ZIP 中央目录
  4. ZIP 中央目录结尾

签名后的各个 APK 部分:
image.png

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 中显示的顺序)进行计算。摘要以分块方式计算,以便通过并行处理来加快计算速度。

APK 摘要:
image.png

简单来讲,数据摘要就是对一段数据进行散列算法计算得出的一段密文数据,过程不可逆,也就是不可解密。

也就是说,V2 摘要签名分两级,第一级是对 APK 文件的 1、3 、4 部分进行摘要,第二级是对第一级的摘要集合进行摘要,然后利用秘钥进行签名。安装的时候,块摘要可以并行处理,这样可以提高校验速度。

总的来说,APK 就是先摘要,再签名。

先看下摘要的定义:Message Digest:摘要是对消息数据执行一个单向Hash,从而生成一0xa5作为数据的个固定长度的Hash值,这个值就是消息摘要,至于常听到的MD5、SHA1都是摘要算法的一种。理论上说,摘要一定会有碰撞,但只要保证有限长度内碰撞率很低就可以,这样就能利用摘要来保证消息的完整性,只要消息被篡改,摘要一定会发生改变。但是,如果消息跟摘要同时被修改,那就无从得知了。

而数字签名是什么呢(公钥数字签名),利用非对称加密技术,通过私钥对摘要进行加密,产生一个字符串,这个字符串+公钥证书就可以看做消息的数字签名,如RSA就是常用的非对称加密算法。在没有私钥的前提下,非对称加密算法能确保别人无法伪造签名,因此数字签名也是对发送者信息真实性的一个有效证明。不过由于Android的keystore证书是自签名的,没有第三方权威机构认证,用户可以自行生成keystore,Android签名方案无法保证APK不被二次签名。

源码解析

  1. 有何痛点?

APK 签名方案 v1、v2 和 v1&v2 APK 包结构对比

apk包对比:
image.png

可以看到,如果只有 V2 签名,那么APK包内容几乎是没有改动的,META_INF 中不会有新增文件。

而 V1 签名是通过 META-INF 中的三个文件保证签名及信息的完整性,如下图:

META-INF 那三个文件:
image.png

APK 签名如何保证 APK 信息完整性

image.png

V1 签名如何保证 APK 信息完整性?

V1 签名主要包含三部分内容,如果狭义上说签名跟公钥的话,仅仅在 .rsa 文件中,V1 签名的三个文件其实是一套机制。

MANIFEST.MF:摘要文件,存储文件名与文件 SHA1 摘要(Base64 格式)键值对,格式如下,其主要作用是保证每个文件的完整性。

从文件开头到第一个空行之间(图中的 1-3 行)是 manifest 文件主属性,从第 5 行开始就是其所包含的条目(entry)。

条目是由 条目名称条目属性 组成,条目名称就是 Name:之后的值如下图中的 AndroidManifest.xml,条目属性是一个 name-value格式的 map如图中的{"SHA1-Digest":"BeF7Z..."}

*.MF 文件:
image.png

如果对 APK 中的资源文件进行了替换,那么该资源的摘要必定发生改变,如果没有修改 MANIFEST.MF 中的信息,那么在安装时候 V1 校验就会失败,无法安装,不过如果篡改文件的同时,也修改其 MANIFEST.MF 中的摘要值,那么 MANIFEST.MF 校验就可以绕过。

CERT.SF:二次摘要文件,存储文件名与 MANIFEST.MF 摘要条目的 SHA1 摘要(Base64 格式)键值对,格式如下:

*.SF 文件:
image.png

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 格式数据:

image.png

** 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 文件。

image.png

之后查看 16 进制的这个signed-sha256.bin 文件数据:

image.png

数字证书:

执行 openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_certs -text 查看 .RSA 中保存的证书信息。截图中可以看到证书包含了签名算法、有效期、证书主体、证书签发者、公钥等信息。

看下 CERT.RSA 文件内容:

image.png

问题:如何把加密数据和证书放到 .RSA 文件中的呢?

CERT.RSA 文件里面存储了证书公钥、过期日期、发行人、加密算法等信息,根据公钥及加密算法,Android 系统就能计算出 CERT.SF 的摘要信息,其严格的格式如下:

image.png

从 CERT.RSA 中,我们能获的证书的指纹信息,在微信分享、第三方 SDK 申请的时候经常用到,其实就是公钥 + 开发者信息的一个签名:

image.png

除了 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 的块,对每个块计算值摘要,之后针对所有摘要进行摘要,再利用摘要进行签名。

image.png

也就是说,V2 摘要签名分两级,第一级是对APK文件的 1、3 、4 部分进行摘要,第二级是对第一级的摘要集合进行摘要,然后利用秘钥进行签名。安装的时候,块摘要可以并行处理,这样可以提高校验速度。

其他

通过上面的描述,可以看出因为APK包的区块1、3、4都是受保护的,任何修改在签名后对它们的修改,都会在安装过程中被签名校验检测失败,而区块2(APK Signing Block)是不受签名校验规则保护的,那是否可以在这个不受签名保护的区块2(APK Signing Block)上做文章呢?我们先来看看对区块2格式的描述:

image.png

区块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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容