Android APK V1 簽名原理
對於 Android 開發者而言, APK 簽名的重要性不言而喻。Android 7.0 後 APK 簽名已經從基於 Jar 簽名的 V1 版本升級到了 V2 版本,為了能更好的理解,我們將從 V1、V2、簽名驗證三個方面進行詳細、深入介紹,但是鑑於篇幅原因,本文先介紹 V1 版簽名原理。
一、重要概念
1、雜湊演算法
Wiki 定義[1]:
A hash function is any function that can be used to map data of arbitrary size to data of fixed size. The values returned by a hash function are called hash values, hash codes, digests, or simply hashes.
也就是說雜湊函式(通常也叫雜湊演算法)可以把任意長度的資料對映成固定長度的資料,對映出來的資料稱為雜湊值、雜湊值、摘要。因為輸入資料不同,得到的雜湊值不同(很大概率),所以可當做輸入資料的指紋。常見的雜湊演算法[2]有 MD5、SHA-1、SHA-256,以下要介紹的 APK 簽名會用到 SHA-265[3] 雜湊演算法。
2、加密
Wiki 定義[4]:
In cryptography, encryption is the process of encoding a message or information in such a way that only authorized parties can access it.
加密就是把可讀的明文資料通過加密演算法轉換成不可讀的密文資料,只有通過相應的解密演算法才能把密文資料轉換成明文資料,常見的加密演算法分為對稱加密和非對稱加密。
對稱加密就是加密和解密都用同一把“鑰匙”。
舉個栗子,小明有一把普通鎖,這把鎖能且只能用同一把鑰匙(暫且稱為 K )上鎖和開鎖:假如小明用鑰匙 K 上了鎖,他的朋友小紅要開啟這個鎖,那麼只能事先讓小明配一把一樣的鑰匙 K' 給她。
非對稱加密就是加密和解密用的是兩把不同的“鑰匙”。
舉個栗子,小明有一把神奇鎖,和普通鎖不同的是,這把神奇鎖必須要藉助兩把不同的鑰匙(暫且稱為鑰匙 A 和 B)才能完成上鎖和開鎖:假如小明用鑰匙 A 上了鎖,那麼用鑰匙 A 已經不能開鎖了,能且只能用與之相對應的鑰匙 B 開鎖,反過來也一樣,而且鑰匙 A 和鑰匙 B 是一一對應關係。
實際應用中,小明自己留著鑰匙 A 而且保密,然後把鑰匙 B 掛在上了鎖的箱子外面一起寄送出去,收到箱子的人就可以用鑰匙 B 來開啟。因為只有通過鑰匙 A 上鎖的箱子才能被鑰匙 B 開啟,這就保證了箱子確實是用鑰匙 A 上鎖後寄過來的。
非對稱加密應用非常廣泛,有 SSL、SSH 以及非常火的比特幣。
以下要介紹的 APK 簽名會用到使用最廣泛的非對稱加密演算法 —— RSA。
3、數字簽名
Wiki 定義[5]:
A digital signature is a mathematical scheme for demonstrating the authenticity of digital messages or documents.
數字簽名就是證明資料真實性的一種方式。
上面講非對稱加密時舉例用的是箱子,如果把箱子換成一段資料的指紋(SHA-256),那麼對資料指紋加密的結果實際上就是其數字簽名。
數字簽名只能通過鑰匙 B(公鑰)解密,那麼如何保證和小明手上的鑰匙 A(私鑰)成對呢?也就是如何保證這份簽名來自小明?這個時候就需要公鑰證照出場了。
4、公鑰證照(數字證照)
還是 Wiki 定義[6]:
In cryptography, a public key certificate, also known as a digital certificate or identity certificate, is an electronic document used to prove the ownership of a public key.
公鑰證照就是證明公鑰的所有者,證照包括了公鑰資訊、公鑰所有者資訊、證照籤發者資訊等;而公鑰證照的真實性由證照頒發機構 —— CA 來保證(CA 證照一般內建在各類作業系統中)。
繼續上面的例子,小明把鑰匙 B 不是直接掛在箱子外面而是加密後放入公鑰證照中,再把證照掛在箱子外一起寄送出去,接收者通過系統內建 CA 證照的公鑰解密得到鑰匙 B(公鑰),再去開鎖。這樣就保證了鑰匙 B 確實是小明的,箱子也確實是小明用鑰匙 A 上鎖的,而且箱子沒有被人動過手腳。
APK 簽名原理和上述 4 個概念息息相關,一份經過簽名的資料,包含原始資料、數字簽名、公鑰證照三個部分。用一張圖[7]來總結一下:
二、APK V1 簽名原理(前方高能警告,將出現大量 jarsigner 原始碼細節)
1、簽名工具
APK 簽名可以用 jarsigner 或者 signapk 兩個工具,Android Studio 預設用的是 signapk,二者主要的區別在於證照和祕鑰儲存的格式不同,前者是通過 Java KeyStore(.jks 檔案或者 .keystore 檔案) 格式,後者分別用 .pem 和 .pk8 格式來儲存證照和金鑰。
Java KeyStore 生成方式:
【生成】證照庫
keytool -genkey -v -keystore strange.keystore -alias strange -keyalg RSA -keysize 2048 -validity 10000
【檢視】證照庫
keytool -list -v -keystore {path2jks} -storepass “pass"
.pem .pk8 生成方式:
【生成】金鑰
openssl genrsa -out key.pem 2048
【生成】證照請求
openssl req -new -key key.pem -out request.pem
【生成】 pem格式的 x.509 證照
openssl x509 -req -days 10000 -in request.pem -signkey key.pem -out certificate.pem -sha256
【生成】 pk8 格式金鑰
openssl pkcs8 -topk8 -outform DER -in key.pem -inform PEM -out key.pk8 -nocrypt
【檢視】pem證照
openssl x509 -in publicKey.x509.pem -text -noout
無論是用的哪種簽名方式,最終都是在 META-INF 目錄下生成三個檔案:MANIFEST.MF、CERT.SF、CERT.RSA(如果是 jarsigner 簽名 .SF 和 .RSA 檔名會根據 alias 來定),這三個檔案各司其職,最終構成了 APK 簽名資訊。
2、原理分析
我們來分析一下 jarsigner 原始碼[8](signapk.jar 與其類似),看看這三個檔案是如何生成的。
首先看 main
函式,只做了一件事情:呼叫 run
方法。
run
主要做了 4 件事,前面都是一些準備工作,最後呼叫 signJar
方法開始進行簽名。
signJar
方法中經歷了幾個步驟,詳細可以看如下截圖中的註釋,最重要的是計算 META-INF/MANIFEST.MF 清單檔案、META-INF/.SF* 待簽名檔案、META-INF/.RSA* 簽名結果檔案。
下面,將對每個步驟詳細展開。
2.1、計算並寫入 META-INF/MANIFEST.MF 清單檔案
我們先來認識一下 manifest 檔案:來自於 jar 包的檔案清單,在 apk 中我們用來記錄所有非目錄檔案的 資料指紋,如下圖所示。
從檔案開頭到第一個空行之間(圖中的 1-3 行)是 manifest 檔案主屬性,從第 5 行開始就是其所包含的條目(entry)。
條目是由 條目名稱 和 條目屬性 組成,條目名稱就是 Name:
之後的值如圖中的res/drawable-xhdpi-v4/img_blank.png
,條目屬性是一個name-value
格式的map
如圖中的{"SHA-256-Digest":"ft47V9YtB/3V9uUqZbN4kTMP+SMJ2D3AK1j7G8lj9l0="}
。
其條目的生成過程如下:
針對每個待簽名 zip 包中的檔案(除了 META-INF 下簽名相關的如 .MF SIG- *.SF *.DSA *.RSA *.EC檔案),進行如下判斷:
- 如果 Manifest 清單中沒有出現,那麼計算 hash 然後增加到 Manifest 中;
- 如果 Manifest 清單中已經包含,那麼計算 hash 後和 Manifest 中的 hash 進行對比覆蓋。
其中最重要的是獲取 hash 屬性的方法 getDigestAttributes
。
先呼叫 getDigests
生成指紋,再封裝成 manifest 條目的屬性物件。
總結一下就是,針對每個待簽名 zip 包中的檔案(除了 META-INF 下簽名相關的如 .MF SIG- *.SF *.DSA *.RSA *.EC檔案),計算其資料指紋並寫入 META-INF/MANIFEST.MF 清單檔案中。
至此,.MF 檔案完成計算並寫入。
2.2、計算並寫入 META-INF/*.SF 簽名檔案
這裡的 .SF 檔案實際上也是清單檔案的一種,它是對上述 MANIFEST.MF 檔案的資料指紋,我們來看看它的內容。
從上面對 MANIFEST.MF 檔案內容的分析,我們可以看出來 SF 包含了 檔案屬性 和一系列 條目 。
a. Signature-Version
是簽名版本。
b. SHA-256-Digest-Manifest-Main-Attributes
是 MANIFEST.MF 檔案屬性 的資料指紋 Base64 值。
c. SHA-256-Digest-Manifest
是整個 MANIFEST.MF 的資料指紋 Base64 值。
d. Created-By
指明檔案生成工具。
e. 第 7 行開始的各個條目,就是對 MANIFEST.MF 各個條目的資料指紋 Base64 值。
如果把 MANIFEST.MF 當做是對 APK 中各個檔案的 hash 記錄,那麼 *.SF 就是 MANIFEST.MF 及其各個條目的 hash 記錄。
我們來看看其生成過程:
先建立了 SignatureFile
物件,再寫入 ZipOutputStream
中,關鍵在於SignatureFile
,我們繼續看其建構函式。
其實就兩步,一是寫屬性,二是寫條目。而指紋的計算都是通過 Java 的 ManifestDigester
及 ManifestDigester.Entry
物件來完成。
至此, .SF 檔案完成計算並寫入。
2.3、計算並寫入 META-INF/*.RSA 簽名結果檔案
.RSA 是 PKCS#7[9] 標準格式的檔案,我們只關心它所儲存的以下兩種資料:
a. 用
私鑰
對 .SF 檔案指紋
進行非對稱加密
後得到的 加密資料
b. 攜帶公鑰
以及各種身份資訊的 數字證照
來看看上述兩種資料:
2.3.1 加密資料
通過 openssl asn1parse 格式化檢視加密後的資料及其偏移量,從下圖中可以看出加密後資料處在 PKCS#7 的最後。
![PKCS#7 簡要資料結構[10]](http://upload-images.jianshu.io/upload_images/300515-6fe37c6e1e0dfa08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
執行 openssl asn1parse -i -inform der -in STRANGEW.RSA
得到如下 ASN1[11] 格式資料:
最後這一段就是加密後的 16 進位制資料了,我們來分析一下。
1115 是位元組偏移量(十進位制)
d=5 表示所處 PKCS#7 資料結構的層級是第 5 層
hl=4 表示頭所佔位元組數為 4 個位元組
l=256 表示資料位元組數為 256(對應 SHA-256 指紋演算法)
執行 dd if=STRANGEW.RSA of=signed-sha256.bin bs=1 skip=$[ 1115 + 4 ] count=256
把加密資料匯出來到 signed-sha256.bin 檔案。
執行 hexdump -C signed-sha256.bin
檢視檔案資料:
2.3.2 數字證照
執行 openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_certs -text
檢視 .RSA 中儲存的證照資訊。截圖中可以看到證照包含了簽名演算法、有效期、證照主體、證照籤發者、公鑰等資訊。
如何把加密資料和證照放到 .RSA 檔案中的呢?
2.3.3 .RSA 檔案計算過程
從原始碼中可以看出先是呼叫 sf.generateBlock
生成 Block
靜態內部類的物件,最後寫入 ZipOutputStream
。
核心在於 sf.generateBlock
,其中只是執行了return new Block(...)
,所以關鍵還是 Block
的建構函式:
從原始碼中可以看出,主要是根據私鑰演算法得到對應的簽名演算法,再用簽名演算法對待簽名資料(這裡是 .SF 檔案)進行簽名,最後把簽名資料和證照放在一起生成位元組陣列。
此致,.RSA 檔案完成計算並寫入。
2.4、寫入除 .MF .RSA .SF 檔案之外的所有檔案
分為兩步,先寫入 META-INF 目錄內的,再寫入 META-INF 目錄外的。writeEntry
方法比較簡單,實際上是完成了檔案從 zipFile
到 ZipOutputStream
的複製。
三、總結
最後總結一下 MANIFEST.MF、CERT.SF、CERT.RSA 如何各司其職構成了 APK 的簽名:
a. 解析出 CERT.RSA 檔案中的證照、公鑰,解密 CERT.RSA 中的加密資料
b. 解密結果和 CERT.SF 的指紋進行對比,保證 CERT.SF 沒有被篡改
c. 而 CERT.SF 中的內容再和 MANIFEST.MF 指紋對比,保證 MANIFEST.MF 檔案沒有被篡改
d. MANIFEST.MF 中的內容和 APK 所有檔案指紋逐一對比,保證 APK 沒有被篡改
相關文章
- Android空包Apk簽名AndroidAPK
- Android APK簽名原理和過程詳解AndroidAPK
- Android v1 簽名 和 v2+ 簽名Android
- Android V1及V2簽名原理簡析Android
- 簽名打包Android版apkAndroidAPK
- Android之重新簽名APKAndroidAPK
- Android APK命令列實現V1、V2簽名及驗證AndroidAPK命令列
- 簽名apkAPK
- Android加固之後Apk重簽名AndroidAPK
- Android 生成簽名apk與多渠道打包AndroidAPK
- APK簽名報錯APK
- Apk 簽名的那些事APK
- apk空包簽名方法及工具APK
- Visual Studio 2017為Android APK包簽名AndroidAPK
- 【Android自動化打包】03. APK的數字簽名AndroidAPK
- 為IONIC開發的安卓apk簽名安卓APK
- IOS App簽名原理iOSAPP
- 數字簽名原理
- iOS逆向——應用簽名及重簽名原理iOS
- User模式下內建apk做重簽名.模式APK
- 分享一個自動編譯,打包,簽名 android apk 的小指令碼編譯AndroidAPK指令碼
- Android 命令列構建和簽署APKAndroid命令列APK
- 蘋果企業簽名原理蘋果
- iOSApp簽名原理iOSAPP
- Elgamal數字簽名原理GAM
- Android自定義打包apk名稱AndroidAPK
- Android 應用簽名Android
- Android簽名機制Android
- 獲取APK檔案的簽名資訊,反射實現APK反射
- 蘋果軟體打包簽名原理蘋果
- ipa重簽名原理介紹
- iOS應⽤簽名原理淺析iOS
- Android 打包簽名 從生成keystore到完成簽名Android
- Android應用重簽名Android
- 程式獲取Android簽名Android
- Android App的簽名打包AndroidAPP
- apk簽名相關文章APK
- android studio 打包簽名apk,完成微信和qq的的第三方分享功能AndroidAPK