Android APK V1 签名原理

对于 Android 开发者而言, APK 签名的重要性不言而喻。Android 7.0 后 APK 签名已经从基于 Jar 签名的 V1 版本升级到了 V2 版本,为了能更好的理解,我们将从 V1、V2、签名验证三个方面进行详细、深入介绍,但是鉴于篇幅原因,本文先介绍 V1 版签名原理。

一、重要概念

1、散列算法

Wiki 定义[1]

A hash function is any function that can be used to map data of arbitrary size to data of fixed size. The values returned by a hash function are called hash values, hash codes, digests, or simply hashes.

也就是说散列函数(通常也叫散列算法)可以把任意长度的数据映射成固定长度的数据,映射出来的数据称为散列值哈希值摘要。因为输入数据不同,得到的散列值不同(很大概率),所以可当做输入数据的指纹。常见的散列算法[2]有 MD5、SHA-1、SHA-256,以下要介绍的 APK 签名会用到 SHA-265[3] 散列算法。

2、加密

Wiki 定义[4]

In cryptography, encryption is the process of encoding a message or information in such a way that only authorized parties can access it.

加密就是把可读的明文数据通过加密算法转换成不可读的密文数据,只有通过相应的解密算法才能把密文数据转换成明文数据,常见的加密算法分为对称加密非对称加密

对称加密就是加密和解密都用同一把“钥匙”。

举个栗子,小明有一把普通锁,这把锁能且只能用同一把钥匙(暂且称为 K )上锁和开锁:假如小明用钥匙 K 上了锁,他的朋友小红要打开这个锁,那么只能事先让小明配一把一样的钥匙 K' 给她。

非对称加密就是加密和解密用的是两把不同的“钥匙”。

举个栗子,小明有一把神奇锁,和普通锁不同的是,这把神奇锁必须要借助两把不同的钥匙(暂且称为钥匙 A 和 B)才能完成上锁和开锁:假如小明用钥匙 A 上了锁,那么用钥匙 A 已经不能开锁了,能且只能用与之相对应的钥匙 B 开锁,反过来也一样,而且钥匙 A 和钥匙 B 是一一对应关系。

实际应用中,小明自己留着钥匙 A 而且保密,然后把钥匙 B 挂在上了锁的箱子外面一起寄送出去,收到箱子的人就可以用钥匙 B 来打开。因为只有通过钥匙 A 上锁的箱子才能被钥匙 B 打开,这就保证了箱子确实是用钥匙 A 上锁后寄过来的。

非对称加密应用非常广泛,有 SSL、SSH 以及非常火的比特币。

以下要介绍的 APK 签名会用到使用最广泛的非对称加密算法 —— RSA

3、数字签名

Wiki 定义[5]

A digital signature is a mathematical scheme for demonstrating the authenticity of digital messages or documents.

数字签名就是证明数据真实性的一种方式。

上面讲非对称加密时举例用的是箱子,如果把箱子换成一段数据的指纹(SHA-256),那么对数据指纹加密的结果实际上就是其数字签名。

数字签名只能通过钥匙 B(公钥)解密,那么如何保证和小明手上的钥匙 A(私钥)成对呢?也就是如何保证这份签名来自小明?这个时候就需要公钥证书出场了。

4、公钥证书(数字证书)

还是 Wiki 定义[6]

In cryptography, a public key certificate, also known as a digital certificate or identity certificate, is an electronic document used to prove the ownership of a public key.

公钥证书就是证明公钥的所有者,证书包括了公钥信息、公钥所有者信息、证书签发者信息等;而公钥证书的真实性由证书颁发机构 —— CA 来保证(CA 证书一般内置在各类操作系统中)。

继续上面的例子,小明把钥匙 B 不是直接挂在箱子外面而是加密后放入公钥证书中,再把证书挂在箱子外一起寄送出去,接收者通过系统内置 CA 证书的公钥解密得到钥匙 B(公钥),再去开锁。这样就保证了钥匙 B 确实是小明的,箱子也确实是小明用钥匙 A 上锁的,而且箱子没有被人动过手脚。

