Android APK 簽名比對(轉)

Android apk簽名的過程

一、 生成MANIFEST.MF文件:html

程序遍歷update.apk包中的全部文件(entry),對非文件夾非簽名文件的文件,逐個生成SHA1的數字簽名信息,再用Base64進行編碼。具體代碼見這個方法:java

private static Manifest addDigestsToManifest(JarFile jar)

關鍵代碼以下:android

 1 for (JarEntry entry: byName.values()) {
 2    String name = entry.getName();
 3    if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
 4        !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
 5           (stripPattern == null ||!stripPattern.matcher(name).matches())) 
 6     {
 7            InputStream data = jar.getInputStream(entry);
 8            while ((num = data.read(buffer)) > 0) {
 9                md.update(buffer, 0, num);
10            }
11           Attributes attr = null;
12           if (input != null) attr = input.getAttributes(name);
13           attr = attr != null ? new Attributes(attr) : new Attributes();
14           attr.putValue("SHA1-Digest", base64.encode(md.digest()));
15           output.getEntries().put(name, attr);
16     }
17 }

以後將生成的簽名寫入MANIFEST.MF文件。關鍵代碼以下:算法

1 Manifest manifest = addDigestsToManifest(inputJar);
2 je = new JarEntry(JarFile.MANIFEST_NAME);
3 je.setTime(timestamp);
4 outputJar.putNextEntry(je);
5 manifest.write(outputJar);

  這裏簡單介紹下SHA1數字簽名。簡單地說, 它就是一種安全哈希算法,相似於MD5算法。它把任意長度的輸入,經過散列算法變成固定長度的輸出(這裏咱們稱做「摘要信息」)。你不能僅經過這個摘要信息復原原來的信息。另外,它保證不一樣信息的摘要信息彼此不一樣。所以,若是你改變了apk包中的文件,那麼在apk安裝校驗時,改變後的文件摘要信息與MANIFEST.MF的檢驗信息不一樣,因而程序就不能成功安裝。安全


二、 
生成CERT.SF文件:網絡

  對前一步生成的Manifest,使用SHA1-RSA算法,用私鑰進行簽名。關鍵代碼以下:函數

1 Signature signature = Signature.getInstance("SHA1withRSA");
2 signature.initSign(privateKey);
3 je = new JarEntry(CERT_SF_NAME);
4 je.setTime(timestamp);
5 outputJar.putNextEntry(je);
6 writeSignatureFile(manifest,
7 new SignatureOutputStream(outputJar, signature));

   RSA是一種非對稱加密算法。用私鑰經過RSA算法對摘要信息進行加密,在安裝時只能使用公鑰才能解密它。解密以後,將它與未加密的摘要信息進行對比,若是相符,則代表內容沒有被異常修改。編碼


三、 
生成CERT.RSA文件:加密

生成MANIFEST.MF沒有使用密鑰信息,生成CERT.SF文件使用了私鑰文件。那麼咱們能夠很容易猜想到,CERT.RSA文件的生成確定和公鑰相關。spa

CERT.RSA文件中保存了公鑰、所採用的加密算法等信息。核心代碼以下:

1 je = new JarEntry(CERT_RSA_NAME);
2 je.setTime(timestamp);
3 outputJar.putNextEntry(je);
4 writeSignatureBlock(signature, publicKey, outputJar); 

  其中writeSignatureBlock的代碼以下:

 1 private static void writeSignatureBlock(
 2          Signature signature, X509Certificate publicKey, OutputStream out)
 3              throws IOException, GeneralSecurityException {
 4                  SignerInfo signerInfo = new SignerInfo(
 5                  new X500Name(publicKey.getIssuerX500Principal().getName()),
 6                  publicKey.getSerialNumber(),
 7                  AlgorithmId.get("SHA1"),
 8                  AlgorithmId.get("RSA"),
 9                  signature.sign());
10  
11          PKCS7 pkcs7 = new PKCS7(
12              new AlgorithmId[] { AlgorithmId.get("SHA1") },
13              new ContentInfo(ContentInfo.DATA_OID, null),
14              new X509Certificate[] { publicKey },
15              new SignerInfo[] { signerInfo });
16  
17          pkcs7.encodeSignedData(out);
18 }

  好了,分析完APK包的簽名流程,咱們能夠清楚地意識到:

一、 Android簽名機制實際上是對APK包完整性和發佈機構惟一性的一種校驗機制。

二、 Android簽名機制不能阻止APK包被修改,但修改後的再簽名沒法與原先的簽名保持一致。(擁有私鑰的狀況除外)。

三、 APK包加密的公鑰就打包在APK包內,且不一樣的私鑰對應不一樣的公鑰。換句話言之,不一樣的私鑰簽名的APK公鑰也必不相同。因此咱們能夠根據公鑰的對比,來判斷私鑰是否一致。

