apk本质上都是zip包,过解压工具打开apk文件,会发现有一个META-INF目录,该目录中有3个文件MANIFEST.MF、CERT.SF、CERT.RSA,这3个文件是签名以后生成的,显然与签名相关。
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编码的,通过shasum res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png 计算sha1值,计算出的sha1值是经过16进行编码的,再把它转成base64编码即KQunCQh0E4bP0utgN0cHdQr9OwA=,可以通过在线工具进行转换:tomeko.net。
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文件相应条目再次进行了摘要计算。
这里要注意下,.MF文件是以空行分隔的。计算.MF各条目摘要时需要再加一个换行符,因为空行还有一个换行符(具体可参考apksigner源码)。我们把abc_list_longpressed_holo.9.png条目保存到一个新文件中,先计算这个条目的sha1值,计算出的sha1值是经过16进行编码的,再把它转成base64编码即xcQ0bHWRc+R9tuxQ3wgY1a2eY0k=
1.3 CERT.RSA
cert.rsa中的是二进行内容,里面保存了签名者的证书信息,以及对cert.sf文件的签名。具体证书包含的内容已经在Android系统安全 — 5.0-APK签名机制原理中作了说明,这里不再重复介绍。
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();
......
}
3. 安装校检过程
安装apk的入口在frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java的installPackageLI方法。(注:这里参考的是10版本的源码)
private void installPackagesLI(List<InstallRequest> requests) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
for (InstallRequest request : requests) {
// TODO(b/109941548): remove this once we've pulled everything from it and into
// scan, reconcile or commit.
final PrepareResult prepareResult;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
prepareResult = preparePackageLI(request.args, request.installResult);
} catch (PrepareFailure prepareFailure) {
request.installResult.setError(prepareFailure.error,
prepareFailure.getMessage());
request.installResult.origPackage = prepareFailure.conflictingPackage;
request.installResult.origPermission = prepareFailure.conflictingPermission;
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
}
校验apk中各文件的hash和.mf、.sf、.rsa相关文件的详细流程,有兴趣的可以追踪代码分析。
4. JAR V1签名机制的劣势
- 签名校验速度慢
校验过程中需要对apk中所有文件进行摘要计算,在apk资源很多、性能较差的机器上签名校验会花费较长时间,导致安装速度慢;
- 完整性保障不够
META-INF目录用来存放签名,自然此目录本身是不计入签名校验过程的,可以随意在这个目录中添加文件,比如一些快速批量打包方案就选择在这个目录中添加渠道文件。
所以,从android7.0开始启用了V2签名。
注: 这是参考相关文档总结出的精华,若有侵权问题,请立即联系我删除该文档