构建与打包能力(二、v1、v2签名原理)

343 阅读6分钟

构建与打包能力系列

1、ApkTool

  • ApkTool是一个逆向分析工具,可以把APK解开,添加渠道信息文件,重新打包重新签名,因此,基于ApkTool的多渠道打包方案分为以下几步: (1)通过ApkTool工具,解压apk;

(2)删除已有签名信息;

(3)在meta-inf文件夹下添加渠道信息文件;

(4)通过ApkTool工具,重新打包生成新APK;

(5)重新签名;

但是每生成一个新的渠道包时,都需要重新解包、打包和签名,而这几步操作又是相对机械且耗时。但是若需要同时生成上百个渠道,则需要几个小时,显然不适合渠道非常多的业务场景。

多渠道打包的切入点原则:附加信息不能影响签名验证

  • Android Apk的签名是什么? 签名就是apk文件摘要信息使用keystore里面的私钥加密后的产物,一旦内容被篡改,摘要就会改变,签名是摘要的加密结果,摘要改变,签名也会失效。APK签名也是这个道理,如果APK签名跟内容对应不起来,系统默认为APK内容被篡改了,从而拒绝安装,以保证系统的安全性。目前Android有三种签名V1、V2(N)、V3(P)。

消息摘要(message digest):将长度不固定的数据作为输入,执行特定的hash函数,生成固定长度的输出,输出的hash值就称为该消息摘要。MD5、SHA、CRC都是hash函数的算法。

2、V1签名方案概述

image.png

  • MANIFEST.MF:记录apk中每一个文件名与文件SHA1摘要,主要作用是保证每个文件的完整性;
  • CERT.SF:记录了对MANIFEST.MF文件整体进行摘要的信息,和每一块摘要的三次摘要,防止MANIFEST.MF被篡改;
  • CERT.RSA:记录了keystore中存储的公钥证书信息,以及对CERT.SF文件的签名(经keystore中的私钥教秘);

不同的keystore进行签名,.MF和.SF生成的文件是一致的,不同的是.RSA文件。.MF和.SF保证完整性,.RSA保证来源。

  • MANIFEST.MF 文件格式
Name: AndroidManifest.xml
SHA1-Digest: tgjuuiwbJndnjjfjmm=

Name: res/mipmap-xxhdpi-v4/ic_launcher_round.png
SHA1-Digest: uuhdhhJiOhhshhshjs=

Name: res/mipmap-xxhdpi-v4/ic_launcher.png
SHA1-Digest: kiytRT98800bnjjjdd=
  • CERT.SF 文件格式
Signature-Version: 1.0
SHA1-Digest_Manifest: m4hdhiidhbbb/jjhhudugb=//对MANIFEST.MF文件整体的摘要
create-By: 1.0 (Android)

Name: AndroidManifest.xml
SHA1-Digest: hdhuybnmdmndmm/uydbdbbx=

Name: res/mipmap-xxhdpi-v4/ic_launcher_round.png
SHA1-Digest: YTRbhkiduhdnndndndnndnn=

Name: res/mipmap-xxhdpi-v4/ic_launcher.png
SHA1-Digest: IOPBNBhbdudubbbbdbwjal9d=
  • CERT.RSA 文件格式
Version: 3 (0x2) //版本
serial Number: 1461033915 (6x57159bbb) //序列号
Signature Algorithm: sha1WithRSAEncryption //签名算法
Issuer:cn=shang //发行者

Validity
Not Beforb: Apr 1902:45:152016 GHT //有效时间
Not After Apr 1302:45:152041 GHT
Subj ect: CN=shang
Sub Ject Public Key Info:
Public Key Algorithm: rsa Encryption
Public=Key: (1024 bit) //公钥
Modulus:
ee: a2: f1: b3:11: fc: a4:49:02: d2:2c: 5a: 2b:d3:bc
...
  • V1签名如何防止篡改 假如攻击者修改了其中某一个文件,那么他必须修改MANIFEST.MF中对应文件的摘要,否则这个文件校验不通过;接着还得修改CERT.SF中的摘要,否则摘要校验不过;还得重新计算CERT.RSA的签名,否则签名校验不通过;但是计算签名需要私钥,私钥在开发者手中,攻击者没有私钥,所以无法签名。
  • V1签名存在的问题 校验速度慢:校验过程中需要对apk中所有文件进行摘要计算,在apk资源很多、性能较差的机器上签名校验会花费较长时间,导致安装速度慢。

