diff --git a/blogs/Android/Framework/APK 构建流程.md b/blogs/Android/Framework/APK 构建流程.md index dd4016c..e7ceda6 100644 --- a/blogs/Android/Framework/APK 构建流程.md +++ b/blogs/Android/Framework/APK 构建流程.md @@ -4,9 +4,10 @@ Android APK 构建流程 #### 目录 -2. 前言 -3. 构建流程 -4. Android Gradle Plugin 源码分析 +1. 前言 +2. 构建流程 + +2. Android Gradle Plugin 源码分析 #### 前言 @@ -18,7 +19,7 @@ Android APK 构建流程 #### 构建流程 -先来一张 [Android 配置构建]() 官网上的一张图: +先来一张 [Android 配置构建](https://developer.android.com/studio/build/index.html?hl=zh-cn#build-process) 官网上的一张图: ![](https://i.loli.net/2019/05/07/5cd1888bcfafa.png) diff --git a/blogs/Android/Framework/Android 签名校验机制.md b/blogs/Android/Framework/Android 签名校验机制.md new file mode 100644 index 0000000..4d704a0 --- /dev/null +++ b/blogs/Android/Framework/Android 签名校验机制.md @@ -0,0 +1,96 @@ +--- +Android 签名校验机制 v1、v2、v3 +--- + +#### 目录 + +2. 概述 +3. v1 +4. v2 +5. v3 +6. 参考 + +#### 概述 + +Android 支持以下三种应用签名方案: + +- v1:基于 JAR 签名 +- v2:APK 签名方案 v2(在 Android 7.0 引入) +- v3:APK 签名方案 v3(在 Android 9 引入) + +为了最大限度地提高兼容性,请按照 v1、v2、v3 的先后顺序采用**所有**方案对应用进行签名。与只通过 v1 方案签名的应用相比,还通过 v2+ 方案签名的应用能够更快的安装到 Android 7.0 及更高版本的设备上。更低版本的 Android 平台会忽略 v2+ 签名,这就需要应用包含 v1 签名。 + +#### v1 方案:JAR 签名 + +v1 利用的是 jarsigner 工具,它是 JDK 自带的。它会对所有的文件(包括 META-INF 中与签名不相关的文件)都会被签名。 + +在 APK 的 META-INF 目录下一般有三个文件:.MF、.SF 和 .RSA 三个文件。 + +**(1).MF 文件:** + +APK 当中的原始文件信息用摘要算法如 SHA1 计算得到的摘要信息并用 base64 编码保存,以及对应采用的摘要算法如 SHA1。 + +**(2).SF 文件:** + +.MF 文件的摘要信息以及 .MF 文件当中每个条目在用摘要算法计算得到的摘要信息并用 base64 编码保存。 + +**(3).RSA 文件:** + +存放证书信息,公钥信息,以及用私钥对 .SF 文件的加密数据即签名信息,这段数据是无法伪造的,除非有私钥,另外,.RSA 文件还记录了所用的签名算法等信息。 + +APK 包在安装的时候,是按照从 (3)到(1)到顺序依次校验的。先用公钥还原签名信息,然后和 .SF 文件中的信息对比,然后用同样的摘要算法对 .MF 文件里面的每一个条目计算对应的摘要信息,然后对比 .MF 文件是否一致。 + +在这个过程中,我没发现有两点: + +1. 在校验的过程中需要解压,因为 .MF 文件的摘要信息是基于原始未压缩文件内存,因此在校验的时候就需要解压出原始数据,而这个解压操作无疑是耗时操作。 +2. APK 包的完整性校验不够强。这里我们可以看到,如果我们在 APK 签名后,对 APK 包中没有涉及到原始文件的数据块做改变,那么这层校验机制就会失效。所以,在美团的打包工具 Walle 的做法就是在 META-INF 中添加空文件来实现多渠道打包,因为在 META-INF 中添加新文件是不需要重新签名的。这也同时说明了,其实存在过度签名的问题。 + +为了解决这些问题,Android 7.0 中引入了 APK 签名方案 v2。 + +#### v2 方案 + +APK 签名方案 v2 是一种全文件签名方案,该方案能够发现对 APK 受保护部分进行的所有更改,从而有助于加快验证速度并**增强完整性保证**。 + +使用 APK 签名方案 v2 进行签名时,会在 APK 文件中插入一个 APK 签名分块,该分块位于 " ZIP 中央目录" 部分之前并紧邻该部分。在 " APK 签名分块" 内,v2 签名和签名者身份信息会存储在其中。 + +![](https://i.loli.net/2019/05/13/5cd8e2f3387d818425.png) + +v2 签名机制不存在解压原始数据,签名校验时间显著减少,因此安装时间也相应减少。同时,它是基于 APK 的二进制内存做的签名信息(APK Signing Block 签名块本身不参与加密校验),因此打包后改变 APK 的其他三部分的任何字节都会导致签名校验不通过。这也说明了,zipalign 是要在 apksigner 之前的。 + +APK Signing Bolck 由这几个部分组成:两个用来标示这个区块长度的八字节 + 这个区块的魔数(APK Sig Bolck 42)+ 这个区块所承载数据(ID-value)。 + +| 偏移 | 字节数 | 描述 | +| ---- | ------ | ------------------------------------------- | +| @0 | 8 | 这个 Block 的长度(本字段的长度不计算在内) | +| @8 | n | 一组 ID-value | +| @-24 | 8 | 这个 Bolck 的长度(和第一个字段一样值) | +| @-16 | 16 | 魔数 "APK Sig Block 42" | + + v2 的签名信息是以 ID(0x7109871a)的 ID-value 来保存在这个区块中,这是一组 ID-value,但是在验证的时候,只是通过 ID 为 0x7109871a 的来获取 APK Signature Scheme v2 Block,对这个区块中其他的 ID-value 选择了忽略。所以 Walle 提供一个自定义的 ID-value 并写入该区域,从而为快速生成渠道包服务。 + +想要了解是如何做的,请参考:[新一代开源Android渠道包生成工具Walle](https://tech.meituan.com/2017/01/13/android-apk-v2-signature-scheme.html) + +在 APK 签名分块内,也就是 ID 为 0x7109871a,它里面有一个叫做 **signed data** 的数据结构。APK 签名方案 v2 负责保护第1、3、4 部分的完整性,以及第二部分包含的 APK 签名方案 v2 分块中的 signed data 分块的完整性。 + +第1、3、4 部分的完整性通过其内容的一个或多个摘要来保护,这些摘要存储在 signed data 分块中,而这些分块则通过一个或多个签名保护。第1、3、4 部分都会被拆分为多个大小为 1MB 的连续块分段计算摘要,摘要以分段方式计算,以便通过并行处理来加快计算速度。 + +APK 签名方案 v2 是在 Android 7.0 中引入的,为了使 APK 可在 Android 6.0 及更低版本的设备上安装,应先使用 JAR 签名功能对 APK 进行签名,然后再使用 v2 方案对其进行签名。 + +#### v3 方案 + +Android 9 支持 APK 密钥轮转,这使得应用能够在 APK 更新过程中更改签名密钥。为了实现轮转,APK 必须指示新旧签名密钥之间的信任级别。为了支持密钥轮转,我们将 APK 签名方案从 v2 更新为 v3,以允许使用新旧密钥。v3 在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 结构的信息。 + +v3 APK 签名分块的格式与 v2 相同,APK 的 v3 签名会存储为一个 ID 为 0xf05368c0 的 ID-value。看来 Google 给自己留了后路,以后再增加版本,肯定又是增加新的 ID-value,在高版本的 SDK 设备上解析即可。 + +更多请参考:[Android 文档 APK 签名方案 v3](https://source.android.com/security/apksigning/v3) + +#### 参考 + +[Oracle Signed_JAR_File](https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File) + +[https://source.android.com/security/apksigning](https://source.android.com/security/apksigning) + +[分析Android V2新签名打包机制](https://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651232457&idx=1&sn=90b16c3868a341272b8f1aa26d6c0122&chksm=f1d9e5aac6ae6cbcfaecb07bdd280abf81a46f1937c43f61e69d7f78d64350943356f5443d58&scene=27#wechat_redirect) + +[新一代开源Android渠道包生成工具Walle](https://tech.meituan.com/2017/01/13/android-apk-v2-signature-scheme.html) + diff --git a/blogs/Android/Gradle/Android Gradle 权威指南/读书笔记之一.md b/blogs/Android/Gradle/Android Gradle 权威指南/读书笔记之一.md index 056609c..1f7d44b 100644 --- a/blogs/Android/Gradle/Android Gradle 权威指南/读书笔记之一.md +++ b/blogs/Android/Gradle/Android Gradle 权威指南/读书笔记之一.md @@ -26,7 +26,7 @@ 在写完 [Gradle Plugin 入门指南](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Gradle/Gradle_Plugin_Guide.md) 和 [Gradle Plugin 实践之 TinyPng Plugin](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Gradle/TinyPngPlugin.md) 之后,总会有一些疑问,如果你仔细阅读过这两篇文章,你甚至会发现文章还有一些错误的说法,比如以下命令中 -q 参数的含义: ``` -./gradlew task -q build +./gradlew -q build ``` -q 的含义并不是表示静默输出,而是…..(下面会讲到 @@ -46,13 +46,13 @@ Gradle 命令一般用于执行一些 Task 。 在前两篇文章,我们也接触到了两个 Gradle 命令,一个是用于执行我们自定义 task 的命令: ``` -./gradlew task myCustomTask +./gradlew myCustomTask ``` 一个是用于执行上传 jar 包依赖的命令: ``` -./gradlew task uploadArchives +./gradlew uploadArchives ``` 这个 uploadArchives Task 其实是 java plugin 里面的 Task,也就是说我们依赖了这个插件: @@ -103,13 +103,13 @@ clean - Deletes the build directory. 比如我们可以执行上面 Android tasks 中的 androidDependencies 来查看项目中的所有依赖: ``` -./gradlew task androidDependencies +./gradlew androidDependencies ``` 当然,我们也可以一次执行多个任务,比如: ``` -./gradlew task clean build +./gradlew clean build ``` 但是,需要主要的是两者的依赖关系,这个我们后面自定义 Task 的时候会讲到。 @@ -446,13 +446,13 @@ myCustomTask.onlyIf{ 我们判断的条件是是否有 build_type 属性,所以当我们还是按往常执行如下命令: ```groovy -./gradlew task -q myCustomTask +./gradlew -q myCustomTask ``` 是不会有任何输出的,那我们怎么做才能会输出呢? ```groovy -./gradlew task -q -Pbuild_type=debug myCustomTask +./gradlew -q -Pbuild_type=debug myCustomTask ``` 加一个参数就好啦,就可以控制任务是否执行了。 diff --git a/blogs/Android/Gradle/Gradle_Plugin_Guide.md b/blogs/Android/Gradle/Gradle_Plugin_Guide.md index a449121..f6698ce 100644 --- a/blogs/Android/Gradle/Gradle_Plugin_Guide.md +++ b/blogs/Android/Gradle/Gradle_Plugin_Guide.md @@ -38,10 +38,10 @@ apply plugin: MyPlugin 在 Plugin 中我们定义了一个 hello 的 Task,然后 apply 这个 Plugin,现在我们可以打开 Terminal 执行这个 Task: ``` -./gradlew task -q hello +./gradlew -q hello ``` -就输出 Hello Plugin~ 啦。这里 -q 参数表示静默执行,后面就是我们定义的 Task name,还是很简单的。 +就输出 Hello Plugin~ 啦。这里 -q 参数表示只输出重要信息的一种日志级别,后面就是我们定义的 Task name,还是很简单的。 如果这个你还有问题,就不用往下看了 :) @@ -163,7 +163,7 @@ top.omooo.my_plugin:my_plugin:1.0 这时候我们我们还没有真正的发布,需要执行: ```java -./gradlew task uploadArchives +./gradlew uploadArchives ``` 执行完后,就可以看到我们根目录生成了一个 repo 文件夹,里面就是我们的插件 jar 包了: @@ -221,7 +221,8 @@ apply plugin: 'top.omooo.my_plugin' 之后,我们肯定会改 MyPlugin 里面的代码的,这时候就要重新上传依赖,用的还是那个命令: ```java -./gradlew task uploadArchives +./gradlew uploadArchives ``` -OK,大功告成~ \ No newline at end of file +OK,大功告成~ + diff --git a/blogs/Android/Gradle/TinyPngPlugin.md b/blogs/Android/Gradle/TinyPngPlugin.md index eb3814f..c5b197b 100644 --- a/blogs/Android/Gradle/TinyPngPlugin.md +++ b/blogs/Android/Gradle/TinyPngPlugin.md @@ -155,7 +155,7 @@ class CrazyPlugin implements Plugin { 回顾上一篇文章,我们说到,既然我们修改了 Plugin 的代码,就要重新生成 jar 包上传依赖,即执行: ``` -./gradlew task uploadArchives +./gradlew uploadArchives ``` 然后在 app 模块的 build.gradle 文件里,我们就可以 apply Plugin 并且添加 Extensions 了: @@ -183,7 +183,7 @@ tinyInfo { 然后我们在执行: ``` -./gradlew task tinyTask +./gradlew tinyTask ``` 就可以输出我们的自定义配置项了。也就是说,我们写的自定义配置项可以在 Task 中获取的。 diff --git a/images/Android/Framework/v2 签名.png b/images/Android/Framework/v2 签名.png new file mode 100644 index 0000000..e73b396 Binary files /dev/null and b/images/Android/Framework/v2 签名.png differ