文章大纲
引言
在进行 Android 应用开发的时候,尤其是开发的是系统应用,就需要有系统签名才能正常运行。对于开发ROM来说,我们开发的App一般是放到系统代码库中跟随系统一起编译为系统应用的,编译时会通过源码里的脚本自动进行签名。如果想要用 Android Studio 单独对某个开发的应用进行签名的话,必须需要使用和Android 系统一模一样的签名文件,这是Android 安全机制的方面之一。同时这也是开发ROM固件时App 一般都是使用源码中编译方式,而很少直接使用使用 IDE 编译,然后 push 到 /system/app/或者/system/priv-app/下,然后adb reboot的原因之一(当然如果你push 的是使用系统keysotre文件签名的apk 是完全ok的,最好把xxx目录下的ota文件夹删除)。
Android系统禁止更新安装签名不一致的APK,另外如果应用还需要使用system权限,必须保证Apk签名与Android 系统签名一致且在 AndroidManifest.xml 清单文件的manifest节点下声明:android:sharedUserId=“android.uid.system”
以后更多精选系列文章以高度完整的系列形式发布在公众号,真正的形成一套完整的技术栈,欢迎关注,目前第一个系列是关于Android系统启动系列的文章,大概有二十几篇干货:
一、Android 签名机制
截至目前为止Android中一共提供了三种应用签名方案:
- V1——基于JAR签名,v1 签名保护的是APK 压缩包下的所有文件(只要原来的文件的签名信息校验通过就认为合法),但是如果在原有APK 压缩包里添加文件是不影响校验的(而删除文件是影响的)
- v2+——搭载 Android 7.0 及更高版本的设备支持 APK 签名方案 v2(v2 方案)及更高版本的方案(在 Android 9 中,v2 方案已更新为 v3 方案,以便在签名分块中包含其他信息,但在其他方面保持相同的工作方式)。该方案会对 APK 的内容进行哈希处理和签名,然后将生成的“APK 签名分块”插入到 APK 中。在验证期间,v2+ 方案会将 APK 文件视为 blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改。新的签名格式向后兼容,因此,使用这种新格式签名的 APK 可在更低版本的 Android 设备上进行安装(会直接忽略添加到 APK 的额外数据),但前提是这些 APK 还带有 v1 签名。
为了保持与 v1 APK 格式的向后兼容性,v2 和 v3 APK 签名存储在“APK 签名分块”内紧邻 ZIP Central Directory 前面。v3 APK 签名分块的格式与 v2 相同。APK 的 v3 签名会存储为一个“ID-值”对,其中 ID 为 0xf05368c0。v3 方案的设计与 v2 方案非常相似,它们采用相同的常规格式,并支持相同的签名算法 ID、密钥大小和 EC 曲线。但是,v3 方案增添了有关受支持的 SDK 版本和 proof-of-rotation 结构的信息。v3 在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 结构的信息。
1、生成密钥对keystore
默认情况下直接通过Android Studio在Run或Debug时,会使用默认的C:\Users\用户名.android\debug.keystore密钥库对App签名:
密钥库名——debug.keystore
密钥别名——androiddebugkey
密钥库密码——android
JDK 自带的keytool工具,执行以下指令
位于\jdk1.8.0_91\bin
keytool -genkeypair -keystore 密钥库名字及存储位置 -alias 密钥别名 -validity 有效天数 -keyalg RSA
其中别名作用在于当密钥库可以存在多个密钥对,可以区分不同密钥对,算法只支持RAS和DSA 。因为可重复使用此命令,在同一密钥库中创建多条密钥对,例如在 debug.keystore 中新增一对密钥,别名是cmo
keytool -genkeypair -keystore debug.keystore -alias cmo -validity 3600
创建完毕后,可以查看密钥对信息
keytool -v -list -keystore keystore文件路径
keytool 工具支持的命令行参数:
2、Android 签名V1
JDK自带的jarsigner 是针对jar包签名的通用工具,也是Android 早期使用的签名工作,即V1。
jarsigner位于 JAVA_HOME/bin/jarsigner.exe,从JDK7开始, jarsigner默认算法是SHA256, 但Android 4.2以下不支持该算法。
在进行V1签名时jarsigner.exe会对压缩包里的每个文件(除了META-INF 文件)进行验证并生成SHA1指纹,V1签名是对压缩包中单个文件逐一进行签名验证,签名后还能对压缩包修改(移动/重新压缩文件)。无论是 apk 包,还是 jar 包,本质上都是一样的,都是压缩文件。因此它们的签名过程都大同小异(仅限V1签名),V1签名本质就是给Apk的文件里,增加一些校验信息(即MATA-INF文件夹里的一些文件),所以Apk压缩文件里的MATA-INF保存着有三个文件:MANIFEST.MF、CERT.SF和CERT.RSA
- MANIFEST.MF——Framework 遍历Apk包中的所有文件(entry),对非文件夹非签名文件的文件(图片、资源xml、so、.dex)逐个生成SHA1的数字签名信息,再用Base64进行编码之后将生成的签名写入MANIFEST.MF文件,即保存所有文件的 SHA1 指纹(除了 META-INF 文件)。
SHA1数字签名——一种安全哈希算法,类似于MD5算法,可以把任意长度的输入,通过散列算法变成固定长度的输出(即“摘要”)即每个文件都对应一个SHA256的数字签名,经过Base64 编码后得到cert.sf文件。但仅依赖这个摘要信息不能复原原来的信息且不同信息的摘要互不相同。因此,如果原Apk的文件被改变了,那么在Apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同导致安装失败。
部分结构如下图:
- CERT.SF——对前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用私钥进行签名,其中的值是对清单文件里的SHA256 再进行SHA256 后再次Base64得到。
RSA是一种非对称加密算法。用私钥通过RSA算法对摘要信息进行加密。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息进行对比,如果相符,则表明内容没有被异常修改
- CERT.RAS——生成MANIFEST.MF没有使用密钥信息,而生成CERT.SF文件使用了私钥文件。CERT.RSA文件中保存了公钥、所采用的加密算法等信息 ,Framework会解析这个RAS文件。
其实Android Studio 或者其他打包Apk的软件本质上都是通过执行jarsigner指令:
jarsigner -keystore 密钥库名 xxx.apk 密钥别名
//兼容android 4.1以下
jarsigner -keystore 密钥库名 -digestalg SHA1 -sigalg SHA1withRSA xxx.apk 密钥别名
jarsigner -keystore debug.keystore -digestalg SHA1 -sigalg SHA1withRSA app.apk mydebugkey
若密钥库中有多个密钥对,则必须指定密钥别名。
3、Android 签名V2+
apksigner 是Google 官方提供的针对 Android apk 签名及验证的专用工具,默认同时使用V1和V2签名,以兼容 Android7.0以下系统版本,即V2。
apksigner 位于 Android SDK/build-tools/SDK 版本 /apksigner.bat
apksigner是对 zip 压缩包的整个文件验证,签名后不能修改压缩包(包括 zipalign ),因此
签名更安全(不能修改压缩包且签名验证时间更短(不需要解压验证),因此安装速度更快
apksigner sign --ks 密钥库名 --ks-key-alias 密钥别名 xxx.apk
//禁用v2
apksigner sign --v2-signing-enabled false --v1-signing-enabled true --ks 密钥库名 xxx.apk
apksigner sign --v1-signing-enabled false --ks debug.keystore --ks-key-alias mydebugkey app.apk
若密钥库中有多个密钥对,则必须指定密钥别名
4、zipalign 、V1、V2+签名
zipalign位于android-sdk/build-tools目录下,主要对APK进行对齐处理,对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快,对齐的作用就是减少运行时内存的使用,如果要上传到Google Play必须进行对齐操作。
zipalign -v -p 4 demo_unsigned.apk demo_signed.apk
zipalign -c -v 4 demo_unsigned.apk //检查 APK 是否对齐
需要注意的是zipalign 可以在V1签名后执行,但 zipalign 不能在V2签名之后执行,只能在V2签名之前执行!
5、签名的校验
如果想对一个APK 检验是否已经签名成功,不同的版本使用的工具不一样。当Android 系统版本在7.0以上是时默认编译打包的时候会使用v2 签名,同时存在v1 和v2 时会优先校验v2。
5.1、对V1进行校验
首先最简单的上就是使用JDK 自带的keytool工具
位于\jdk1.8.0_91\bin
keytool -printcert -jarfile xxxx.apk
还可以通过Android Studio 自带的“Analyze Apk ”功能可以直接打开并分析apk的组成,也可以直接在AS 里双击apk或者把外部的apk拖到AS里,一般apk 签名成功之后就会在apk包下的MATA-INF保存着签名文件CERT.RSA和CERT.SF,如果没有这个文件说明没有进行签名
默认情况下AS 的assemble gradle task 是不会进行签名的,即使配置了singingConfigs
通过keytool工具查看 /MATA-INF/CERT.RSA文件
keytool -printcert -file META-INF/CERT.RSA
简而言之,v1检验是基于 /MATA-INF/MANIFEST.MF文件的,Framework 会在安装APK时候判断你有没有签名,如果签名版本是V1则会去遍历 /MATA-INF/MANIFEST.MF文件,逐一去校验数字签名信息和当前安装包里文件的数字签名信息。
5.2、对V2+ 进行校验
apksigner verify -v --print-certs xxxx.apk
简而言之,v2+ 签名是在原有ZIP文件格式的基础上,插入一个签名分块区域,记录签名信息。
6、Android 签名的作用
- Android签名机制其实是对Apk包完整性和发布机构唯一性的一种校验机制。
- Android签名机制不能阻止Apk包被修改,但修改后的再签名无法与原先的签名保持一致。(拥有私钥的情况除外)。
- Apk包加密的公钥就打包在Apk包内,且不同的私钥对应不同的公钥。换言之,不同的私钥签名的Apk公钥也必不相同。所以可以根据公钥的对比,来判断私钥是否一致
7、签名版本的选择
根据项目灵活地选择V1和V2版本签名,可以在一定程度上让你的APK瘦身成功,简而言之,V1就是在原有的压缩包里META-INF生成签名文件,而V2则是向整个压缩包文件按照固定格式插入字节码数据。
- 只进行V2签名的APK只能运行于Android API 大于等于7.0的设备上。
- 只进行V1签名可以运行全版本
- 同时进行V1和V2签名也可以运行全版本,更安全但压缩包也相对来说最大。
8、Apk安装签名解析
Android系统安装程序肯定会获取APK信息进行比对,所以]可以通过Android源码获得一些思路和帮助。Apk安装是PackageParser负责解析安装包,PackageParser负责AndroidManifest(applicationInfo、Version、mSharedUserId、permissions、四大组件等)、签名读取解析
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
parsed = parseMonolithicPackage(packageFile, flags);
}
long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
cacheResult(packageFile, flags, parsed);
if (LOG_PARSE_TIMINGS) {
parseTime = cacheTime - parseTime;
cacheTime = SystemClock.uptimeMillis() - cacheTime;
if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
+ "ms, update_cache=" + cacheTime + " ms");
}
}
return parsed;
}
篇幅问题不再展开,未完待续…
/frameworks/base/core/java/android/content/pm/PackageParser.java
二、用 platform.pk8 和 platform.x509.pem 生成 keystore 系统签名文件
如果想要用 Android Studio 单独对某个开发的应用使用Android系统keystore文件进行签名的话,首先得自己获取系统keystore文件,Android 中不同的system 权限使用不同的keystore,每个密钥都包含两个文件:扩展名为 .x509.pem 的证书和扩展名为 .pk8 的私钥。私钥需要加以保密,并用于对 apk 包进行签名。密钥本身也可能受密码保护。而证书只包含公开的一半密钥,因此可以大范围地分发,证书被用于验证某个 apk 包是否由相应的私钥进行签名。标准 Android 版本使用四个密钥,所有这些密钥都位于 build/target/product/security 中:
- testkey(testkey.pk8)——适用于未另外指定密钥的 apk 包的通用默认密钥。
- 平台(platform.pk8)——适用于核心平台所包含的 apk 包的测试密钥。
- 共享(shared.pk8)——适用于家庭/联系人进程中的共享内容的测试密钥。
- 媒体(media.pk8)——适用于媒体/下载系统所包含的 apk 包的测试密钥
通过系统的 .pk8 和 .x509.pem 转换成为我们可以直接使用的 keystore 文件,而将.x509.pem 和.pk8导入keystore文件、或者.jks文件,需要openssl工具 、密钥和证书管理工具keytool ,并配置好环境变量
1、在源码的/build/target/product/security下获取platform.pk8 和 platform.x509.pem
platform.pk8 和 platform.x509.pem 存在于源码项目里/build/target/product/security/下。
Android 使用公开指数为 3 的 2048 位 RSA 密钥即.pk8,可以使用 openssl.org 提供的 openssl 工具来生成证书/私钥对:
- generate RSA key
openssl genrsa -3 -out crazymo.pem 2048
- create a certificate with the public part of the key 创建证书
openssl req -new -x509 -key crazymo.pem -out crazymo.x509.pem -days 1000000 -subj '/C=CN/ST=ZJ/L=SH/O=crazymo, Inc./OU=crazymo Mobility/CN=crazymo/emailAddress=crazymo@google.com'
- create a PKCS#8-formatted version of the private key 创建 .pk8
openssl pkcs8 命令可创建一个适用于该版本系统的 .pk8 文件
//未设置密码
openssl pkcs8 -in crazymo.pem -topk8 -outform DER -out crazymo.pk8 -nocrypt
或者
//需要设置密码
openssl pkcs8 -in crazymo.pem -topk8 -outform DER -out crazymo.pk8 -passout stdin
2、openssl把pkcs8格式的私钥转化成pkcs12格式
openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt
3、openssl 把x509.pem公钥转换成pkcs12格式
需要输入密码:android
openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name androiddebugkey
4、生成platform.keystore
keytool -importkeystore -deststorepass android -destkeypass android -destkeystore platform.keystore -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias androiddebugkey
如果没有使用系统原生的keystore签名文件进行签名,即使你的申请system权限的Apk本身没有问题且也使用其他keystore进行了正确的签名安装到一些ROM会导致以下错误。
Y:\MoVoice>adb install -r MoAIVoiceService.apk
Performing Streamed Install
adb: failed to install MoAIVoiceService.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Packag
e /data/app/vmdl428612103.tmp/base.apk has no certificates at entry AndroidManifest.xml]
[ INSTALL_PARSE_FAILED_NO_CERTIFICATES : Packag
e /data/app/vmdl428612103.tmp/base.apk has no certificates at entry AndroidManifest.xml]
三、使用系统keystore对App 进行签名
无论是何种keystore,都可以用两种形式对App 进行签名。
1、利用SDK 自带的signapk.jar和platform.pk8 与platform.x509.pem 进行签名
signapk.jar位于/prebuilts/sdk/tools/lib
为了方便把签名证书platform.pk8 、platform.x509.pem ,签名工具signapk.jar 放置在同一个文件夹,然后执行以下指令通过signapk.jar可执行包,以platform.x509.pem证书文件和platform.pk8私钥文件对源.apk进行签名,签名后的文件保存为目标.apk
1.1、 windows环境下
java -jar signapk.jar platform.x509.pem platform.pk8 源.apk 目标.apk
- 源.apk——预进行签名的Apk ,指的是你将要使用系统签名文件的源Apk,需要替换为你自己Apk的名称
- 目标.apk——签名成功后的Apk
1.2、 Ubuntu 编译环境执行
java -jar out/host/linux-x86/framework/signapk.jar build/target/product/security/platform.x509.pem build/target/product/security/platform.pk8 预进行签名.apk 签名后.apk
2、通过Android Studio 进行签名
通过Android Studio 工具栏Build——>Generate Signed Bundle or APK
选择APK
然后Next——>Finish。
四、 Android自定义系统签名制作介绍(覆盖模式)
1、系统中有4组key用于build阶段对apk进行签名:
- Media
- Platform
- Shared
- Testkey
default key是放在Android源码的/build/target/product/security目录下:
- media.pk8与media.x509.pem;
- platform.pk8与platform.x509.pem;
- shared.pk8与shared.x509.pem;
- testkey.pk8与testkey.x509.pem;
其中,*.pk8
文件为私钥,*.x509.pem
文件为公钥,这需要去了解非对称加密方式。
可以在apk的android.mk文件中会指定LOCAL_CERTIFICATE 变量:
- LOCAL_CERTIFICATE := testkey ——普通APK,默认情况下使用
- LOCAL_CERTIFICATE := platform —— 该APK完成一些系统的核心功能,这种方式编译出来的APK所在进程的UID为system
- LOCAL_CERTIFICATE := shared ——该APK是media/download系统中的一环
- LOCAL_CERTIFICATE := media —— 该APK是media/download系统中的一环
如果不指定,默认使用testkey。对应的,除了在Android.mk指定上述的值,还需要在APK源码的AndroidManifest.xml文件的manifest节点里面申明权限:
- android:sharedUserId=“android.uid.system”
- android:sharedUserId=“android.uid.shared”
- android:sharedUserId=“android.media”
Build规则是Build/core/prebuilt.mk。在build/core/config.mk中,
DEFAULT_SYSTEM_DEV_CERTIFICATE
可以通过PRODUCT_DEFAULT_DEV_CERTIFICATE
去指定各家厂商的key path。
2、签名生成
Android源码提供了自定义系统签名的工具(/development/tools)make_key,使用如下命令
development/tools/make_key testkey '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
可以生成testkey.pk8、testkey.x509.pem公私钥 ,其中第一个参数(testkey)是需要生成的签名名称,第二个参数是公司信息。
代号 | 说明 |
---|---|
C | 国家 (2 letter code) |
ST | 省 (full name) |
L | 城市 (eg, city) |
O | 组织 (eg, company) |
OU | 部门 (eg, section) |
CN | 通用名称 (eg, your name or your server’s hostname) |
emailAddress | 邮箱 |
3、签名覆盖
最后可以使用:openssl pkcs8 -inform DER -nocrypt -in testkey.pk8 -out testkey.pem导出私钥信息,将生成的签名文件(秘钥对)copy到build/targets/product/security/目录下即可。
五、Android Studio 调试签名Apk
首先在Module下的build.gradle脚本里配置,这样子通过Android Studio在Run或Debug时,就会使用你配置的keystore。
默认情况下Android Studio 的assemble gradle task 是不会进行签名的,即使配置了singingConfigs,所以如果要打包签名APK 最好直接通过Android Studio 的图形界面。
android{
...
signingConfigs {
releae {
storeFile file('Z:\\MoCode\\Keystore\\platform.keystore')
storePassword 'android'
keyAlias 'platform'
keyPassword 'android'
}
debug {
storeFile file('Z:\\MoCode\\Keystore\\debug.keystore')
storePassword 'android'
keyAlias 'debug'
keyPassword 'android'
}
}
...
}
这样配置后再通过Android Studio Run或者Debug进行安装,就可以会通过配置的签名文件进行签名并自动安装到设备上(若一般我们的服务是没有activity的话,还需要设置Luncher 为nothing)。最后附一个 Android 原生的platform 权限所用的签名文件。