完整性不够:V1签名只会校验apk文件中的部分文件,例如META-INF文件夹就不会参与校验,META-INF目录用来存放签名,自然此目录本身是不计入签名校验过程的,可以随意在这个目录中添加文件,比如一些快速批量打包方案就选择在这个目录中添加渠道文件。

3、V2签名方案概述

  • V2签名是在Android7.0之后引入到,它解决了V1签名校验时速度慢的问题,同时对apk完整性的校验扩展到整个安装包

使用的是apkSinger,位于 /build-tools/29/apksigner.bat

  • Zip文件存储结构

image.png

  • apkSigner签名之前

image.png

  • 分块计算摘要 v2签名不针对单个文件校验了,而是针对APK进行校验,将APK分成多个1M体积的数据块,对每个块计算数据摘要,之后针对所有摘要再次进行摘要。

v2摘要签名分两级,第一级分块摘要,第二级是对第一级的摘要集合进行摘要,然后利用密钥进行签名。安装的时候,块摘要可以并行处理,这样可以提高校验速度。

image.png

  • 签名之后的apk

image.png

  • 在这个APK文件结构下,只有块2记录apk签名信息的区域,不是全部参与v2签名的校验,所以大家都在这部分做文章。
  • APK Signing Block 中包含了多个“ID-值”的键值对,而v2签名的信息,会记录在ID为0x7109871a中,而不会校验其他ID下的值。
  • 因此,基于v2签名的多渠道方案就这样诞生了:在APK签名块中添加一个额外的ID值,用于记录渠道信息。
  • 文件隐写:LSB图像最低位有效算法和盲水印技术。
  • 可商用的多渠道自动化打包方案 image.png
  • 如何集成vasDolly
dependencies {
   classpath 'com.leon.channel:plugin:2.0.3'
}
  • 项目根目录新建渠道列表 flavor_channel.txt
yyb //每行一个渠道
360
huawei
xiaomi
  • 根目录新建flavor_channel.gradle,并在app模块中应用
apply plugin: 'channel'
channel {
    //指定渠道文件

    channelFile = file("../flavor_channel.txt")
    println("channelFile:" + channelFile.exists())
    //多渠道包的输出目录,默认为new File(project.buildDir,"channel")
    baseOutputDir = new File(project.buildDir, "channel")
    //多渠道包的命名规则,默认为:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}
    apkNameFormat = '${appName}-${versionName}-${flavorName}-${buildType}'
    //快速模式:生成渠道包时不进行校验(速度可以提升10倍以上,默认为false)
    isFastMode = false
    //buildTime的时间格式,默认格式:yyyyMMdd-HHmmss
    buildTimeDateFormat = 'yyyyMMdd-HH:mm:ss'
    //低内存模式(仅针对V2签名,默认为false):只把签名块、中央目录和EOCD读取到内存,不把最大头的内容块读取到内存,在手机上合成APK时,可以使用该模式
    lowMemory = false
}

rebuildChannel {
    //指定渠道文件
    channelFile = new File("../flavor_channel.txt")
    println("rebuildChannel:" + channelFile.exists())
    baseDebugApk = new File(project.buildDir, "outputs/packers/app-release_10_jiagu_sign.apk")
    baseReleaseApk = new File(project.buildDir, "outputs/packers/app-release_10_jiagu_sign.apk")
    debugOutputDir = new File(project.buildDir, "outputs/rebuildChannel/debug")
    releaseOutputDir = new File(project.buildDir, "outputs/rebuildChannel/release")
    //快速模式:生成渠道包时不进行校验(速度可以提升10倍以上,默认为false)
    isFastMode = false
    //低内存模式(仅针对V2签名,默认为false):只把签名块、中央目录和EOCD读取到内存,不把最大头的内容块读取到内存,在手机上合成APK时,可以使用该模式
    lowMemory = false
}
/**
 * apkNameFormat支持以下字段:
 *
 * appName : 当前project的name
 * versionName : 当前Variant的versionName
 * versionCode : 当前Variant的versionCode
 * buildType : 当前Variant的buildType,即debug or release
 * flavorName : 当前的渠道名称
 * appId : 当前Variant的applicationId
 * buildTime : 当前编译构建日期时间,时间格式可以自定义,默认格式:yyyyMMdd-HHmmss
 **/

dependencies {
    //多渠道
    api 'com.leon.channel:helper:2.0.3'
}
  • 读取渠道信息 通过helper类库中的ChannelReaderUtil类读取渠道信息。
String channel = ChannelReaderUtil.getChannel(getApplicationContext());