Android打包、签名与混淆

8,179 阅读13分钟

Android打包、签名与混淆

前几天实习上要打包出一个apk,然后上头就发了我一个.keystore文件,叫我打包的时候用这个签名。当时我在想啊,原来我只是在学习计算机网络的时候了解公钥、密钥的区别,在复习https后又加深了解了一下,但总之这些都不是我直接接触的,都是些理论知识了解而过,现在遇到了,我就在想Android的这个打包时为啥要签名?签名不是应该存在于网络传输中的吗?

原来才学习Android的时候做了个音乐的app,使用的是喜马拉雅的SDK,其中引入后就叫你必须使用混淆,当时第一次接触了混淆。后来反编译过一些软件,发现有些包名为a,b...这样的,才了解了下混淆的作用。

这两个知识点呢都是保证了我们应用的安全,而且都是在打包的时候使用到的,我就把他们总结在了一起去学习。

最后真的感谢我选择了Android,我们大学快结束了才学习了编译原理、c++,当时我认为这两门课对我们软件的学生可能用处不大,因为大部分同学走的方向都是java方向,当我开始做Android直播方向的毕设的时候,我感觉学校开的所有课程都能联系起来了

Android打包流程

在这里插入图片描述

图为典型的Android应用模块的编译流程 Android编译流程的步骤:

  1. 打包资源文件Resource Files,生成R.java文件(AAPT:Android Asset Packaging Tool)
  2. 处理AIDL文件,生成java代码(AIDL:Android Interface Definition Language,实现进程间的通信)
  3. 编译代码文件(Source code),生成对应的.class文件
  4. .class文件转化为dex文件
  5. 将dex文件和编译的资源文件组合成成apk(apk-builder)
  6. 使用调试或发布密钥库签名(jarsigner)
  7. 应用优化,减少运行时占用的内存(zipalign:归档对其工具 —> mmap内存映射 —> 减少运行时消耗的内存)

ps:

  • 如果您使用的是 apksigner,则只能在为 APK 文件签名之前执行 zipalign。如果您在使用 apksigner 为 APK 签名之后对 APK 进行进一步更改,则签名将会失效。
  • 如果您使用的是 jarsigner,则只能在为 APK 文件签名之后执行 zipalign。

ps2:

  • 我们经常直接解压一些apk的时候会发现有很多的classes.dex文件,默认的dex文件应该是一个的,但是每个dex文件的方法数不能超过65535。我们只要在glide下配置multiDexEnabled true就好了
    某解压后的apk

签名

平时呢我们对这种初级开发者,一个应用诞生后,真的就是一个属于自己的app了,一般就调试运行在自己的手机上,有些时候甚至还做不到多机型适配,出于炫耀等原因,你可能会将你的app打包成apk,然后呢丢在一个服务器上,生成一个二维码,发个朋友圈(别问我为啥这么清楚,问就是我有个朋友)。这种时候我们完全没接触到签名啊。

当时的我认为只要不发布到各种应用商店就可以不用签名的,我上官方文档查了下,发现了下面这段话:

Android 系统要求所有 APK 必须先使用证书进行数字签名,然后才能安装到设备上或进行更新。

我们一般调试build出的apk都是app-debug.apk,按照上文的说法呢!系统应该是自动帮我们进行了签名,但签名的文件呢?没想到那么好找,我一点开.android文件夹就看到了debug.keystore,没错这就是本机默认的数字签名。

您首次在 Android Studio 中运行或调试项目时,IDE 会自动在 $HOME/.android/debug.keystore 中创建调试密钥库和证书,并设置密钥库和密钥密码。

官文中也能找到对应的解释(而且还告诉了我们在首次运行和调试的时候自动创建的,这就意味着你安装IDE的时候是不存在的,也就是说每台机器产生的调试密钥库和证书应该是不同的)

我们查看下这里面的内容:

嗯好,看完就到了解决最初问题的时候了。

签名有什么用?

官文中的签名注意事项明确的告诉了我们签名的作用。

