綁定帳號登入

Android 台灣中文網

[教程] Android APK簽名對比及說明

[複製連結] 查看: 13828|回覆: 2|好評: 1
暗桌之光 | 收聽TA | 顯示全部樓層 |閱讀模式
發表於 2011-7-25 14:45

馬上加入Android 台灣中文網,立即免費下載應用遊戲。

您需要 登錄 才可以下載或查看,沒有帳號?註冊

x
發佈過Android應用的朋友們應該都知道,Android APK的發佈是需要簽名的。簽名機制在Android應用和框架中有著十分重要的作用。
例如,Android系統禁止更新安裝簽名不一致的APK;如果應用需要使用system權限,必須保證APK簽名與Framework簽名一致,等等。在《APK Crack》一文中,我們瞭解到,要解鎖一個APK,必然需要重新對APK進行簽名。而這個簽名,一般情況無法再與APK原先的簽名保持一致。(除非APK原作者的私鑰洩漏,那已經是另一個層次的軟體安全問題了。)
簡單地說,簽名機制標明了APK的發行機構。因此,站在軟體安全的角度,我們就可以通過比對APK的簽名情況,判斷此APK是否由「官方」發行,而不是被解鎖篡改過重新簽名打包的「盜版軟體」。

Android簽名機制
    為了說明APK簽名比對對軟體安全的有效性,我們有必要瞭解一下Android APK的簽名機制。為了更易於大家理解,我們從Auto-Sign工具的一條批處理命令說起。
在《APK Crack》一文中,我們瞭解到,要簽名一個沒有簽名過的APK,可以使用一個叫作Auto-sign的工具。Auto-sign工具實際執行的是一個叫做Sign.bat的批處理命令。用文本編輯器開啟這個批處理文件,我們可以發現,實現簽名功能的命令主要是這一行命令:
  1. java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
複製代碼
這條命令的意義是:通過signapk.jar這個可執行jar包,以「testkey.x509.pem」這個公鑰文件和「testkey.pk8」這個私鑰文件對「update.apk」進行簽名,簽名後的文件保存為「update_signed.apk」。
    對於此處所使用的私鑰和公鑰的生成方式,這裡就不做進一步介紹了。這方面的資料大家可以找到很多。我們這裡要講的是signapk.jar到底做了什麼。
    signapk.jar是Android源碼包中的一個簽名工具。由於Android是個開源項目,所以,很高興地,我們可以直接找到signapk.jar的源碼!路徑為/build/tools/signapk/SignApk.java。
對比一個沒有簽名的APK和一個簽名好的APK,我們會發現,簽名好的APK包中多了一個叫做META-INF的文件夾。裡面有三個文件,分別名為MANIFEST.MF、CERT.SF和CERT.RSA。signapk.jar就是生成了這幾個文件(其他文件沒有任何改變。因此我們可以很容易去掉原有簽名訊息)。
    通過閱讀signapk源碼,我們可以理清簽名APK包的整個過程。

1、 生成MANIFEST.MF文件:
程式遍歷update.apk包中的所有文件(entry),對非文件夾非簽名文件的文件,逐個生成SHA1的數字簽名訊息,再用Base64進行編碼。具體代碼見這個方法:
    private static Manifest addDigestsToManifest(JarFile jar)
關鍵代碼如下:
  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.                  InputStream data = jar.getInputStream(entry);
  7.                  while ((num = data.read(buffer)) > 0) {
  8.                      md.update(buffer, 0, num);
  9.                  }
  10.                  Attributes attr = null;
  11.                  if (input != null) attr = input.getAttributes(name);
  12.                  attr = attr != null ? new Attributes(attr) : new Attributes();
  13.                  attr.putValue("SHA1-Digest", base64.encode(md.digest()));
  14.                  output.getEntries().put(name, attr);
  15.            }
  16.      }
複製代碼
之後將生成的簽名寫入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的檢驗訊息不同,於是程式就不能成功安裝。

2、 生成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算法對摘要訊息進行加密。在安裝時只能使用公鑰才能解密它。解密之後,將它與未加密的摘要訊息進行對比,如果相符,則表明內容沒有被異常修改。

3、 生成CERT.RSA文件:
生成MANIFEST.MF沒有使用密鑰訊息,生成CERT.SF文件使用了私鑰文件。那麼我們可以很容易猜測到,CERT.RSA文件的生成肯定和公鑰相關。
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.          PKCS7 pkcs7 = new PKCS7(
  11.              new AlgorithmId[] { AlgorithmId.get("SHA1") },
  12.              new ContentInfo(ContentInfo.DATA_OID, null),
  13.              new X509Certificate[] { publicKey },
  14.              new SignerInfo[] { signerInfo });

  15.          pkcs7.encodeSignedData(out);
  16.      }
複製代碼
好了,分析完APK包的簽名流程,我們可以清楚地意識到:
1、 Android簽名機制其實是對APK包完整性和發佈機構唯一性的一種校驗機制。
2、 Android簽名機制不能阻止APK包被修改,但修改後的再簽名無法與原先的簽名保持一致。(擁有私鑰的情況除外)。
3、 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的軟體安全呢?
    個人認為主要有以下三種場景:
1、 程式自檢測。在程式執行時,自我進行簽名比對。比對樣本可以存放在APK包內,也可存放於雲端。缺點是程式被解鎖時,自檢測功能同樣可能遭到破壞,使其失效。
2、 可 信賴的第三方檢測。由可信賴的第三方程式負責APK的軟體安全問題。對比樣本由第三方收集,放在雲端。這種方式適用於殺毒安全軟體或者 APP Market之類的軟體下載市場。缺點是需要聯網檢測,在無網絡情況下無法實現功能。(不可能把大量的簽名數據放在移動設備本地)。
3、 系統限定安裝。這就涉及到改Android系統了。限定僅能安裝某些證書的APK。軟體發佈商需要向系統發佈上申請證書。如果發現問題,能追蹤到是哪個軟體發佈商的責任。適用於系統提供商或者終端產品生產商。缺點是過於封閉,不利於系統的開放性。
以上三種場景,雖然各有缺點,但缺點並不是不能克服的。例如,我們可以考慮程式自檢測的功能用native method的方法實現等等。軟體安全是一個複雜的課題,往往需要多種技術聯合使用,才能更好的保障軟體不被惡意破壞。
「用Android 就來APK.TW」,快來加入粉絲吧!
Android 台灣中文網(APK.TW)

評分

參與人數 7碎鑽 +5 幫助 +7 技術 +2 收起 理由
gameuser + 1 + 1 非常讃
pphyy5844548 + 1 + 1 非常讚
asdf5345 + 1
y3b4 + 1
nightyfly + 1 + 1 非常讚
magkono + 1 + 1 + 1 有一點難!不過我會努力學習瞭解!!感謝分.
georgiad + 1 + 1 + 1 讚一個!

查看全部評分

用Android 就來Android 台灣中文網(https://apk.tw)
nurkkkk | 收聽TA | 顯示全部樓層
發表於 2012-8-13 19:39
說那麼多我還是部會憑證
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 註冊

本版積分規則