转载请注明出处:https://blog.youkuaiyun.com/zwjemperor/article/details/80877305
github:https://github.com/rushgit/zhongwenjun.github.com
在APK签名机制原理详解中我们已经了解了APK签名和校验的基本过程,这一篇我们来分析JAR签名机制。JAR签名对对jar包进行签名的一种机制,由于jar包apk本质上都是zip包,所以可以应用到对apk的签名。本文从JAR签名结构、签名过程,再到签名校验的源码分析,全方面来分析Android中JAR签名及校验的机制。
1. 签名过程
通过解压工具打开apk文件,会发现有一个META-INF目录,该目录中有3个文件,这3个文件是签名以后生成的,显然与签名相关,我们依次看这几个文件中的内容。
1.1 文件内容解析
1.1.1 先看MANIFEST.MF:
Manifest-Version: 1.0
Created-By: 1.8.0_92 (Oracle Corporation)
Name: res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png
SHA1-Digest: KQunCQh0E4bP0utgN0cHdQr9OwA=
Name: res/drawable-xxhdpi-v4/abc_ic_star_half_black_16dp.png
SHA1-Digest: EikVyBT5I7pmbJO2k8qF0V5hUc0=
......
这个文件列出了apk中所有的文件,以及它们的摘要,摘要字符串是通过base64编码的,通过计算来验证下:
先计算出res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png的sha1值。
计算出的sha1值是经过16进行编码的,再把它转成base64编码,可以通过在线工具进行转换:tomeko.net
可以看到转换过后的base64值和MANIFEST.MF文件中内容是一样的。
1.1. 2 再看CERT.SF
Signature-Version: 1.0
SHA1-Digest-Manifest: odZIAbrTVCfKGy6HEd5+gdBHw0I=
Created-By: 1.8.0_92 (Oracle Corporation)
Name: res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png
SHA1-Digest: xcQ0bHWRc+R9tuxQ3wgY1a2eY0k=
Name: res/drawable-xxhdpi-v4/abc_ic_star_half_black_16dp.png
SHA1-Digest: pj+V2r2pJOgJwGGNpeqxnykl0Nc=
......
SF文件的内容和MF比较相似,同样包含了apk所有文件的摘要,不同的是:
- SF文件在主属性中记录了整个MF文件的摘要(SHA1-Digest-Manifest)
- SF文件其余部分记录的是MF相应条目的摘要,也就说对MF文件相应条目再次进行了摘要计算。
我们再来验证下,首先计算出MANIFEST.MF文件的sha1值,再转换base64编码:
再来验证res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png
这里要注意下,.MF文件是以空行分隔的。计算.MF各条目摘要时需要再加一个换行符,因为空行还有一个换行符(具体可参考apksigner源码)。我们把abc_list_longpressed_holo.9.png条目保存到一个新文件中,先计算这个条目的sha1值:
再把sha1值转为base64编码:
1.1.3 再看CERT.RSA
cert.rsa中的是二进行内容,里面保存了签名者的证书信息,以及对cert.sf文件的签名。具体证书包含的内容已经在APK签名机制原理详解中作了说明,这里不再重复介绍。
1.2 整体签名过程
具体签名过程如下:
具体过程可参考apksigner源码
public static void main(String[] args) {
......
//生成MANIFEST.MF文件,遍历apk的所有文件,计算除META-INF目录下的
//.SF/.RSA/.DSA文件外所有文件的摘要。
JarEntry je;
Manifest manifest = addDigestsToManifest(inputJar);
// MANIFEST.MF
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
// 生成CERT.SF文件
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//计算MF文件摘要,及MF相应条目的摘要
writeSignatureFile(manifest, baos);
//计算SF文件的摘要
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
// 生成CERT.RSA
// 对SF文件的摘要(signedData)进行签名,将证书信息一同写入RSA文件中
je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey, privateKey, outputJar);
outputJar.close();
......
}
2. 校验过程
上面说的是签名过程,接下来看apk安装过程是怎样进行签名校验的。校验过程和签名过程刚好相反:
首先校验cert.sf文件的签名
计算cert.sf文件的摘要,与通过签名者公钥解密签名得到的摘要进行对比,如果一致则进入下一步;
校验manifest.mf文件的完整性
计算manifest.mf文件的摘要,与cert.sf主属性中记录的摘要进行对比,如一致则逐一校验mf文件各个条目的完整性;
校验apk中每个文件的完整性
逐一计算apk中每个文件(META-INF目录除外)的摘要,与mf中的记录进行对比,如全部一致,刚校验通过;
校验签名的一致性
如果是升级安装,还需校验证书签名是否与已安装app一致。
以上步骤需要全部通过才算签名校验通过,任何一步失败都将导致校验失败。这个过程能保证apk不可被篡改吗?我们来看看篡改apk内容会发生什么:
篡改apk内容
校验apk中每个文件的完整性时失败;如果是添加新文件,因为此文件的hash值在.mf和.sf中无记录,同样校验失败;
篡改apk内容,同时篡改manifest.mf文件相应的摘要
校验manifest.mf文件的摘要会失败;
篡改apk内容,同时篡改manifest.mf文件相应的摘要,以及cert.sf文件的内容
校验cert.sf文件的签名会失败;
把apk内容和签名信息一同全部篡改
这相当于对apk进行了重新签名,在此apk没有安装到系统中的情况下,是可以正常安装的,这相当于是一个新的app;但如果进行覆盖安装,则证书不一证,安装失败。
从这里可以看出只要篡改了apk中的任何内容,都会使得签名校验失败。
3. 签名校验代码分析
前面介绍了签名和校验的整体流程,现在来看校验过程的代码实现。安装apk的入口在frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java的installPackageLI方法。(注:这里参考的是5.1.1版本的源码)
在这方法中构造了一个PackageParser对象,这个类是用来解析apk文件的,具体的签名校验在这个对象的collectCertificates方法中。
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
......
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mS