主要有以下三点:

  • 应用升级:当安装应用的更新时,系统会比较新版本和现有版本中的证书。如果证书匹配,则系统允许更新。如果使用不同的证书为新版本签名,您必须为应用分配另一个软件包名称 - 在此情况下,用户会将新版本作为全新应用进行安装。
  • 应用模块化:Android 允许通过同一证书签名的多个 APK 在同一个进程中运行(如果应用请求这样做),以便系统将其视为单个应用。这样一来,您便可以按模块部署您的应用,并且用户可以独立更新每个模块。
  • 通过权限共享代码/数据:Android 提供了基于签名的权限执行机制,以便一个应用可以将功能提供给使用指定证书签名的另一个应用。通过使用同一个证书为多个 APK 签名并使用基于签名的权限检查功能,您的应用可以采用安全的方式共享代码和数据。

在实际的应用中呢,我遇到了这种情况,当前的设备上已经安装该应用的apk,但公司叫我重新开发了,我开发完成后直接使用app-debug.apk在设备上进行安装,就出现了该错误。(解决方法一,卸载了原程序,再安装,但这就将缓存的数据给清空了。这让我想到了“明日方舟”,每次大的版本更新都要将原程序卸载了,安装新的apk,安装后得重新登录)(解决方法二,将签名一起发给程序猿——我们,打包的时候使用相同的签名)

这就体现出了签名最常用的作用之一“应用升级”。这也是我想了解签名与写下这篇博客的导火索之一。

签名能保障apk的安全吗?

回答这个问题呢,就需要去了解几个名词:

证书 公钥证书(.der 或 .pem 文件,也称为数字证书或身份证书)包含公钥/私钥对中的公钥,以及可以标识持有对应私钥的所有者的一些其他元数据(例如名称和位置)。

在为您的应用签名时,签名工具会将该证书附加到应用。该证书会将 APK 或 app bundle 与您和您对应的私钥相关联。这有助于 Android 确保您应用日后的所有更新都真实可靠,并且来自原始作者。用于创建此证书的密钥称为应用签名密钥

您可以从 Play 管理中心的“应用签名”页面下载您的应用签名密钥和上传密钥的证书,以便向 API 提供商注册您的密钥。您可以与任何人共享该证书。它不包含您的私钥。

每个应用在其整个生命周期内必须使用同一证书,以便用户能够以应用更新的形式安装新版本。要详细了解让所有应用在其整个生命周期内使用同一证书的好处,请参阅上面的签名注意事项

证书指纹是证书独一无二的简短表示形式,通常 API 提供商会要求同时提供证书指纹和软件包名称,以注册使用其服务的应用。您可以在 Play 管理中心的“应用签名”页面上找到上传证书和应用签名证书的 MD5、SHA-1 和 SHA-256 指纹。您还可以从同一页面下载原始证书 (.der) 来计算其他指纹。

以下是您应该了解的不同类型的密钥和密钥库:

  • 应用签名密钥:用于为用户设备上安装的 APK 签名的密钥。作为 Android 安全更新模型的一部分,应用签名密钥在应用的整个生命周期内保持不变。应用签名密钥属于私钥,因此必须保密。不过,您可以与他人共享使用应用签名密钥生成的证书。

  • 上传密钥:在为 Google Play 应用签名计划上传 app bundle 或 APK 之前用于为其签名的密钥。您必须为上传密钥保密。不过,您可以与他人共享使用上传密钥生成的证书。您可以通过以下某种方式生成上传密钥:

    • 如果要让 Google 在您选择加入计划时为您生成应用签名密钥,则您用于为应用签名以进行发布的密钥将被指定为上传密钥。
    • 如果您在将新应用或现有应用加入计划时向 Google 提供应用签名密钥,则可以在选择加入计划的过程中或之后生成新的上传密钥,以提高安全性。
    • 如果您没有生成新的上传密钥,则继续将您的应用签名密钥用作上传密钥来为每个版本签名。
    • 提示:为了让您的密钥安全无虞,最好确保应用签名密钥和上传密钥不同。
  • Java 密钥库(.jks 或 .keystore):一个二进制文件,用作证书和私钥的存储区。

  • Play Encrypt Private Key (PEPK) 工具:使用此工具可从 Java 密钥库中导出私钥,并对私钥加密以便传输到 Google Play。在提供应用签名密钥以供 Google 使用时,请选择从 Java 密钥库导出并上传密钥选项,并按说明下载和使用此工具。或者,您也可以选择导出并上传密钥(不使用 Java 密钥库)选项,以下载、查看和使用 PEPK 工具的开放源代码。