APK 签名原理和上述 4 个概念息息相关,一份经过签名的数据,包含原始数据、数字签名、公钥证书三个部分。用一张图[7]来总结一下:

数字签名原理

二、APK V1 签名原理(前方高能警告,将出现大量 jarsigner 源码细节)

1、签名工具

APK 签名可以用 jarsigner 或者 signapk 两个工具,Android Studio 默认用的是 signapk,二者主要的区别在于证书和秘钥存储的格式不同,前者是通过 Java KeyStore(.jks 文件或者 .keystore 文件) 格式,后者分别用 .pem 和 .pk8 格式来存储证书和密钥。

Java KeyStore 生成方式:

【生成】证书库
keytool -genkey -v -keystore strange.keystore -alias strange -keyalg RSA -keysize 2048 -validity 10000

【查看】证书库
keytool -list -v -keystore {path2jks} -storepass “pass"

.pem .pk8 生成方式:

【生成】密钥
openssl genrsa -out key.pem 2048
【生成】证书请求
openssl req -new -key key.pem -out request.pem
【生成】 pem格式的 x.509 证书
openssl x509 -req -days 10000 -in request.pem -signkey key.pem -out certificate.pem -sha256
【生成】 pk8 格式密钥
openssl pkcs8 -topk8 -outform DER -in key.pem -inform PEM -out key.pk8 -nocrypt

【查看】pem证书
openssl x509 -in publicKey.x509.pem -text -noout

无论是用的哪种签名方式,最终都是在 META-INF 目录下生成三个文件:MANIFEST.MFCERT.SFCERT.RSA(如果是 jarsigner 签名 .SF 和 .RSA 文件名会根据 alias 来定),这三个文件各司其职,最终构成了 APK 签名信息。

2、原理分析

我们来分析一下 jarsigner 源码[8](signapk.jar 与其类似),看看这三个文件是如何生成的。

首先看 main 函数,只做了一件事情:调用 run 方法。

`main` 函数

run 主要做了 4 件事,前面都是一些准备工作,最后调用 signJar 方法开始进行签名。

`run` 方法

signJar 方法中经历了几个步骤,详细可以看如下截图中的注释,最重要的是计算 META-INF/MANIFEST.MF 清单文件、META-INF/.SF* 待签名文件、META-INF/.RSA* 签名结果文件。

signJar 签名流程

下面,将对每个步骤详细展开。

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

我们先来认识一下 manifest 文件:来自于 jar 包的文件清单,在 apk 中我们用来记录所有非目录文件的 数据指纹,如下图所示。

MANIFEST.MF 文件内容

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

条目是由 条目名称条目属性 组成,条目名称就是 Name:之后的值如图中的res/drawable-xhdpi-v4/img_blank.png,条目属性是一个name-value格式的map如图中的{"SHA-256-Digest":"ft47V9YtB/3V9uUqZbN4kTMP+SMJ2D3AK1j7G8lj9l0="}

其条目的生成过程如下:


MANIFEST.MF 计算

针对每个待签名 zip 包中的文件(除了 META-INF 下签名相关的如 .MF SIG- *.SF *.DSA *.RSA *.EC文件),进行如下判断:

  • 如果 Manifest 清单中没有出现,那么计算 hash 然后增加到 Manifest 中;
  • 如果 Manifest 清单中已经包含,那么计算 hash 后和 Manifest 中的 hash 进行对比覆盖。

其中最重要的是获取 hash 属性的方法 getDigestAttributes

生成 MANIFEST.MF 条目属性

先调用 getDigests 生成指纹,再封装成 manifest 条目的属性对象。

计算指纹

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

至此,.MF 文件完成计算并写入。

