Android APK V1 簽名原理

對于 Android 開發(fā)者而言, APK 簽名的重要性不言而喻。Android 7.0 后 APK 簽名已經(jīng)從基于 Jar 簽名的 V1 版本升級到了 V2 版本,為了能更好的理解,我們將從 V1、V2、簽名驗(yàn)證三個方面進(jìn)行詳細(xì)、深入介紹,但是鑒于篇幅原因,本文先介紹 V1 版簽名原理。

一、重要概念

1、散列算法

Wiki 定義[1]

Ahash functionis 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 calledhash values,hash codes,digests, or simplyhashes.

也就是說散列函數(shù)(通常也叫散列算法)可以把任意長度的數(shù)據(jù)映射成固定長度的數(shù)據(jù),映射出來的數(shù)據(jù)稱為散列值、哈希值摘要。因?yàn)檩斎霐?shù)據(jù)不同,得到的散列值不同(很大概率),所以可當(dāng)做輸入數(shù)據(jù)的指紋。常見的散列算法[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.

加密就是把可讀的明文數(shù)據(jù)通過加密算法轉(zhuǎn)換成不可讀的密文數(shù)據(jù),只有通過相應(yīng)的解密算法才能把密文數(shù)據(jù)轉(zhuǎn)換成明文數(shù)據(jù),常見的加密算法分為對稱加密非對稱加密。

對稱加密就是加密和解密都用同一把“鑰匙”。

舉個栗子,小明有一把普通鎖,這把鎖能且只能用同一把鑰匙(暫且稱為 K )上鎖和開鎖:假如小明用鑰匙 K 上了鎖,他的朋友小紅要打開這個鎖,那么只能事先讓小明配一把一樣的鑰匙 K' 給她。

非對稱加密就是加密和解密用的是兩把不同的“鑰匙”。

舉個栗子,小明有一把神奇鎖,和普通鎖不同的是,這把神奇鎖必須要借助兩把不同的鑰匙(暫且稱為鑰匙 A 和 B)才能完成上鎖和開鎖:假如小明用鑰匙 A 上了鎖,那么用鑰匙 A 已經(jīng)不能開鎖了,能且只能用與之相對應(yīng)的鑰匙 B 開鎖,反過來也一樣,而且鑰匙 A 和鑰匙 B 是一一對應(yīng)關(guān)系。

實(shí)際應(yīng)用中,小明自己留著鑰匙 A 而且保密,然后把鑰匙 B 掛在上了鎖的箱子外面一起寄送出去,收到箱子的人就可以用鑰匙 B 來打開。因?yàn)?b>只有通過鑰匙 A 上鎖的箱子才能被鑰匙 B 打開,這就保證了箱子確實(shí)是用鑰匙 A 上鎖后寄過來的。

非對稱加密應(yīng)用非常廣泛,有 SSL、SSH 以及非常火的比特幣。

以下要介紹的 APK 簽名會用到使用最廣泛的非對稱加密算法 ——RSA。

3、數(shù)字簽名

Wiki 定義[5]

A digital signature is a mathematical scheme for demonstrating the authenticity of digital messages or documents.

數(shù)字簽名就是證明數(shù)據(jù)真實(shí)性的一種方式。

上面講非對稱加密時舉例用的是箱子,如果把箱子換成一段數(shù)據(jù)的指紋(SHA-256),那么對數(shù)據(jù)指紋加密的結(jié)果實(shí)際上就是其數(shù)字簽名。

數(shù)字簽名只能通過鑰匙 B(公鑰)解密,那么如何保證和小明手上的鑰匙 A(私鑰)成對呢?也就是如何保證這份簽名來自小明?這個時候就需要公鑰證書出場了。

4、公鑰證書(數(shù)字證書)

還是 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.

公鑰證書就是證明公鑰的所有者,證書包括了公鑰信息、公鑰所有者信息、證書簽發(fā)者信息等;而公鑰證書的真實(shí)性由證書頒發(fā)機(jī)構(gòu) —— CA 來保證(CA 證書一般內(nèi)置在各類操作系統(tǒng)中)。

繼續(xù)上面的例子,小明把鑰匙 B 不是直接掛在箱子外面而是加密后放入公鑰證書中,再把證書掛在箱子外一起寄送出去,接收者通過系統(tǒng)內(nèi)置 CA 證書的公鑰解密得到鑰匙 B(公鑰),再去開鎖。這樣就保證了鑰匙 B 確實(shí)是小明的,箱子也確實(shí)是小明用鑰匙 A 上鎖的,而且箱子沒有被人動過手腳。