(以上都是官文对应用签名要掌握的知识点的概述——原文引用来自密钥、证书和密钥库

通过上文可以总结出:Android签名机制不能阻止APK包被修改,但修改后的再签名无法与原先的签名保持一致(除非你拥有私钥)。也就是说如果你的软件还是会被反编译的,但其他人反编译后再打包的话其实是另一个应用。(比如我们原来玩的诺基亚小游戏的时候经常会有些是汉化版,然后我们下下来是不会替换掉原来的程序,我就想起当时玩一个叫“上古2”的网游,下了个双开的版本,现在才知道原来那个被破解了然后重新打包的应用啊)

综上所述:签名机制其实是对APK包完整性和发布机构唯一性的一种校验机制。能简单的保证apk的安全,但很有限,应该说是一个软件最基本的防御。我们可以适当的提升下安全系数,在程序运行的时候进行签名对比,但被破解后,让程序跳过签名对比的部分,就能破坏了这最基本的防御。

签名的原理:我们可以看debug.keystore中写了签名算法名称:SHA1withRSA。 可以得出该apk是使用SHA1-RSA算法,用私钥进行签名的。

SHA-1:是一种密码散列函数,虽然该密码现在已经不够安全了,但还在被广泛的使用。它把任意长度的输入,通过散列算法变成固定长度的输出。

RSA:是一种非对称加密算法。用私钥通过RSA算法对SHA-1的输出信息进行加密。

在安装时只能使用公钥才能解密RSA加密后的信息。解密之后,将它与SHA-1的输出信息进行对比,如果相符,则表明内容没有被异常修改。

混淆

混淆处理:缩短类和成员的名称,从而减小 DEX 文件的大小。

混淆处理的目的是通过缩短应用的类、方法和字段的名称来减小应用的大小。

虽然混淆处理不会从应用中移除代码,但如果应用的 DEX 文件将许多类、方法和字段编入索引,那么混淆处理将可以显著缩减应用的大小。 不过,由于混淆处理会对代码的不同部分进行重命名,因此在执行某些任务(如检查堆栈轨迹)时需要用到额外的工具。

此外,如果您的代码依赖于应用的方法和类的可预测命名( 例如,使用反射时),您应该将相应签名视为入口点并为其指定保留规则。

可以得到混淆的目的就是缩小应用的大小,但很多地方都说混淆可以保护代码的安全,因为他降低了代码的可读性,让反编译后的破解得花费一些时间。这虽然可能不是混淆出现的目的之一,但人们将他活学活用了,发现了它另外的价值。

混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。

我曾经因为觉得某app的某功能实现效果特别好,然后将其反编译后,发现混淆真的很有用,可能应为我看小说就几个名字看了一半后都除了主角,其他人都对不上号吧,经常还倒回去找下这个人是谁?(要是他是英文的名字,我更加记不住了,可能都不会回去找,这种情况就需要一张纸写下相应名字间的关系一起夹在书里了,这可能也算“混淆”吧)

总结

最后呢!就是市面上应该没有一款软件是100%安全的,只是他们的加密手段复杂可能还会随时间修改,破解它可能是成本问题。

再最后呢!PC上的反编译软件大家都很熟悉(apktool + dex2jar + jd-gui)

手机上呢我挺喜欢一款叫“开发助手”的软件的(好像是某滴滴大佬开发的),能看应用布局、屏幕取色、app用的什么加密软件等等,功能性个人认为比“MT管理器”要好吧。

Android studio 中我们可以使用 build -> Analyze APK... 去分析apk,然后去进一步优化我们apk的大小。