本文主要是介绍Android10.0应用安装白名单---添加签名校验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
背景
为了避免系统被安装上各种各样的app,客户要求系统需要有个安装白名单的功能。
文章目录
- 背景
- 思路
- Android应用签名
- apk安装白名单进行签名校验
- 获取apk签名的证书指纹
- android源码中获取证书指纹
- 签名校验
- 结语
- 微信公众号
思路
白名单功能主要是通过确认要安装的应用是否在白名单上,如果不在,则不允许安装。筛选的标准可以通过包名进行判断。但单纯包名进行判断还是不够安全,这里是想再加个签名校验的机制,毕竟每个签名都是独一无二的。
这个过程,主要难点在于如何获取各个apk签名。在说明如何在代码中获取到系统签名之前,先大概说下Android应用的签名机制。
Android应用签名
签名的基本目的就是不允许apk内部任意一个文件的内容被篡改。所以得保证每个文件都被加密到。
将任意一个签过名的apk进行解压,都可以发现一个叫META-INF的文件夹,该文件夹包括但不限于如下三个文件:
CERT.RSA CERT.SF MANIFEST.MF
1.MANIFEST.MF文件。对APK包中每个文件(文件夹和签名文件除外)进行遍历,使用SHA1或者SHA256生成摘要信息。然后再用base64进行编码,截取MANIFEST.MF文件中某一段:
Name: AndroidManifest.xml
SHA1-Digest: D2yOY7wstlBC3AbjQznUDa6/8Xw=
这里我们可以自己对AndroidManifest.xml这个文件通过SHA1生成摘要信息,有在线的SHA1:
http://www.metools.info/code/c92.html
拿到摘要后,可以通过在线base64编码,将此摘要进行编码:
http://tomeko.net/online_tools/hex_to_base64.php?lang=en
可以看到这里获取到的base64编码结果跟MANIFEST.MF文件描述的一样。
其余文件的base64编码也是如此计算得来。从这里就知道,如果apk中的文件被篡改了,则最终得到的base64编码跟MANIFEST.MF文件描述不一样,就会导致安装出错。当然,也可以手动计算出所修改文件的base64编码,然后更新到MANIFEST.MF文件中,但也会功亏一篑,因为还有CERT.RSA和CERT.SF。
2.CERT.SF文件。截取部分该文件:
Signature-Version: 1.0
SHA1-Digest-Manifest: iaeBE2KJWElTbmMNGnjxretMvz8=
Created-By: 1.0 (Android SignApk)Name: kotlin/collections/MapWithDefault.kotlin_metadata
SHA1-Digest: je8WuIzQjM5yX2bkDmjjyviMxOE=Name: kotlin/coroutines/intrinsics/CoroutinesIntrinsicsHKt.kotlin_metadata
SHA1-Digest: reRpbwjfyR7tladgIdLzjpREduA=Name: res/interpolator/btn_checkbox_checked_mtrl_animation_interpolator_0.xml
SHA1-Digest: 9Lo7vzAcNsmM/qCg9/iamygexzk=
这个文件是对MANIFEST.MF文件中的每一项再做一次SHA1计算,然后再进行base64编码。另外还对MANIFEST.MF文件的内容进行SHA1计算,然后进行编码,将该值写到SHA1-Digest-Manifest中:
SHA1-Digest-Manifest: iaeBE2KJWElTbmMNGnjxretMvz8=
这里对MANIFEST.MF文件进行了一层篡改保护,只要MANIFEST.MF文件被修改过,那最后得到的base64编码肯定对不上。当然,到此为止,如果只是做到这一步,那还是可以被篡改的。关键看看CERT.RSA文件。
3.CERT.RSA文件。该文件保存了公钥,所采用的加密算法等信息。还有比较重要的是,对CERT.SF文件的内容用私钥进行加密之后的值。也就是说,即使手动篡改了MANIFEST.MF文件和CERT.SF文件,CERT.RSA文件还是会对应不上,安装就会失败。
apk安装白名单进行签名校验
上面所说是签名的过程,而安装则是反过来。
CERT.RSA这个文件中的包含的公钥对数字签名(自己本身)进行解密,将解密后的结果与CERT.SF文件hash运算后的结果进行比对,一致的话就返回证书链信息,并将证书链保存在certificates对象中,同时说明CERT.SF文件没有被篡改。如果这里对比通过,则下一步就验证MANIFEST.MF是否被篡改。在这一步,会遍历每个文件(entry),对非文件夹非签名文件的文件,逐个生成SHA1的数字签名信息,再用Base64进行编码,并与MANIFEST.MF中对内容进行比对,若一一对应,则表示文件没被篡改。到这里校验APK所有文件是否有被篡改,也已完成。
显然,如果我们想要在安装白名单上实现签名校验,那可以对比RSA文件上的证书签名是否与apk的签名一致即可。
获取apk签名的证书指纹
拿到app后,通过解压apk,获取到CERT.RSA文件,执行如下指令:
keytool -printcert -file CERT.RSA
会可以得到签名的证书指纹:
Certificate fingerprints:MD5: 0E:BA:50:A4:5C:15:B3:5D:97:7D:04:D8:43:79:B3:58SHA1: 41:79:1C:9B:8F:AF:15:E1:AC:D5:AA:F5:92:10:FD:42:46:7D:82:70SHA256: 2D:37:0C:21:F5:DF:D5:53:D2:A7:96:31:4B:70:92:5F:B3:8A:DE:EF:90:86:4C:92:0B:BB:BB:12:88:7D:35:20
每个签名的证书指纹都是唯一的,如此一来,就可以通过该证书指纹的唯一性来进行签名校验了。
android源码中获取证书指纹
要想获取apk的签名证书指纹,就需要先获取其签名。app安装时通过PackageParser类来解析安装包的信息,而签名也包含其中。我们可以利用这个来获取到签名的证书指纹。具体的安装流程这里不多介绍,直接定位到PackageManagerService类的preparePackageLI方法,该方法会去new出一个PackageParser对象来做apk包的解析:
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)throws PrepareFailure {...PackageParser pp = new PackageParser();pp.setSeparateProcesses(mSeparateProcesses);pp.setDisplayMetrics(mMetrics);pp.setCallback(mPackageParserCallback);Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");final PackageParser.Package pkg;try {pkg = pp.parsePackage(tmpPackageFile, parseFlags);DexMetadataHelper.validatePackageDexMetadata(pkg);} catch (PackageParserException e) {throw new PrepareFailure("Failed parse during installPackageLI", e);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}...try {// either use what we've been given or parse directly from the APKif (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {pkg.setSigningDetails(args.signingDetails);} else {PackageParser.collectCertificates(pkg, false /* skipVerify */);}} catch (PackageParserException e) {throw new PrepareFailure("Failed collect during installPackageLI", e);}...
}
这里列出部分代码,pkg是一个Pakage对象,属于PackageParser内部类,该类用于保存apk相关的信息,如包名,apk路径,签名等。
签名拿到后,就可以拿到它的证书指纹了,如下:
String sha1 = getFingerprint(pkg.mSigningDetails.signatures[0], "SHA1");private String getFingerprint(Signature signature, String hashAlgorithm) {if (signature == null) {return null;}try {MessageDigest digest = MessageDigest.getInstance(hashAlgorithm);return toHexadecimalString(digest.digest(signature.toByteArray()));} catch(NoSuchAlgorithmException e) {// ignore}return null;
}
MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。
这里是根据SHA算法获取摘要,即证书指纹。
签名校验
用该指纹和使用keytool得出的SHA1值进行比对,如果符合,则签名校验通过,允许应用安装。具体实现也比较单,可以将keytool得出的SHA1值保存到白名单中,然后在apk安装过程中,再读出进行比对即可。
结语
每个签名的证书指纹,即消息摘要,是哪些内容经过SHA1、MD5、SHA256算法得到的呢?私钥加上其他的一些内容?
微信公众号
我在微信公众号也有写文章,更新比较及时,有兴趣者可以微信搜索【Android系统实战开发】,关注有惊喜哦!
这篇关于Android10.0应用安装白名单---添加签名校验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!