2.2、计算并写入 META-INF/*.SF 签名文件

这里的 .SF 文件实际上也是清单文件的一种,它是对上述 MANIFEST.MF 文件的数据指纹,我们来看看它的内容。

.SF 文件实际上也是清单文件

从上面对 MANIFEST.MF 文件内容的分析,我们可以看出来 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 指明文件生成工具。
e. 第 7 行开始的各个条目,就是对 MANIFEST.MF 各个条目的数据指纹 Base64 值。

如果把 MANIFEST.MF 当做是对 APK 中各个文件的 hash 记录,那么 *.SF 就是 MANIFEST.MF 及其各个条目的 hash 记录。

我们来看看其生成过程:

.SF 文件计算并写入

先创建了 SignatureFile 对象,再写入 ZipOutputStream 中,关键在于SignatureFile,我们继续看其构造函数。

.SF 文件计算

其实就两步,一是写属性,二是写条目。而指纹的计算都是通过 Java 的 ManifestDigesterManifestDigester.Entry 对象来完成。

至此, .SF 文件完成计算并写入。

2.3、计算并写入 META-INF/*.RSA 签名结果文件

.RSA 是 PKCS#7[9] 标准格式的文件,我们只关心它所保存的以下两种数据:

a. 用私钥对 .SF 文件指纹进行非对称加密后得到的 加密数据
b. 携带公钥以及各种身份信息的 数字证书

来看看上述两种数据:

2.3.1 加密数据

通过 openssl asn1parse 格式化查看加密后的数据及其偏移量,从下图中可以看出加密后数据处在 PKCS#7 的最后。

![PKCS#7 简要数据结构[10]](http://upload-images.jianshu.io/upload_images/300515-6fe37c6e1e0dfa08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

执行 openssl asn1parse -i -inform der -in STRANGEW.RSA 得到如下 ASN1[11] 格式数据:

ASN1 格式数据

最后这一段就是加密后的 16 进制数据了,我们来分析一下。

1115 是字节偏移量(十进制)
d=5 表示所处 PKCS#7 数据结构的层级是第 5 层
hl=4 表示头所占字节数为 4 个字节
l=256 表示数据字节数为 256(对应 SHA-256 指纹算法)

执行 dd if=STRANGEW.RSA of=signed-sha256.bin bs=1 skip=$[ 1115 + 4 ] count=256 把加密数据导出来到 signed-sha256.bin 文件。

执行 hexdump -C signed-sha256.bin 查看文件数据:

.RSA 中的 16 进制加密数据
2.3.2 数字证书

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

.RSA 中的数字证书

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

2.3.3 .RSA 文件计算过程

从源码中可以看出先是调用 sf.generateBlock 生成 Block 静态内部类的对象,最后写入 ZipOutputStream

.RSA 文件计算并写入

核心在于 sf.generateBlock,其中只是执行了return new Block(...),所以关键还是 Block 的构造函数:

.RSA 文件计算

从源码中可以看出,主要是根据私钥算法得到对应的签名算法,再用签名算法对待签名数据(这里是 .SF 文件)进行签名,最后把签名数据和证书放在一起生成字节数组。

此致,.RSA 文件完成计算并写入。

2.4、写入除 .MF .RSA .SF 文件之外的所有文件

分为两步,先写入 META-INF 目录内的,再写入 META-INF 目录外的。writeEntry方法比较简单,实际上是完成了文件从 zipFileZipOutputStream的复制。

写入签名相关文件之外的文件

三、总结

最后总结一下 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 没有被篡改


  1. https://en.wikipedia.org/wiki/Hash_function

  2. https://en.wikipedia.org/wiki/List_of_hash_functions

  3. https://en.wikipedia.org/wiki/SHA-2

  4. https://en.wikipedia.org/wiki/Encryption

  5. https://en.wikipedia.org/wiki/Digital_signature

  6. https://en.wikipedia.org/wiki/Public_key_certificate

  7. https://zh.wikipedia.org/wiki/%E6%95%B8%E4%BD%8D%E7%B0%BD%E7%AB%A0

  8. http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/classes/sun/security/tools/jarsigner

  9. https://en.wikipedia.org/wiki/PKCS

  10. http://qistoph.blogspot.hk/2012/01/manual-verify-pkcs7-signed-data-with.html

  11. https://wiki.openssl.org/index.php/Manual:Asn1parse(1)

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

推荐阅读更多精彩内容