APK 簽名原理和上述 4 個概念息息相關(guān),一份經(jīng)過簽名的數(shù)據(jù),包含原始數(shù)據(jù)、數(shù)字簽名、公鑰證書三個部分。用一張圖[7]來總結(jié)一下:

數(shù)字簽名原理

二、APK V1 簽名原理(前方高能警告,將出現(xiàn)大量 jarsigner 源碼細(xì)節(jié))

1、簽名工具

APK 簽名可以用 jarsigner 或者 signapk 兩個工具,Android Studio 默認(rèn)用的是 signapk,二者主要的區(qū)別在于證書和秘鑰存儲的格式不同,前者是通過 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 文件名會根據(jù) alias 來定),這三個文件各司其職,最終構(gòu)成了 APK 簽名信息。

2、原理分析

我們來分析一下 jarsigner 源碼[8](signapk.jar 與其類似),看看這三個文件是如何生成的。

首先看main函數(shù),只做了一件事情:調(diào)用run方法。

`main` 函數(shù)

run主要做了 4 件事,前面都是一些準(zhǔn)備工作,最后調(diào)用signJar方法開始進(jìn)行簽名。

`run` 方法

signJar方法中經(jīng)歷了幾個步驟,詳細(xì)可以看如下截圖中的注釋,最重要的是計算META-INF/MANIFEST.MF清單文件、META-INF/.SF* 待簽名文件、META-INF/.RSA* 簽名結(jié)果文件。

signJar 簽名流程

下面,將對每個步驟詳細(xì)展開。

2.1、計算并寫入 META-INF/MANIFEST.MF 清單文件

我們先來認(rèn)識一下manifest文件:來自于 jar 包的文件清單,在 apk 中我們用來記錄所有非目錄文件的數(shù)據(jù)指紋,如下圖所示。

MANIFEST.MF 文件內(nèi)容

從文件開頭到第一個空行之間(圖中的 1-3 行)是 manifest 文件主屬性,從第 5 行開始就是其所包含的條目(entry)。

條目是由條目名稱條目屬性組成,條目名稱就是Name:之后的值如圖中的res/drawable-xhdpi-v4/img_blank.png,條目屬性是一個name-value格式的map如圖中的{"SHA-256-Digest":"ft47V9YtB/3V9uUqZbN4kTMP+SMJ2D3AK1j7G8lj9l0="}。

其條目的生成過程如下:

MANIFEST.MF 計算

針對每個待簽名 zip 包中的文件(除了META-INF 下簽名相關(guān)的如.MF SIG-*.SF *.DSA? *.RSA *.EC文件),進(jìn)行如下判斷:

如果 Manifest 清單中沒有出現(xiàn),那么計算 hash 然后增加到 Manifest 中;

如果 Manifest 清單中已經(jīng)包含,那么計算 hash 后和 Manifest 中的 hash 進(jìn)行對比覆蓋。

其中最重要的是獲取 hash 屬性的方法getDigestAttributes。

生成 MANIFEST.MF 條目屬性

先調(diào)用getDigests生成指紋,再封裝成 manifest 條目的屬性對象。

計算指紋

總結(jié)一下就是,針對每個待簽名 zip 包中的文件(除了META-INF 下簽名相關(guān)的如.MF SIG-*.SF *.DSA? *.RSA *.EC文件),計算其數(shù)據(jù)指紋并寫入 META-INF/MANIFEST.MF 清單文件中。

至此,.MF 文件完成計算并寫入。

