对APK进行签名,正常情况下使用Gradle进行签名或者第三方开源签名工具或插件进行签名及ADB命名,这样需要给测试或运维人员进行讲解,解释半天他们会问你有批量工具吗?那作为程序员得你,只能说有。那我给大家一个用python写的基于apksigner进行批量签名的脚本,双击就OK了。
1. V1&V2签名
在给大家放出大招之前,先卖个萌,来一起了解一下Android的签名。在Android 7.0以下版本,一直使用的是Oracle提供的V1 scheme (JAR signing)签名方案。自从9行代码引发的88亿$官司之后,Google逐渐想法摆脱对Java的依赖。于是乎也就有了V2签名,从Android 7.0开始, 谷歌增加新签名方案 V2 Scheme (APK Signature),更安全的签名方式。
为了兼容之前的签名,默认是先进行了V1签名,然后再使用V2对其进行签名。
在 Android 7.0 以后,可以根据 APK 签名方案 v2(v2 方案)或 JAR 签名(v1 方案)验证 APK。更低版本的平台会忽略 v2 签名,仅验证 v1 签名。
1.1 V1签名
来自JDK(jarsigner), 是JDK提供的针对jar包签名的通用工具,位于JDK/bin/jarsigner.exe
对zip压缩包的每个文件进行验证, 签名后还能对压缩包修改(移动/重新压缩文件)
对V1签名的apk/jar解压,在META-INF存放签名文件(MANIFEST.MF, CERT.SF, CERT.RSA), 其中MANIFEST.MF文件保存所有文件的SHA1指纹(除了META-INF文件)
由此可知: V1签名是对压缩包中单个文件签名验证。
1.2 V2签名
来自Google(apksigner), 对zip压缩包的整个文件验证, 签名后不能修改压缩包(包括zipalign),对V2签名的apk解压,没有发现签名文件,重新压缩后V2签名就失效, 由此可知: V2签名是对整个APK签名验证。
为了保护 APK 内容,APK 包含以下 4 个部分:
- ZIP 条目的内容(从偏移量 0 处开始一直到“APK 签名分块”的起始位置)
- APK 签名分块
- ZIP 中央目录
- ZIP 中央目录结尾
APK 签名方案 v2 负责保护第 1、3、4 部分的完整性,以及第 2 部分包含的“APK 签名方案 v2 分块”中的 signed data
分块的完整性。
第 1、3 和 4 部分的完整性通过其内容的一个或多个摘要来保护,这些摘要存储在 signed data
分块中,而这些分块则通过一个或多个签名来保护。
第 1、3 和 4 部分的摘要采用以下计算方式,类似于两级 Merkle 树。 每个部分都会被拆分成多个大小为 1 MB(220 个字节)的连续块。每个部分的最后一个块可能会短一些。每个块的摘要均通过字节 0xa5
的连接、块的长度(采用小端字节序的 uint32 值,以字节数计)和块的内容进行计算。顶级摘要通过字节 0x5a
的连接、块数(采用小端字节序的 uint32 值)以及块的摘要的连接(按照块在 APK 中显示的顺序)进行计算。摘要以分块方式计算,以便通过并行处理来加快计算速度。
由于第 4 部分(ZIP 中央目录结尾)包含“ZIP 中央目录”的偏移量,因此该部分的保护比较复杂。当“APK 签名分块”的大小发生变化(例如,添加了新签名)时,偏移量也会随之改变。因此,在通过“ZIP 中央目录结尾”计算摘要时,必须将包含“ZIP 中央目录”偏移量的字段视为包含“APK 签名分块”的偏移量。
V2签名优点很明显:
1). 签名更安全(不能修改压缩包)
2). 签名验证时间更短(不需要解压验证),因而安装速度加快
2. apksigner
在 Android 7.0 Nougat 中引入了全新的 APK Signature Scheme v2签名方式。如果要对已经打包好的apk进行v2签名,这时我们就必须借助Android SDK提供的apksigner工具。
这个工具位于SDK目录的build-tools目录下。必须说明的是,v2签名方式时在Android7.0后才推出的,所以只有版本>25的SDK\build-tools\中才能找到apksigner.jar。
上面lib目录里面有apksigner.jar,这里路径最好配置到环境变量里。
apksigner对apk签名的各个参数
apksigner对apk签名的各个参数
apksigner sign //执行签名操作
--ks 你的jks路径 //jks签名证书路径
--ks-key-alias 你的alias //生成jks时指定的alias
--ks-pass pass:你的密码 //KeyStore密码
--key-pass pass:你的密码 //签署者的密码,即生成jks时指定alias对应的密码
--out output.apk //输出路径
input.apk //需要签名的APK
签名示例:
apksigner sign --ks aoaoyi.jks --ks-key-alias aoaoyi --ks-pass pass:aoaoyi.com --key-pass pass:aoaoyi.com --out output.apk input.apk
apksigner检查apk是否已经签名:
apksigner verify -v --print-certs xxx.apk
参数:
-v, --verbose 显示详情(显示是否使用V1和V2签名)
--print-certs 显示签名证书信息
例如:
apksigner verify -v aoaoyi.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Number of signers: 1
有上面大家已经对Android签名有了大致了解,由于V1逐渐被遗忘,我也没有细说,如果想知道V1签名的使用方法请看apk反编译及重新打包,里面有介绍。
接下来真是切入主题->使用Python、apksigner实现对APK进行批量签名。
3.1 我们先来看MultiSigner.py代码
#!/usr/bin/python
#coding:UTF-8
import shutil
import os
"""
1.安装Python27,配置环境变量。
2.配置apksinger的环境变量。
3.把xxx.jks、xxx.apk(支持多个)放入和MultiSigner.py同目录下。
4.双击MultiSigner.py,出现命令行窗体,等待。
5.按任意键结束。
6.查看output目录下已签好的apk文件。
"""
#jks签名证书(放在当前目录中)
jksFile = 'aoaoyi.jks'
#KeyStore密码
storePassword = 'aoaoyi.com'
#生成jks时指定的alias
keyAlias = 'aoaoyi'
#签署者的密码,即生成jks时指定alias对应的密码
keyPassword = 'aoaoyi.com@gmail'
# 获取当前目录中所有的apk源包
src_apks = []
# python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.')
for file in os.listdir('.'):
if os.path.isfile(file):
extension = os.path.splitext(file)[1][1:]
if extension in 'apk':
src_apks.append(file)
try:
for src_apk in src_apks:
# file name (with extension)
src_apk_file_name = os.path.basename(src_apk)
# 分割文件名与后缀
temp_list = os.path.splitext(src_apk_file_name)
# name without extension
src_apk_name = temp_list[0]
# 后缀名,包含. 例如: ".apk "
src_apk_extension = temp_list[1]
# 创建生成目录
output_dir = 'output/'
# 目录不存在则创建
if not os.path.exists(output_dir):
os.mkdir(output_dir)
#目标文件路径
target_apk = output_dir + src_apk_name + src_apk_extension
#签名后的文件路径
signer_apk = output_dir + src_apk_name + '_signer' + src_apk_extension
#拼装签名命令
signer_str = 'cmd.exe /k apksigner sign --ks ' + jksFile + ' --ks-pass pass:' + storePassword + ' --ks-key-alias ' + keyAlias + ' --key-pass pass:' + keyPassword + ' --out ' + signer_apk + ' ' + src_apk
#执行签名命令
signer_result = os.popen(signer_str)
#输出签名命令执行结果
if(signer_result.read() != ''):
print 'signer_result:\t', 'success'
else:
print 'signer_result:\t', 'fail'
#拼装验证签名命令
verify_str = 'apksigner verify -v --print-certs ' + signer_apk
#执行对签过的apk进行签名验证
verify_result = os.popen(verify_str)
#输出验证签名命令执行结果
print 'verify_result:\t', verify_result.read()
#os.remove(target_apk)
except Exception, e:
print 'Exception:\t', repr(e)
print '请输入任意键退出'.decode('UTF-8').encode('GBK')
#等待输入
raw_input()
3.2. 目录文件
3.2. 运行后,命令展示信息
你把这个脚本甩给测试或运维,你就再也不受他们打扰了。如果考虑到安全的话,你在代码服务器上构建个GO界面直接调用这个脚本,把生成的apk包生成一个下载地址供下载就可以了。这样就节约了自己很多时间,就可以有时间喝茶撩妹子了。
参考:
文章评论