Android下载文件合法性完整性校验
转载自:http://blog.****.net/l2show/article/details/48182367
一.概述
二.实现
1.文件完整性效验
这里字符串取MD5就不做赘述了.既然要效验文件的完整性,那么就牵扯到取文件MD5摘要,这里是用JDK中的MessageDigest通过读取文件的二进制流,使用update累计更新文件流的MD5摘要来获取整个文件的MD5摘要.拿到文件MD5之后跟接口下发的对比就OK了.所以单纯做文件完整性效验还是很简单的.
- /**
- * get file md5
- * @param file
- * @return
- * @throws NoSuchAlgorithmException
- * @throws IOException
- */
- private static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
- if (!file.isFile()) {
- return null;
- }
- MessageDigest digest;
- FileInputStream in;
- byte buffer[] = new byte[1024];
- int len;
- digest = MessageDigest.getInstance("MD5");
- in = new FileInputStream(file);
- while ((len = in.read(buffer, 0, 1024)) != -1) {
- digest.update(buffer, 0, len);
- }
- in.close();
- BigInteger bigInt = new BigInteger(1, digest.digest());
- return bigInt.toString(16);
- }
文件合法性的效验相比完整性效验就要复杂不少,这里合法性是根据签名来做的,所以起码要简单了解签名的过程,签名之后文件发生了什么变化和怎么获取文件的签名信息等.
1)签名后产出
生成签名文件和签名的过程这里就不细说了,主要分析一下签名之后的一些情况.解压签名之后的文件可以看到经过签名生成出来了一个META-INF文件夹,里面一般都是三个文件用来存放所有文件的效验以及签名信息.
MANIFEST.MF:可以明文查看,对所有文件取BASE64哈希值.
ANDROIDK.SF:可以明文查看,对所有文件的前三行取BASE64哈希值.
ANDROIDK.RSA:前面两个文件都只是对文件做了一个哈希摘要,并没有签名公钥等信息,RSA文件里面就包含了我们所需要的信息,其中包含有开发者信息,开发者公钥以及CA根据前两个文件的摘要信息经过私钥加密后的密文.RSA文件是不能查看明文的,这里可以使用openssl的命令来输出该文件的信息.openssl pkcs7 -inform DER -in ANDROIDK.RSA -noout -print_certs -text, 从这个文件的数据结构来看本章需要用到的其实就是公钥,获取文件自身证书信息的公钥,然后对比app自身的签名公钥就可以判断文件的合法性.
2)获取app自身签名
通过上面的介绍可以了解到签过名的文件都可以获取到一个基于RSA算法的RSA public key,这里就通过效验这个公钥的方式来验证合法性,app自身的签名信息可以通过PackageInfo获取,获取到之后经过字符串转换和截取,将RSA public key部分摘取出来就OK了.
- /**
- * get local app rsa public key
- * @param ctx
- * @return
- * @throws IOException
- * @throws PackageManager.NameNotFoundException
- * @throws CertificateException
- */
- private static String getLocalSignature(Context ctx) throws IOException,
- PackageManager.NameNotFoundException, CertificateException {
- String signCode = null;
- //get signature info depends on package name
- PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(
- ctx.getPackageName(), PackageManager.GET_SIGNATURES);
- Signature[] signs = packageInfo.signatures;
- Signature sign = signs[0];
- CertificateFactory certFactory = CertificateFactory
- .getInstance("X.509");
- X509Certificate cert = (X509Certificate) certFactory
- .generateCertificate(new ByteArrayInputStream(sign.toByteArray()));
- String pubKey = cert.getPublicKey().toString();
- String ss = subPublicSignature(pubKey);
- ss = ss.replace(",", "");
- ss = ss.toLowerCase();
- int aa = ss.indexOf("modulus");
- int bb = ss.indexOf("publicexponent");
- signCode = ss.substring(aa + 8, bb);
- return signCode;
- }
获取外部文件签名的过程其实可以参考Android内部效验apk文件的过程,Android源码中的PackageParser类会在安装apk之前对apk文件做合法性效验,但是遗憾的是这个类被标注为hide了,所以我们不能直接使用.那么就只剩下两个方法了,一个是通过反射使用PackageParser的方法,一个看源码中效验这部分的实现然后抠出来自己实现.
- /**
- * Package archive parsing
- *
- * {@hide}
- */
- public class PackageParser {
- //source/frameworks/base/core/java/android/content/pm
- }
从PackageParser类中的collectCertificates方法中可以看到如下代码片段.首先根据文件路径将签名后的apk,jar或zip文件装载到JarFile中(JarFile是继承自ZipFile),然后获取文件内容部的某个文件(这部分代码块是获取的manifest文件),再获取到该文件的证书信息.只要能拿到证书信息,那么拿到公钥什么的都是小case了.
- public boolean collectCertificates(Package pkg, int flags) {
- //.................
- JarFile jarFile = new JarFile(mArchiveSourcePath);
- //.................
- JarEntry jarEntry = jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME);
- //.................
- certs = loadCertificates(jarFile, jarEntry, readBuffer);
- pkg.mSignatures = null;
- //.................
- }
- private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je,
- byte[] readBuffer) throws IOException {
- // We must read the stream for the JarEntry to retrieve
- // its certificates.
- InputStream is = new BufferedInputStream(jarFile.getInputStream(je));
- while (is.read(readBuffer, 0, readBuffer.length) != -1) {
- // not using
- }
- is.close();
- return je != null ? je.getCertificates() : null;
- }