美团新一代渠道包打包神器walle
背景:
Android 7.0 中新增了 APK Signature Scheme v2
签名方式
如果Android Studio升级到 v2.2+,构建APK时默认使用的签名方式就是APK Signature Scheme v2
目前比较流行的2套 多渠道打包脚本
- 在APK内注入${channel}.txt 文件
- 在APK的zip info中写入 channel 信息
实质上都会在签名后修改APK文件,目前都会造成 签名认证失败
官方介绍 https://tech.meituan.com/android-apk-v2-signature-scheme.html
因为在Android 7.0(Nougat)推出了新的应用签名方案APK Signature Scheme v2,用之前快速生成渠道包的方式(美团Android自动化之旅—生成渠道包)打出来的包,没法签名。因此有了现在新的渠道包打包工具walle。
新的应用签名方案APK Signature Scheme v2
Android 7.0(Nougat)引入一项新的应用签名方案APK Signature Scheme v2,它是一个对全文件进行签名的方案,能提供更快的应用安装时间、对未授权APK文件的更改提供更多保护,在默认情况下,Android Gradle 2.2.0插件会使用APK Signature Scheme v2和传统签名方案来签署你的应用。
下面以 新的应用签名方案
来指APK Signature Scheme v2。
目前该方案不是强制性的,在 build.gradle
添加 v2SigningEnabled false
,就能使用传统签名方案来签署我们的应用(见下面的代码片段)。
android {...defaultConfig { ... }signingConfigs {release {storeFile file("myreleasekey.keystore")storePassword "password"keyAlias "MyReleaseKey"keyPassword "password"v2SigningEnabled false}}}
但新的应用签名方案有着良好的向后兼容性,能完全兼容低于Android 7.0(Nougat)的版本。对比旧签名方案,它有更快的验证速度和更安全的保护,因此新的应用签名方案可能会被采纳成一个强制配置,笔者认为现在有必要对现有的渠道包生成方式进行检查、升级或改造来支持新的应用签名方案。
新的签名方案对已有的渠道生成方案有什么影响呢?下图是新的应用签名方案和旧的签名方案的一个对比:
新的签名方案会在ZIP文件格式的 Central Directory
区块所在文件位置的前面添加一个APK Signing Block区块,下面按照ZIP文件的格式来分析新应用签名方案签名后的APK包。
整个APK(ZIP文件格式)会被分为以下四个区块:
- Contents of ZIP entries(from offset 0 until the start of APK Signing Block)
- APK Signing Block
- ZIP Central Directory
- ZIP End of Central Directory
新应用签名方案的签名信息会被保存在区块2(APK Signing Block)中, 而区块1(Contents of ZIP entries
)、区块3(ZIP Central Directory
)、区块4(ZIP End of Central Directory
)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。
之前的渠道包生成方案是通过在META-INF目录下添加空文件,用空文件的名称来作为渠道的唯一标识,之前在META-INF下添加文件是不需要重新签名应用的,这样会节省不少打包的时间,从而提高打渠道包的速度。但在新的应用签名方案下META-INF已经被列入了保护区了,向META-INF添加空文件的方案会对区块1、3、4都会有影响,新应用签名方案签署的应用经过我们旧的生成渠道包方案处理后,在安装时会报以下错误:
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?]
目前另外一种比较流行的渠道包快速生成方案(往APK中添加ZIP Comment)也因为上述原因,无法在新的应用签名方案下进行正常工作。
如果新的应用签名方案后续改成强制要求,那我们现有的生成渠道包的方式就会无法工作,那我们难道要退回到解放前,通过传统的方式(例如:使用APKTool逆向工具、采用Flavor + BuildType等比较耗时的方案来进行渠道包打包)来生成支持新应用签名方案的渠道包吗?
如果只有少量渠道包的场景下,这种耗时时长还能够勉强接受。但是目前我们有将近900个渠道,如果采用传统方式打完所有的渠道包需要近3个小时,这是不能接受的。
那我们有没有其他更好的渠道包生成方式,既能支持新的应用签名方案,又能体验毫秒级的打包耗时呢?我们来分析一下新方案中的区块2——Block。
可扩展的APK Signature Scheme v2 Block
通过上面的描述,可以看出因为APK包的区块1、3、4都是受保护的,任何修改在签名后对它们的修改,都会在安装过程中被签名校验检测失败,而区块2(APK Signing Block)是不受签名校验规则保护的,那是否可以在这个不受签名保护的区块2(APK Signing Block)上做文章呢?我们先来看看对区块2格式的描述:
偏移 | 字节数 | 描述 |
---|---|---|
@+0 | 8 | 这个Block的长度(本字段的长度不计算在内) |
@+8 | n | 一组ID-value |
@-24 | 8 | 这个Block的长度(和第一个字段一样值) |
@-16 | 16 | 魔数 “APK Sig Block 42” |
区块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 Signing Block,如果能够找到,则进行验证,验证成功则继续进行安装,如果失败了则终止安装
- 如果未找到APK Signing Block,则执行原来的签名验证机制,也是验证成功则继续进行安装,如果失败了则终止安装
那Android应用在安装时新的应用签名方案是怎么进行校验的呢?笔者通过翻阅Android相关部分的源码,发现下面代码段是用来处理上面所说的ID-value的:
public static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock,Result result) throws SignatureNotFoundException {checkByteOrderLittleEndian(apkSigningBlock);// FORMAT:// OFFSET DATA TYPE DESCRIPTION// * @+0 bytes uint64: size in bytes (excluding this field)// * @+8 bytes pairs// * @-24 bytes uint64: size in bytes (same as the one above)// * @-16 bytes uint128: magicByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);int entryCount = 0;while (pairs.hasRemaining()) {entryCount++;if (pairs.remaining() < 8) {throw new SignatureNotFoundException("Insufficient data to read size of APK Signing Block entry #" + entryCount);}long lenLong = pairs.getLong();if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {throw new SignatureNotFoundException("APK Signing Block entry #" + entryCount+ " size out of range: " + lenLong);}int len = (int) lenLong;int nextEntryPos = pairs.position() + len;if (len > pairs.remaining()) {throw new SignatureNotFoundException("APK Signing Block entry #" + entryCount + " size out of range: " + len+ ", available: " + pairs.remaining());}int id = pairs.getInt();if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {return getByteBuffer(pairs, len - 4);}result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);pairs.position(nextEntryPos);}throw new SignatureNotFoundException("No APK Signature Scheme v2 block in APK Signing Block");}
上述代码中关键的一个位置是 if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {return getByteBuffer(pairs, len - 4);}
,通过源代码可以看出Android是通过查找ID为 APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a
的ID-value,来获取APK Signature Scheme v2 Block,对这个区块中其他的ID-value选择了忽略。
在APK Signature Scheme v2中没有看到对无法识别的ID,有相关处理的介绍。
当看到这里时,我们可不可以设想一下,提供一个自定义的ID-value并写入该区域,从而为快速生成渠道包服务呢?
怎么向ID-value中添加信息呢?通过阅读ZIP的文件格式和APK Signing Block格式的描述,笔者通过编写下面的代码片段进行验证,发现通过在已经被新的应用签名方案签名后的APK中添加自定义的ID-value,是不需要再次经过签名就能安装的,下面是部分代码片段。
public void writeApkSigningBlock(DataOutput dataOutput) {long length = 24;for (int index = 0; index < payloads.size(); ++index) {ApkSigningPayload payload = payloads.get(index);byte[] bytes = payload.getByteBuffer();length += 12 + bytes.length;}ByteBuffer byteBuffer = ByteBuffer.allocate(Long.BYTES);byteBuffer.order(ByteOrder.LITTLE_ENDIAN);byteBuffer.putLong(length);dataOutput.write(byteBuffer.array());for (int index = 0; index < payloads.size(); ++index) {ApkSigningPayload payload = payloads.get(index);byte[] bytes = payload.getByteBuffer();byteBuffer = ByteBuffer.allocate(Integer.BYTES);byteBuffer.order(ByteOrder.LITTLE_ENDIAN);byteBuffer.putInt(payload.getId());dataOutput.write(byteBuffer.array());dataOutput.write(bytes);}...}
新一代渠道包生成工具
到这里为止一个新的渠道包生成方案逐步清晰了起来,下面是新一代渠道包生成工具的描述:
- 对新的应用签名方案生成的APK包中的ID-value进行扩展,提供自定义ID-value(渠道信息),并保存在APK中
- 而APK在安装过程中进行的签名校验,是忽略我们添加的这个ID-value的,这样就能正常安装了
- 在App运行阶段,可以通过ZIP的
EOCD(End of central directory)
、Central directory
等结构中的信息(会涉及ZIP格式的相关知识,这里不做展开描述)找到我们自己添加的ID-value,从而实现获取渠道信息的功能
新一代渠道包生成工具完全是基于ZIP文件格式和APK Signing Block存储格式而构建,基于文件的二进制流进行处理,有着良好的处理速度和兼容性,能够满足不同的语言编写的要求,目前笔者采用的是Java+Groovy开发, 该工具主要有四部分组成:
- 用于写入ID-value信息的Java类库
- Gradle构建插件用来和Android的打包流程进行结合
- 用于读取ID-value信息的Java类库
- 用于供
com.android.application
使用的读取渠道信息的AAR
这样,每打一个渠道包只需复制一个APK,然后在APK中添加一个ID-value即可,这种打包方式速度非常快,对一个30M大小的APK包只需要100多毫秒(包含文件复制时间)就能生成一个渠道包,而在运行时获取渠道信息只需要大约几毫秒的时间。
这个项目我们取名为Walle(瓦力),已经开源,项目的Github地址是: https://github.com/Meituan-Dianping/walle (求Issue、PR、Star)。希望业内有类似需求的团队能够在APK Signature Scheme V2签名下愉快地生成渠道包,同时也期待大家一起对该项目进行完善和优化。
总结
以上就是我们对新的应用签名方案进行的分析,并根据它所带来的文件存储格式上的变化,找到了可以利用的ID-value,然后基于这个ID-value来构建我们新一代渠道包生成工具。
新一代渠道包生成工具能够满足新应用签名方案对安全性的要求,同时也能满足对渠道包打包时间的要求,至此大家生成渠道包的方式需要升级了!
文章中引用的图片来源于:https://source.android.com/security/apksigning/v2.html
上面是关于工具实现的介绍,下面介绍工具的使用:https://github.com/Meituan-Dianping/walle
Gradle插件使用方式配置build.gradle在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下:buildscript {dependencies {classpath 'com.meituan.android.walle:plugin:1.1.6'}
}
并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AARapply plugin: 'walle'dependencies {compile 'com.meituan.android.walle:library:1.1.6'
}
配置插件walle {// 指定渠道包的输出路径apkOutputFolder = new File("${project.buildDir}/outputs/channels");// 定制渠道包的APK的文件名称apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';// 渠道配置文件channelFile = new File("${project.getProjectDir()}/channel")
}
配置项具体解释:apkOutputFolder:指定渠道包的输出路径, 默认值为new File("${project.buildDir}/outputs/apk")apkFileNameFormat:定制渠道包的APK的文件名称, 默认值为'${appName}-${buildType}-${channel}.apk'
可使用以下变量:projectName - 项目名字appName - App模块名字packageName - applicationId (App包名packageName)buildType - buildType (release/debug等)channel - channel名称 (对应渠道打包中的渠道名字)versionName - versionName (显示用的版本号)versionCode - versionCode (内部版本号)buildTime - buildTime (编译构建日期时间)fileSHA1 - fileSHA1 (最终APK文件的SHA1哈希值)flavorName - 编译构建 productFlavors 名
channelFile:包含渠道配置信息的文件路径。 具体内容格式详见:渠道配置文件示例,支持使用#号添加注释。如何获取渠道信息在需要渠道等信息时可以通过下面代码进行获取String channel = WalleChannelReader.getChannel(this.getApplicationContext());
如何生成渠道包生成渠道包的方式是和assemble${variantName}Channels指令结合,渠道包的生成目录默认存放在 build/outputs/apk/,也可以通过walle闭包中的apkOutputFolder参数来指定输出目录用法示例:生成渠道包 ./gradlew clean assembleReleaseChannels
支持 productFlavors ./gradlew clean assembleMeituanReleaseChannels
临时生成某渠道包我们推荐使用channelFile/configFile配置来生成渠道包,但有时也可能有临时生成渠道包需求,这时可以使用:生成单个渠道包: ./gradlew clean assembleReleaseChannels -PchannelList=meituan生成多个渠道包: ./gradlew clean assembleReleaseChannels -PchannelList=meituan,dianping
基于360加固后签名失效的解决方案:https://github.com/Jay-Goo/ProtectedApkResignerForWalle
其中的配置
美团新一代渠道包打包神器walle相关推荐
- 新一代开源Android渠道包生成工具Walle
本文转载自[http://tech.meituan.com/android-apk-v2-signature-scheme.html] 在Android 7.0(Nougat)推出了新的应用签名方案A ...
- Android渠道包打包流程
1.环境要求 Windows.JDK1.7.0以上.WinRAR 2.打包步骤 (1)从Jenkins打包平台取得最终作为发版外卖apk (2)apk重命名为src.zip(没错,就是改成一个压缩包) ...
- 新一代打包神器Walle(瓦力)使用小结
Walle多渠道打包 介绍 Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护.在默认 ...
- android 渠道包 占位符配置,Android打包
8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 打包的事,之前很少做,也了解的比较少,今天好好看了看,总结一下,以后要是用到了,或许可以少走些弯路. 多渠道打包 友盟多 ...
- android 渠道号_亲测:安卓打渠道包神器,1分钟出自动出100个渠道包
是美团开源的,个人觉得很好用,分享出来. 下面是官方地址: https://github.com/Meituan-Dianping/walle 注意: buildtoos最好选用27版本,因为新的bu ...
- Android 批量打包 基于Walle的多渠道快速打包自动脚本
Android 批量打包 基于Walle的多渠道快速打包自动脚本 Android Signature V2 Scheme签名下的新一代渠道包打包神器 github主页:https://github.c ...
- android渠道包加固,Android多渠道打包和加固那些事
多渠道两种打包方式: 一.利用android studio的productFlavors去多渠道打包.速度慢,让人崩溃.但与市场上的一些加固无缝接入. 二.利用美团的walle,快速高效,但是在加固上 ...
- Android-Ant自动编译打包android项目 -- 2 ----签名与渠道包
上篇介绍了怎么使用ant自动编译打包现有的android项目,这篇将继续介绍如果如何在ant打包应用的时候加入签名信息以及自动打包渠道包. 1. 加入签名信息: 在项目的根目录下建一个ant.prop ...
- android 渠道打包工具,Walle —— Android多渠道打包神器
图片只为致敬斯坦·李 Walle为了android多渠道打包而生的工具,废话不多说直接上用法 配置build.gradle 在项目的build.gradle文件中添加walle插件依赖 depende ...
最新文章
- android SQLite数据库用法图文详解(附源码)
- Python中sorted函数的用法
- 简单谈谈5G/C-V2X技术与自动驾驶的关系
- CSS选择器的权重与优先规则
- ASP.NET Core MVC中的 [Required]与[BindRequired]
- 强化学习在美团“猜你喜欢”的实践
- 无法获取未定义或 null 引用的属性“title”_JDK中四种对象引用类型
- 在Linux系统下初始化COM组件,Linux操作系统的引导和初始化.doc
- python链表_手把手实现 python 的链表数据结构
- 简单两个矩阵如何用计算机运算,教你使用excel做矩阵运算
- HTML个人简历代码模板(静态页面)
- Springboot集成海康SDK(以海康USB_SDK为例)
- 你的微信接收消息也会延迟吗?原因居然出在这,一招教你轻松解决
- 最新数据!国内5G手机出货已超29万部
- 【C++】RAll,裸指针,弃用auto_ptr原因
- 【并发】3、MESI
- 【openGauss笔记】SQL语法
- PHP数据类型与运算符
- python -字典练习1:生成银行卡号
- Android高德地图绘制区域,Android高德地图多边形的绘制与编辑
热门文章
- 黑莓商务演示应用教程(一)
- 论文脚注上方横线不是顶格的怎么办?
- 七牛云文件存储的使用【刘新宇】
- 用git来管理单机游戏存档
- CW 发送邮件(获取运输信息)
- 二维码跳转android或ios下载app中转处理
- 百度开发者中心聚力AI开放能力,打造新时代下开发者的“淘金矿”
- 一文搞懂AWS Region, VPC, VPC endpoint,AZ, Subnet 基础篇上
- DFX 安全测试-- 告诉你什么是XSS、sql注入?POST和GET的区别....
- 【技巧】仿某宝网详情页宝贝放大镜效果