轉載自:http://blog.csdn.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;
- }