apk簽名原理及實現
釋出過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的批處理命令。用文字編輯器開啟這個批處理檔案,我們可以發現,實現簽名功能的命令主要是這一行命令:
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk這條命令的意義是:通過signapk.jar這個可執行jar包,以“
對於此處所使用的私鑰和公鑰的生成方式,這裡就不做進一步介紹了。這方面的資料大家可以找到很多。我們這裡要講的是signapk.jar到底做了什麼。
signapk.jar是Android原始碼包中的一個簽名工具。由於Android是個開源專案,所以,很高興地,我們可以直接找到signapk.jar的原始碼!路徑為/build/tools/signapk/SignApk.java。
對比一個沒有簽名的APK和一個簽名好的APK,我們會發現,簽名好的APK包中多了一個叫做
通過閱讀signapk原始碼,我們可以理清簽名APK包的整個過程。
1、 生成MANIFEST.MF檔案:
程式遍歷update.apk包中的所有檔案(entry),對非資料夾非簽名檔案的檔案,逐個生成SHA1的數字簽名信息,再用Base64進行編碼。具體程式碼見這個方法:
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檔案中儲存了公鑰、所採用的加密演算法等資訊。核心程式碼如下:
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檔案。關鍵程式碼如下:
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包完整性和釋出機構唯一性的一種校驗機制。
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簽名比對的應用場景
個人認為主要有以下三種場景:
1、 程式自檢測。在程式執行時,自我進行簽名比對。比對樣本可以存放在APK包內,也可存放於雲端。缺點是程式被破解時,自檢測功能同樣可能遭到破壞,使其失效。
2、 可信賴的第三方檢測。由可信賴的第三方程式負責APK的軟體安全問題。對比樣本由第三方收集,放在雲端。這種方式適用於防毒安全軟體或者APP Market之類的軟體下載市場。缺點是需要聯網檢測,在無網路情況下無法實現功能。(不可能把大量的簽名資料放在移動裝置本地)。
3、 系統限定安裝。這就涉及到改Android系統了。限定僅能安裝某些證書的APK。軟體釋出商需要向系統釋出上申請證書。如果發現問題,能追蹤到是哪個軟體釋出商的責任。適用於系統提供商或者終端產品生產商。缺點是過於封閉,不利於系統的開放性。
以上三種場景,雖然各有缺點,但缺點並不是不能克服的。例如,我們可以考慮程式自檢測的功能用native method的方法實現等等。軟體安全是一個複雜的課題,往往需要多種技術聯合使用,才能更好的保障軟體不被惡意破壞。
相關推薦
apk簽名原理及實現
釋出過Android應用的朋友們應該都知道,Android APK的釋出是需要簽名的。簽名機制在Android應用和框架中有著十分重要的作用。 例如,Android系統禁止更新安裝簽名不一致的APK;如果應用需要使用system許可權,必須保證APK簽名與Framewo
關於base64編碼的原理及實現
一個 replace 編碼範圍 func nco 都是 style bit 如果 我們的圖片大部分都是可以轉換成base64編碼的data:image。 這個在將canvas保存為img的時候尤其有用。雖然除ie外,大部分現代瀏覽器都已經支持原生的基於base64的enco
java設計模式singleton原理及實現
最新 不必要 -- 不同 適合 所有 引用 ati cnblogs 題外話:我要變強,要變強,變強,強。 1、 Singleton的應用場景以及為什麽要使用singleSingleton是一生只能有一個實例的對象。只能由singleton自身創建一個實例。外人是無法創建實例
決策樹原理及實現
方式 -1 變化 log nbsp 導致 結點 以及 重要 1、決策樹原理 1.1、定義 分類決策樹模型是一種描述對實例進行分類的樹形結構。決策樹由結點和有向邊組成。結點有兩種類型:內部節點和葉節點,內部節點表示一個特征或屬性,葉節點表示一個類。
RPC原理及實現
.get 版本 pcs 連接方式 正常 zookeepe list 接口 分布式計算 1 簡介 RPC 的主要功能目標是讓構建分布式計算(應用)更容易,在提供強大的遠程調用能力時不損失本地調用的語義簡潔性。為實現該目標,RPC 框架需提供一種透明調用機制讓使用者不必顯式的
SSO單點登錄原理及實現
response dem nbsp boolean 配置文件 實現 有效 ucc ons 1.SSO分類 根據實現的域不同,可以把SSO分為同域SSO、同父域SSO、跨域SSO三種類型。 2.SSO實現原理 a.打開統一的登錄界面 b.登錄,同時向服務器寫入Cookie
線程池的原理及實現
execute inter void date() 超過 緩沖 線程池大小 exceptio 調整 1、線程池簡介: 多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。 假設一個服務器完成一項任務所需時間為:T1
線程池原理及實現
任務隊列 批量 not alt con 成了 代碼 pla extends 1、線程池簡介: 多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。 假設一個服務器完成一項任務所需時間為:T1
四.HashSet原理及實現學習總結
throw map 所有 cti con name 保持 nts equal 在上一篇博文(HashMap原理及實現學習總結)詳細總結了HashMap的實現過程,對於HashSet而言,它是基於HashMap來實現的,底層采用HashMap來保存元素。所以如果對HashMa
五.HashTable原理及實現學習總結
容量 區別 存儲 們的 如果 isn cte ash ref 有兩個類都提供了一個多種用途的hashTable機制,他們都可以將可以key和value結合起來構成鍵值對通過put(key,value)方法保存起來,然後通過get(key)方法獲取相對應的value值。一個是
CGLib動態代理原理及實現
aop object col 子類 doc pos 輸出 intercept pub JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,
sso簡單原理及實現
登錄用戶 會話 例子 www 哪些 java類 pad adb 應對 轉自:http://www.cnblogs.com/ywlaker/ 一、單系統登錄機制 1、http無狀態協議 web應用采用browser/server架構,http作為通信協議。http是無
網站統計中的數據收集原理及實現
fun 美的 置配 客戶 etc 分析 獲取 固定 open 網站統計中的數據收集原理及實現 網站數據統計分析工具是網站站長和運營人員經常使用的一種工具,比較常用的有谷歌分析、百度統計和騰訊分析等等。所有這些統計分析工具的第一步都是網站訪問數據的收集。目前主流的數據收
智能指針原理及實現(1)- shared_ptr
red ++ 直接 初始 targe -- div urn 記錄 C++沒有內存回收機制,每次程序員new出來的對象需要手動delete,流程復雜時可能會漏掉delete,導致內存泄漏。於是C++引入智能指針,可用於動態資源管理,資源即對象的管理策略。 一、智能指針類別 智
智能指針原理及實現(2)- unique_ptr
unique clas 結束 基礎 無法 body 智能指針 周期 文件 只允許基礎指針的一個所有者。 可以移到新所有者(具有移動語義),但不會復制或共享(即我們無法得到指向同一個對象的兩個unique_ptr)。 替換已棄用的 auto_ptr。 相較於 boost::s
分布式鎖原理及實現方式
ack 常用 ima 個數 tar 刪除 不能 cap lock 本文轉自:http://www.hollischuang.com/archives/1716 目前幾乎很多大型網站及應用都是分布式部署的,分布式場景中的數據一致性問題一直是一個比較重要的
【數據結構】ArrayList原理及實現學習總結(2)
!= 需要 但是 object count def 原理 arrays 位置 ArrayList是一個基於數組實現的鏈表(List),這一點可以從源碼中看出: transient Object[] elementData; // non-private to si
【數據結構】ArrayList原理及實現學習總結
sset bsp perm arraylist 節點 article cell public arr 關於Java集合的小抄中是這樣描述的: 以數組實現。節約空間,但數組有容量限制。超出限制時會增加50%容量,用System.arraycopy()復制到新的數組,因此最好能
Svm算法原理及實現
load 統計 分割 opp 出了 數組 註意 tst 簡單 Svm(support Vector Mac)又稱為支持向量機,是一種二分類的模型。當然如果進行修改之後也是可以用於多類別問題的分類。支持向量機可以分為線性核非線性兩大類。其主要思想為找到空間中的一個
LVS-NAT和LVS-DR類型的工作原理及實現步驟
本地 頭文件 交換機 響應 實現原理 arp 處理 直接 通告 lvs-dr類型工作原理:①:client端向目標IP(VIP)發送請求,經由路由器和交換機設備和後,此時的請求數據包頭文件的目標MAC值為調度器的MAC,源MAC值為client的MAC,目標IP為VIP,源