Android APK 签名、打包笔记
我们知道,一款Android 要发布的话,必须经过签名,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签名方案
在v1方案中,签名只保护apk中的元数据,也就是单个文件。apk其实就是一个zip文件,我们将打包签名好的apk文件,用解压缩文件解压,就可以看到一个名称为META-INF的文件夹里面。
在META-INF文件夹中,存在3个文件,MANIFEST.MF , CERT.SF,CERT.RSA。这些就是v1版本的apk在安装时候,进行签名校验 很重要的文件。
MANIFEST.MF
这个文件中保存着apk解压之后,所有文件(非文件夹)的信息,看下面的一段内容:
Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.2.0Name: AndroidManifest.xml
SHA1-Digest: g+psNXzyZSKU8DMfQr8FdR0KjXI=Name: LICENSES
SHA1-Digest: +7g2kf8cIIZknpabOMYpXi8vrK8=Name: META-INF/android.arch.core_runtime.version
SHA1-Digest: OxxKFJcpzAROGjnfMbNijNv1+JU=Name: META-INF/android.arch.lifecycle_extensions.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
在每个文件块中,其中 name就是解压之后,文件的名称(包含路径),而SHA1-Digets就是对文件的内容提取摘要之后 进行Base64之后生成的字符串内容。
CERT.SF
先看一段CERT.SF的内容:
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: 00yHda1FOBSkUAwjlTfipwkY+0k=
X-Android-APK-Signed: 2Name: AndroidManifest.xml
SHA1-Digest: BNl/B2KAFMNJ1keEnqQ5+vkw8mY=Name: LICENSES
SHA1-Digest: 9irOepAoIoOn8huusmI9KxjOT6c=Name: META-INF/android.arch.core_runtime.version
SHA1-Digest: gfKdTxzy15iV0z7ZogHp7+qdek8=
SHA1-Digest-Manifest :是对整个MANIFEST.MF使用 SHA1算法之后,生成的Base64的编码的字符串。
Name:也是对应解压之后的文件的名称
SHA1-Digest:是对MANIFEST.MF中对应名称(Name)做SHA1之后生成再生成的Base64编码的字符串
CERT.RSA
这里会把之前生成的CERT.SF文件,用我们私钥计算出签名,然后将签名以及包含公钥信息的数字证书一同写入CERT.RSA中保存。
在mac电脑中,我们可以在终端中使用以下命令查看CERT.SF的内容:
openssl pkcs7 -inform DER -in CERT.RSA -text -noout -print_certs
其中CERT.RSA 表示的是文件CERT.RSA ,如果不是在当前文件夹中,应该填写CERT.RSA 的相对路径或者绝对路径。
可以用一张图来解释签名的过程:
安装校验过程。
我们从破解 sign.apk的过程来讲解校验的过程。首先由于v1是针对单一文件进行提取摘要的方式进行校验。
1.假如,我们破坏或者修改了apk中的某一个文件,那么我们必须修改MANIFEST.MF中的对应文件的摘要值,才能通过sdk对MANIFEST.MF的校验。
2.就算我们修改某个文件之后,并且修改了对应的MANIFEST.MF里面的摘要之后,由于CERT.SF中保存的是未修改之前的MANIFEST.MF中每个条目的SHA1之后的Base64编码值,因此前后比对,不一样。第二步对CERT.SF的校验,也不会通过。只有我们在修改MANIFEST.MF 摘要的同时,也修改CERT.SF对应条目的摘要值,才能通过第二步对CERT.SF的检测。
3前面两步通过伪装修改的方式对MANIFEST.MF和CERT.SF的校验都通过了的话,那么第三步CERT.RSA 的校验,就不可能伪装了,因为CERT.RSA 使用的是开发者的私钥信息进行的签名。用户无法获得其私钥信息,没法伪装。
v1版本签名下的多渠道包实现
既然上面的签名校验方式,不允许我们对apk解压之后的某个文件的修改。 我们就可以不破坏这种校验机制增加文件,来记录apk包的渠道信息。或者利用zip的文件格式来做文章存储我们的渠道信息。市面上有2种对v1签名生成多渠道包的方式。
方式1:们可以在不改变原来apk中任何单个文件的情况下,通过对apk增加文件的方式来记录相关的渠道信息。实现多渠道打包。
我们可以在META-INF,或者apk包解压的任何文件夹位置添加一个我们自己的文件,文件名称如上面channel_xiaomi.txt,以文件名称作为渠道名,然后再重新生成apk的 zip文件。因为上面的MANIFEST.MF ,CERT.SF ,CERT.RSA是对apk解压之后的每个单独文件进行校验,但是我们增减的channel_xiaomi.txt是在生成渠道包之后,重新加入进去的,所以android系统安装apk的时候,我们增加的channel_xiaomi.txt并不在我们android系统校验的范围内,我们就可以在app内,读取这个文件的名称来获取对应的渠道。这里主要耗费的时间是对apk解压之后生成channel_xxx.txt的空文件,然后重新打包成.apk文件。
方式二:利用zip文件的文件格式,我们可以在文件最后的部分的comment(注释)中加入自己的渠道信息,然后再app中去读去zip文件的comment里面的内容,读去到对应的渠道号。这里主要是对zip文件的操作。
V2签名方案
Android 7.0(Nougat)引入一项新的应用签名方案APK Signature Scheme v2,它是一个对全文件进行签名的方案,能提供更快的应用安装、对未授权APK文件的更改提供更多保护,在默认情况下,Android Gradle 2.2.0插件会使用APK Signature Scheme v2和传统签名方案来签署你的应用。
目前该方案不是强制性的,在 build.gradle
添加 v2SigningEnabled false
,就能使用传统签名方案来签署我们的应用(见下面的代码片段)
android {...defaultConfig { ... }signingConfigs {release {storeFile file("xxx.keystore")storePassword "password"keyAlias "MyReleaseKey"keyPassword "password"v2SigningEnabled false}}}
APK 签名方案 v2 是一种全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保护。使用 APK 签名方案 v2 进行签名时,会在 APK 文件中插入一个 APK签名分块,该分块位于“ZIP 中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2 签名和签名者身份信息会存储在 APK 签名方案 v2 分块中。
新的签名方案会在ZIP文件格式的 Central Directory 区块所在文件位置的前面添加一个APK Signing Block区块。整个APK(ZIP文件格式)会被分为以下四个区块: 1. Contents of ZIP entries(from offset 0 until the start of APK Signing Block) 2. APK Signing Block 3. ZIP Central Directory 4. ZIP End of Central Directory。
之前的渠道包生成方案是通过在META-INF目录下添加空文件,用空文件的名称来作为渠道的唯一标识,之前在META-INF下添加文件是不需要重新签名应用的,这样会节省不少打包的时间,从而提高打渠道包的速度。但在新的应用签名方案下META-INF已经被列入了保护区了,向META-INF添加空文件的方案会对区块1、3、4都会有影响,v2签名方案签署的应用经过我们旧的生成渠道包方案处理后,在安装时会报以下错误:
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES:
Failed to collect certificates from base.apk: META-INF/CERT.SF indicates base.apk is signed using APK Signature Scheme v2, but no such signature was found. Signature stripped?]
目前V1签名另外一种比较流行的往APK中添加ZIP Comment,生成多渠道包的方案,也因为上述原因,无法在新的应用签名方案下进行正常工作。
既然v1签名生成多渠道包的方式对于v2不能使用,那么就需要找另外的出路了。
既然v2签名保护1,3,4块数据区,不能对1,3,4区快的数据做修改,那么就可以在2 数据区 Apk Signing block做修改,在这块数据加入我们的渠道信息。
APK 签名分块
为了保持与 v1 APK 格式向后兼容,v2 及更高版本的 APK 签名会存储在“APK 签名分块”内,该分块是为了支持 APK 签名方案 v2 而引入的一个新容器。在 APK 文件中,“APK 签名分块”位于“ZIP 中央目录”(位于文件末尾)之前并紧邻该部分。
该分块包含多个“ID-值”对,所采用的封装方式有助于更轻松地在 APK 中找到该分块。APK 的 v2 签名会存储为一个“ID-值”对,其中 ID 为 0x7109871a。
数据 | 字节数 | 描述 |
size of blcok | 8个字节 | 除此字段外 block的总长度 |
id-value-pairs | 8个字节 | 此id与value 数据的总长度 |
id | 4个字节 | id数据 |
value | n个字节 | value数据 |
... | ||
size of block | 8个字节 | 与第一个字段相同 |
magic | 16个字节 | 魔数,标记block的数据格式 |
在解析 APK 时,首先要通过以下方法找到“ZIP 中央目录”的起始位置:在文件末尾找到“ZIP 中央目录结尾”记录,然后从该记录中读取“中央目录”的起始偏移量。通过 magic
值,可以快速确定“中央目录”前方可能是“APK 签名分块”。然后,通过 size of block
值,可以高效地找到该分块在文件中的起始位置。在解译该分块时,应忽略 ID 未知的“ID-值”对.
验证
在 Android 7.0 及更高版本中,可以根据 APK 签名方案 v2+ 或 JAR 签名(v1 方案)验证 APK。更低版本的平台会忽略 v2 签名,仅验证 v1 签名。
通过上图可以看出新的应用签名方案的验证过程:
1. 寻找APK Signing Block,如果能够找到,则进行验证,验证成功则继续进行安装,如果失败了则终止安装
2. 如果未找到APK Signing Block,则执行原来的签名验证机制,也是验证成功则继续进行安装,如果失败了则终止安装。
在参考文章中,zip的文件格式中我们可以知道End of Central Directory的这块的数据格式,如下:
Offset | Bytes | Description | 译 |
---|---|---|---|
0 | 4 | End of central directory signature = 0x06054b50 | 核心目录结束标记,固定为0x06054b50 |
4 | 2 | Number of this disk | 当前的磁盘编号 |
6 | 2 | Disk where central directory starts | 核心目录开始的磁盘编号 |
8 | 2 | Number of central directory records on this disk | 磁盘上所记录的核心目录数量 |
10 | 2 | Total number of central directory records | 核心目录的总数 |
12 | 4 | Size of central directory (bytes) | 核心目录的大小 |
16 | 4 | Offset of start of central directory, relative to start of archive | 核心目录开始位置相对于archive的偏移 |
20 | 2 | Comment length (n) | 注释的长度 |
22 | n | Comment | 注释的内容 |
知道apk的文件结构,这样可以从apk(实质就是zip文件) EOCD,反推出Central Directory的位置,最后确定是否有APK signing Block,然后确认是否是v2签名,同时我们也可以在APK signing Block这块内容增加一个固定的id(不与ID 0x7109871a重复就行
)-value对,来存放我们apk的channel信息,这样就不会破坏整个apk的签名信息,又可以增加我们的渠道信息。
在使用美团打包walle的时候,如果你的build-tools 版本较高的话,可能打出来的包,无法在Android P 上安装。因为 android p 需要 apksigningblock 的长度确保为 4096 的倍数。具体的解决方案在这里。
V3签名方案
Android 9 支持 APK秘钥轮替,这使应用能够在 APK 更新过程中更改其签名密钥。为了实现轮替,APK 必须指示新旧签名密钥之间的信任级别。为了支持密钥轮替,google将 APK签名从 v2 更新为 v3,以允许使用新旧密钥。v3 在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 结构的信息。
参考文章:
APK 签名方案 v2 | Android 开源项目 | Android Open Source Project
zip文件格式
美团自动化打包实践
Android APK 签名、打包笔记相关推荐
- android+命令行编译,打包生成apk文件,Android 使用Android Studio + Gradle 或 命令行 进行apk签名打包...
默认为debug mode,使用的签名文件在: $HOME/.android/debug.keystore 比如 C:\Users\chengcj1.android\debug.keystore 1. ...
- android换台电脑打包签名,Android 使用Android Studio + Gradle 或 命令行 进行apk签名打包 -电脑资料...
1. 默认为debug mode,使用的签名文件在: $HOME/.android/debug.keystore 2. Release Mode 签名: build.gradle:android {s ...
- Android 应用开发(33)---Android程序签名打包
Android程序签名打包 第一章的倒数第二节,本节给大家介绍的是如何将我们的程序打包成Apk文件,并且为我们的Apk签名! 上一节中已经说了,我们后续的教程使用的IDE是Android Studio ...
- Android APK 签名比对
Android APK 签名比对 转载请注明出处:http://www.blogjava.net/zh-weir/archive/2011/07/19/354663.html Android APK ...
- Android APK签名 JKS 密钥库使用专用格式。建议使用 “keytool -importkeystore -srckeystore E:\xxxxxx- pkcs12“ 迁移到行业标准格式
Android Studio中进行签名 转载地址:Android APK签名 JKS 密钥库使用专用格式.建议使用 "keytool -importkeystore -srckeystore ...
- Android安卓签名打包原理,步骤详细讲解(debug,release区别.V1,V2区别)
所有的Android应用程序都要求开发人员用一个证书进行数字签名,Android系统不会安装没有进行签名的应用程序. 在应用程序开发期间,由于是以Debug调试模式编译的,因此IDE(ADT)根据会自 ...
- android studio密钥库口令,Android应用开发Android Studio签名打包及根据keystore密钥获取SHA1安全码...
本文将带你了解Android应用开发Android Studio签名打包及根据keystore密钥获取SHA1安全码,希望本文对大家学Android有所帮助. " 一.签名打包两种方式 1. ...
- 5 Android程序签名打包
在上一章,我们创建了自己的 Android 工程,并成功的在模拟器中运行起来.同时提到,工程目录中有一个 bin 目录,运行之后我们可以在此目录下找到我们的apk.那么不难想到,我们在点"R ...
- Android APK签名原理
Android APK 签名原理涉及到密码学的加密算法.数字签名.数字证书等基础知识,这里做个总结记录. 非对称加密 需要两个密钥,一个是公开密钥,另一个是私有密钥:一个用作加密的时候,另一个则用作解 ...
- Android apk签名获取方法
Android apk签名获取的一种方法,供大家参考 Windows(黑窗口)或Mac(终端)获取apk的签名: 1,先获取.jks文件路径(复制好备用) 2,在黑窗口或终端中输入 keytool - ...
最新文章
- java 跳转 oop_Java基础之:OOP——接口
- Sql字符串操作函数
- oracle更改字段名顺序的方法
- mysql主从切换gtid不一致_GTID 复制、主从不一致跳过操作、快速切换master
- 经典java程序员的面试题及答案
- NET问答: 如何将 ASP.NET Core WebAPI 中抛出的异常封装成对象?
- 链表c语言stl,C++STL之List容器
- android 底部黑边,android – 截屏周围的黑色边缘
- php 判断 单选按钮事件,jquery如何判断单选按钮是否选中
- Java程序员必经的实践之路:微服务与SOA架构
- python入门代码大全-初学python有哪些可以临摹的小段练习代码素材?
- Docker 外部访问容器Pp、数据管理volume、网络network 介绍
- iOS 改变UITextField中光标颜色
- c++中变量名和变量值
- mmdetection学习之anchor_generator
- 10篇最新年优秀CISSP认证考试心得分享(值得收藏)
- Docker详细学习文档
- 【Python实战项目】全球疫情数据采集 + 可视化展示
- 大家都在学Python,你和别人的差距在哪?
- 深入学习Redis系列文章
热门文章
- 城市热点助沈阳市苏家屯区**网平稳升级
- 盛格塾暑期公益课程《学活LINUX》
- 近视600度适合学计算机吗,近视超过600度的朋友注意了,这几点你必须要知道
- xcode编程c语言,使用xcode编写c语言的方法介绍
- How to manage sudo users in Centos
- 请勿滥用 2PC, 忘记提交prepared transaction对PostgreSQL造成的危害.
- Nexus 5X 刷 Android 7.0
- VIOSLAM 综述
- ftp服务器输入密码无法显示,登录FTP服务器时提示输入密码
- Android 自定义View 仿微信好友,字母排序