To build a better world !

          Android APK 簽名比對


          轉(zhuǎn)載請注明出處:http://www.aygfsteel.com/zh-weir/archive/2011/07/19/354663.html

          Android APK 簽名比對

          發(fā)布過Android應用的朋友們應該都知道,Android APK的發(fā)布是需要簽名的。簽名機制在Android應用和框架中有著十分重要的作用。

          例如,Android系統(tǒng)禁止更新安裝簽名不一致的APK;如果應用需要使用system權限,必須保證APK簽名與Framework簽名一致,等等。在APK Crack一文中,我們了解到,要破解一個APK,必然需要重新對APK進行簽名。而這個簽名,一般情況無法再與APK原先的簽名保持一致。(除非APK原作者的私鑰泄漏,那已經(jīng)是另一個層次的軟件安全問題了。)

          簡單地說,簽名機制標明了APK的發(fā)行機構。因此,站在軟件安全的角度,我們就可以通過比對APK的簽名情況,判斷此APK是否由“官方”發(fā)行,而不是被破解篡改過重新簽名打包的“盜版軟件”。


          Android簽名機制

              為了說明APK簽名比對對軟件安全的有效性,我們有必要了解一下Android APK的簽名機制。為了更易于大家理解,我們從Auto-Sign工具的一條批處理命令說起。

          APK Crack一文中,我們了解到,要簽名一個沒有簽名過的APK,可以使用一個叫作Auto-sign的工具。Auto-sign工具實際運行的是一個叫做Sign.bat的批處理命令。用文本編輯器打開這個批處理文件,我們可以發(fā)現(xiàn),實現(xiàn)簽名功能的命令主要是這一行命令:

              java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk

              這條命令的意義是:通過signapk.jar這個可執(zhí)行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,我們會發(fā)現(xiàn),簽名好的APK包中多了一個叫做META-INF的文件夾。里面有三個文件,分別名為MANIFEST.MFCERT.SFCERT.RSA。signapk.jar就是生成了這幾個文件(其他文件沒有任何改變。因此我們可以很容易去掉原有簽名信息)。

              通過閱讀signapk源碼,我們可以理清簽名APK包的整個過程。


          1、 
          生成MANIFEST.MF文件:

          程序遍歷update.apk包中的所有文件(entry),對非文件夾非簽名文件的文件,逐個生成SHA1的數(shù)字簽名信息,再用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數(shù)字簽名。簡單地說,它就是一種安全哈希算法,類似于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算法對摘要信息進行加密。在安裝時只能使用公鑰才能解密它。解密之后,將它與未加密的摘要信息進行對比,如果相符,則表明內(nèi)容沒有被異常修改。


          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 
          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包的簽名流程,我們可以清楚地意識到:

          1、 Android簽名機制其實是對APK包完整性和發(fā)布機構唯一性的一種校驗機制。

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

          3、 APK包加密的公鑰就打包在APK包內(nèi),且不同的私鑰對應不同的公鑰。換句話言之,不同的私鑰簽名的APK公鑰也必不相同。所以我們可以根據(jù)公鑰的對比,來判斷私鑰是否一致。


          APK簽名比對的實現(xiàn)方式

              好了,通過Android簽名機制的分析,我們從理論上證明了通過APK公鑰的比對能判斷一個APK的發(fā)布機構。并且這個發(fā)布機構是很難偽裝的,我們暫時可以認為是不可偽裝的。

              有了理論基礎后,我們就可以開始實踐了。那么如何獲取到APK文件的公鑰信息呢?因為Android系統(tǒng)安裝程序肯定會獲取APK信息進行比對,所以我們可以通過Android源碼獲得一些思路和幫助。

              源碼中有一個隱藏的類用于APK包的解析。這個類叫PackageParser,路徑為frameworks\base\core\java\android\content\pm\PackageParser.java。當我們需要獲取APK包的相關信息時,可以直接使用這個類,下面代碼就是一個例子函數(shù):

           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, 00);
          16     }

              其中參數(shù)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應用開發(fā)中使用隱藏的類及方法,可以參看我的這篇文章:《Android應用開發(fā)中如何使用隱藏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方法獲取公鑰信息。所以比對簽名證書本質(zhì)上就是比對公鑰信息。

              OK,獲取到APK簽名證書之后,就剩下比對了。這個簡單,功能函數(shù)如下所示:

           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簽名比對的應用場景

              經(jīng)過以上的論述,想必大家已經(jīng)明白簽名比對的原理和我的實現(xiàn)方式了。那么什么時候什么情況適合使用簽名對比來保障Android APK的軟件安全呢?

              個人認為主要有以下三種場景:

          1、 程序自檢測。在程序運行時,自我進行簽名比對。比對樣本可以存放在APK包內(nèi),也可存放于云端。缺點是程序被破解時,自檢測功能同樣可能遭到破壞,使其失效。

          2、 可信賴的第三方檢測。由可信賴的第三方程序負責APK的軟件安全問題。對比樣本由第三方收集,放在云端。這種方式適用于殺毒安全軟件或者APP Market之類的軟件下載市場。缺點是需要聯(lián)網(wǎng)檢測,在無網(wǎng)絡情況下無法實現(xiàn)功能。(不可能把大量的簽名數(shù)據(jù)放在移動設備本地)。

          3、 系統(tǒng)限定安裝。這就涉及到改Android系統(tǒng)了。限定僅能安裝某些證書的APK。軟件發(fā)布商需要向系統(tǒng)發(fā)布上申請證書。如果發(fā)現(xiàn)問題,能追蹤到是哪個軟件發(fā)布商的責任。適用于系統(tǒng)提供商或者終端產(chǎn)品生產(chǎn)商。缺點是過于封閉,不利于系統(tǒng)的開放性。

          以上三種場景,雖然各有缺點,但缺點并不是不能克服的。例如,我們可以考慮程序自檢測的功能用native method的方法實現(xiàn)等等。軟件安全是一個復雜的課題,往往需要多種技術聯(lián)合使用,才能更好的保障軟件不被惡意破壞。


          參考資料

          Android源碼

          《Android中的簽名機制》


          轉(zhuǎn)載請注明出處:http://www.aygfsteel.com/zh-weir/archive/2011/07/19/354663.html

          posted on 2011-07-19 23:35 zh.weir 閱讀(49455) 評論(6)  編輯  收藏 所屬分類: Android軟件安全

          評論

          # re: Android APK 簽名比對 2012-05-21 16:58 hardPass

          樓主您好!
          我最近在開發(fā)中遇到一個與SHA1數(shù)字簽名有關的問題,希望您能幫我解惑。
          我的project中有個圖片,其在apk中的SHA1數(shù)字簽名總是不正確。
          其在apk中的數(shù)字簽名是4ss2KZ3FzkmfE6HAAsVu0aJKx1U=,
          但是我用我的代碼生成的卻是sjmKOs4BYDXg7COdeTc8tIfPBR0=
          我生成數(shù)字簽名的代碼大致如下:
          [code="java"]
          public static void main(String[] args) throws NoSuchAlgorithmException, Exception {
          MessageDigest md = MessageDigest.getInstance("sha-1");
          FileInputStream in = new FileInputStream("./ic_launcher.png");
          int bytes = 0;
          while ((bytes = in.read()) != -1) {
          md.update((byte)bytes);
          }
          in.close();
          byte[] thedigest = md.digest();
          System.out.println(Base64Encoder.encode(thedigest));
          }
          [/code]
          同時,無法在avd(android 2.2)中安裝含有該圖片的apk。
          在實際手機中的情況更怪異,有的手機能夠正常安裝,有的則是Application not installed.
          請樓主能抽空幫我看下問題,不甚感激!
          另外我的問題在這邊有更詳細的描述,并且還有相關附件
          http://www.iteye.com/topic/1123803  回復  更多評論   

          # re: Android APK 簽名比對 2012-11-29 23:38 r_y

          私鑰是keystore,然后可以通過代碼獲得與私鑰對應的公鑰。
          問題:怎么直接通過私鑰獲得公鑰?有工具嗎?  回復  更多評論   

          # re: Android APK 簽名比對 2013-02-04 17:26 sv_gn

          應用在運行中查詢自己的簽名信息,可以做到嗎?  回復  更多評論   

          # re: Android APK 簽名比對 2013-03-12 14:13 andye

          為什么要傳進去apk的文件路徑啊?這個很困難的。誰知道用戶會下載完放到哪里啊
            回復  更多評論   

          # re: Android APK 簽名比對 2013-07-05 11:15 eieihihi

          很好很強大 , 有空按你說的試試@~~  回復  更多評論   

          # re: Android APK 簽名比對 2013-08-13 23:51 freerabbit

          作者中對CERT.SF和CERT.RSA的分析似乎并不太準確,請看看http://blog.csdn.net/u011688064/article/details/9956785  回復  更多評論   

          公告

          大家好!歡迎光臨我的 Android 技術博客!



          本博客旨在交流與 Android 操作系統(tǒng)相關的各種技術及信息。

          博客內(nèi)的文章會盡量以開源的形式提供給大家,希望我們能相互交流,共同提高!

          有不足之處,請不吝賜教!

          我的郵箱:zh.weir@gmail.com
          我的新浪微博:@囧虎張建偉

           

          導航

          <2011年7月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          統(tǒng)計

          留言簿(19)

          隨筆分類(24)

          隨筆檔案(18)

          文章檔案(1)

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 翁牛特旗| 屏东县| 芜湖县| 洪泽县| 威远县| 贵定县| 虞城县| 赣州市| 介休市| 鹿邑县| 安徽省| 通渭县| 黄浦区| 同心县| 呼伦贝尔市| 花莲市| 阿城市| 成武县| 陈巴尔虎旗| 海盐县| 无为县| 厦门市| 利辛县| 板桥市| 曲麻莱县| 桑植县| 涪陵区| 巢湖市| 柯坪县| 同江市| 娄烦县| 九江县| 江山市| 黄平县| 泰顺县| 孟村| 天津市| 梧州市| 清镇市| 榆社县| 敖汉旗|