APK簽名比對的實現方式

    好了,經過Android簽名機制的分析,咱們從理論上證實了經過APK公鑰的比對能判斷一個APK的發佈機構。而且這個發佈機構是很難假裝的,咱們暫時能夠認爲是不可假裝的。

    有了理論基礎後,咱們就能夠開始實踐了。那麼如何獲取到APK文件的公鑰信息呢?由於Android系統安裝程序確定會獲取APK信息進行比對,因此咱們能夠經過Android源碼得到一些思路和幫助。

    源碼中有一個隱藏的類用於APK包的解析。這個類叫PackageParser,路徑爲frameworks\base\core\java\android\content\pm\PackageParser.java。當咱們須要獲取APK包的相關信息時,能夠直接使用這個類,下面代碼就是一個例子函數:

 1 private PackageInfo parsePackage(String archiveFilePath, int flags){
 2          
 3      PackageParser packageParser = new PackageParser(archiveFilePath);
 4      DisplayMetrics metrics = new DisplayMetrics();
 5      metrics.setToDefaults();
 6      final File sourceFile = new File(archiveFilePath);
 7      PackageParser.Package pkg = packageParser.parsePackage(
 8              sourceFile, archiveFilePath, metrics, 0);
 9      if (pkg == null) {
10          return null;
11      }
12      
13      packageParser.collectCertificates(pkg, 0); 
14      
15      return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0);
16 }

 

  其中參數archiveFilePath指定APK文件路徑;flags需設置PackageManager.GET_SIGNATURES位,以保證返回證書籤名信息。

    具體如何經過PackageParser獲取簽名信息在此處不作詳述,具體代碼請參考PackageParser中的public boolean collectCertificates(Package pkg, int flags)private Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer)方法。至於如何在Android應用開發中使用隱藏的類及方法,能夠參看個人這篇文章:《Android應用開發中如何使用隱藏API》

    緊接着,咱們能夠經過packageInfo.signatures來訪問到APK的簽名信息。還須要說明的是 Android中Signature和Java中Certificate的對應關係。它們的關係以下面代碼所示:

1 pkg.mSignatures = new Signature[certs.length];
2 for (int i=0; i<N; i++) {
3     pkg.mSignatures[i] = new Signature(
4     certs[i].getEncoded());
5 }

 

  也就是說signature = new Signature(certificate.getEncoded()); certificate證書中包含了公鑰和證書的其餘基本信息。公鑰不一樣,證書確定互不相同。咱們能夠經過certificate的getPublicKey方法獲取公鑰信息。因此比對簽名證書本質上就是比對公鑰信息

    OK,獲取到APK簽名證書以後,就剩下比對了。這個簡單,功能函數以下所示:

 1 private boolean IsSignaturesSame(Signature[] s1, Signature[] s2) {
 2     if (s1 == null) {
 3         return false;
 4     }
 5     if (s2 == null) {
 6         return false;
 7     }
 8     HashSet<Signature> set1 = new HashSet<Signature>();
 9     for (Signature sig : s1) {
10         set1.add(sig);
11     }
12     HashSet<Signature> set2 = new HashSet<Signature>();
13     for (Signature sig : s2) {
14         set2.add(sig);
15     }
16     // Make sure s2 contains all signatures in s1.
17     if (set1.equals(set2)) {
18         return true;
19     }
20     return false;
21 }

 

APK簽名比對的應用場景

    通過以上的論述,想必你們已經明白簽名比對的原理和個人實現方式了。那麼何時什麼狀況適合使用簽名對比來保障Android APK的軟件安全呢?

    我的認爲主要有如下三種場景:

一、 程序自檢測。在程序運行時,自我進行簽名比對。比對樣本能夠存放在APK包內,也可存放於雲端。缺點是程序被破解時,自檢測功能一樣可能遭到破壞,使其失效。

二、 可信賴的第三方檢測。由可信賴的第三方程序負責APK的軟件安全問題。對比樣本由第三方收集,放在雲端。這種方式適用於殺毒安全軟件或者 APP Market之類的軟件下載市場。缺點是須要聯網檢測,在無網絡狀況下沒法實現功能。(不可能把大量的簽名數據放在移動設備本地)。

三、 系統限定安裝。這就涉及到改Android系統了。限定僅能安裝某些證書的APK。軟件發佈商須要向系統發佈上申請證書。若是發現問題,能追蹤到是哪一個軟件發佈商的責任。適用於系統提供商或者終端產品生產商。缺點是過於封閉,不利於系統的開放性。

以上三種場景,雖然各有缺點,但缺點並非不能克服的。例如,咱們能夠考慮程序自檢測的功能用native method的方法實現等等。軟件安全是一個複雜的課題,每每須要多種技術聯合使用,才能更好的保障軟件不被惡意破壞。


參考資料

Android源碼

《Android中的簽名機制》


轉載請註明出處:http://www.blogjava.net/zh-weir/archive/2011/07/19/354663.html

相關文章
相關標籤/搜索