2.2、計算并寫入 META-INF/*.SF 簽名文件

這里的 .SF 文件實(shí)際上也是清單文件的一種,它是對上述 MANIFEST.MF 文件的數(shù)據(jù)指紋,我們來看看它的內(nèi)容。

.SF 文件實(shí)際上也是清單文件

從上面對 MANIFEST.MF 文件內(nèi)容的分析,我們可以看出來 SF 包含了文件屬性和一系列條目。

a.Signature-Version是簽名版本。

b.SHA-256-Digest-Manifest-Main-Attributes是 MANIFEST.MF文件屬性的數(shù)據(jù)指紋 Base64 值。

c.SHA-256-Digest-Manifest是整個 MANIFEST.MF 的數(shù)據(jù)指紋 Base64 值。

d.Created-By指明文件生成工具。

e. 第 7 行開始的各個條目,就是對 MANIFEST.MF 各個條目的數(shù)據(jù)指紋 Base64 值。

如果把 MANIFEST.MF 當(dāng)做是對 APK 中各個文件的 hash 記錄,那么 *.SF 就是 MANIFEST.MF 及其各個條目的 hash 記錄。

我們來看看其生成過程:

.SF 文件計算并寫入

先創(chuàng)建了SignatureFile對象,再寫入ZipOutputStream中,關(guān)鍵在于SignatureFile,我們繼續(xù)看其構(gòu)造函數(shù)。

.SF 文件計算

其實(shí)就兩步,一是寫屬性,二是寫條目。而指紋的計算都是通過 Java 的ManifestDigester及ManifestDigester.Entry對象來完成。

至此, .SF 文件完成計算并寫入。

2.3、計算并寫入 META-INF/*.RSA 簽名結(jié)果文件

.RSA 是 PKCS#7[9]標(biāo)準(zhǔn)格式的文件,我們只關(guān)心它所保存的以下兩種數(shù)據(jù):

a. 用私鑰對 .SF 文件指紋進(jìn)行非對稱加密后得到的加密數(shù)據(jù)

b. 攜帶公鑰以及各種身份信息的數(shù)字證書

來看看上述兩種數(shù)據(jù):

2.3.1 加密數(shù)據(jù)

通過 openssl asn1parse 格式化查看加密后的數(shù)據(jù)及其偏移量,從下圖中可以看出加密后數(shù)據(jù)處在 PKCS#7 的最后。

![PKCS#7 簡要數(shù)據(jù)結(jié)構(gòu)[10]](http://upload-images.jianshu.io/upload_images/300515-6fe37c6e1e0dfa08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

執(zhí)行openssl asn1parse -i -inform der -in STRANGEW.RSA得到如下 ASN1[11]格式數(shù)據(jù):

ASN1 格式數(shù)據(jù)

最后這一段就是加密后的 16 進(jìn)制數(shù)據(jù)了,我們來分析一下。

1115是字節(jié)偏移量(十進(jìn)制)

d=5表示所處 PKCS#7 數(shù)據(jù)結(jié)構(gòu)的層級是第 5 層

hl=4表示頭所占字節(jié)數(shù)為 4 個字節(jié)

l=256表示數(shù)據(jù)字節(jié)數(shù)為 256(對應(yīng) SHA-256 指紋算法)

執(zhí)行dd if=STRANGEW.RSA of=signed-sha256.bin bs=1 skip=$[ 1115 + 4 ] count=256把加密數(shù)據(jù)導(dǎo)出來到signed-sha256.bin文件。

執(zhí)行hexdump -C signed-sha256.bin查看文件數(shù)據(jù):

.RSA 中的 16 進(jìn)制加密數(shù)據(jù)

2.3.2 數(shù)字證書

執(zhí)行openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_certs -text查看 .RSA 中保存的證書信息。截圖中可以看到證書包含了簽名算法、有效期、證書主體、證書簽發(fā)者、公鑰等信息。

.RSA 中的數(shù)字證書

如何把加密數(shù)據(jù)和證書放到 .RSA 文件中的呢?

2.3.3 .RSA 文件計算過程

從源碼中可以看出先是調(diào)用sf.generateBlock生成Block靜態(tài)內(nèi)部類的對象,最后寫入ZipOutputStream。

.RSA 文件計算并寫入

核心在于sf.generateBlock,其中只是執(zhí)行了return new Block(...),所以關(guān)鍵還是Block的構(gòu)造函數(shù):

.RSA 文件計算

從源碼中可以看出,主要是根據(jù)私鑰算法得到對應(yīng)的簽名算法,再用簽名算法對待簽名數(shù)據(jù)(這里是 .SF 文件)進(jìn)行簽名,最后把簽名數(shù)據(jù)和證書放在一起生成字節(jié)數(shù)組。

此致,.RSA 文件完成計算并寫入。

2.4、寫入除 .MF .RSA .SF 文件之外的所有文件

分為兩步,先寫入 META-INF 目錄內(nèi)的,再寫入 META-INF 目錄外的。writeEntry方法比較簡單,實(shí)際上是完成了文件從zipFile到ZipOutputStream的復(fù)制。

寫入簽名相關(guān)文件之外的文件

三、總結(jié)

最后總結(jié)一下 MANIFEST.MF、CERT.SF、CERT.RSA 如何各司其職構(gòu)成了 APK 的簽名:

a. 解析出 CERT.RSA 文件中的證書、公鑰,解密 CERT.RSA 中的加密數(shù)據(jù)

b. 解密結(jié)果和 CERT.SF 的指紋進(jìn)行對比,保證 CERT.SF 沒有被篡改

c. 而 CERT.SF 中的內(nèi)容再和 MANIFEST.MF 指紋對比,保證 MANIFEST.MF 文件沒有被篡改

d. MANIFEST.MF 中的內(nèi)容和 APK 所有文件指紋逐一對比,保證 APK 沒有被篡改

作者:Cavabiao

鏈接:http://www.itdecent.cn/p/a27783a713f2

來源:簡書

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容