對于 Android 開發(fā)者而言冰评, APK 簽名的重要性不言而喻阵翎。Android 7.0 后 APK 簽名已經(jīng)從基于 Jar 簽名的 V1 版本升級到了 V2 版本,為了能更好的理解殃恒,我們將從 V1模蜡、V2、簽名驗(yàn)證三個方面進(jìn)行詳細(xì)佑菩、深入介紹盾沫,但是鑒于篇幅原因,本文先介紹 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.
也就是說散列函數(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)?strong>只有通過鑰匙 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é)一下:
二、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
方法。
run
主要做了 4 件事旱易,前面都是一些準(zhǔn)備工作禁偎,最后調(diào)用 signJar
方法開始進(jìn)行簽名腿堤。
signJar
方法中經(jīng)歷了幾個步驟,詳細(xì)可以看如下截圖中的注釋届垫,最重要的是計(jì)算 META-INF/MANIFEST.MF 清單文件释液、META-INF/.SF* 待簽名文件、META-INF/.RSA* 簽名結(jié)果文件装处。
下面误债,將對每個步驟詳細(xì)展開。
2.1妄迁、計(jì)算并寫入 META-INF/MANIFEST.MF 清單文件
我們先來認(rèn)識一下 manifest 文件:來自于 jar 包的文件清單寝蹈,在 apk 中我們用來記錄所有非目錄文件的 數(shù)據(jù)指紋,如下圖所示登淘。
從文件開頭到第一個空行之間(圖中的 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 下簽名相關(guān)的如 .MF SIG- *.SF *.DSA *.RSA *.EC文件)流妻,進(jìn)行如下判斷:
- 如果 Manifest 清單中沒有出現(xiàn)牲蜀,那么計(jì)算 hash 然后增加到 Manifest 中;
- 如果 Manifest 清單中已經(jīng)包含绅这,那么計(jì)算 hash 后和 Manifest 中的 hash 進(jìn)行對比覆蓋涣达。
其中最重要的是獲取 hash 屬性的方法 getDigestAttributes
。
先調(diào)用 getDigests
生成指紋证薇,再封裝成 manifest 條目的屬性對象度苔。
總結(jié)一下就是,針對每個待簽名 zip 包中的文件(除了 META-INF 下簽名相關(guān)的如 .MF SIG- *.SF *.DSA *.RSA *.EC文件)浑度,計(jì)算其數(shù)據(jù)指紋并寫入 META-INF/MANIFEST.MF 清單文件中寇窑。
至此,.MF 文件完成計(jì)算并寫入箩张。
2.2疗认、計(jì)算并寫入 META-INF/*.SF 簽名文件
這里的 .SF 文件實(shí)際上也是清單文件的一種,它是對上述 MANIFEST.MF 文件的數(shù)據(jù)指紋伏钠,我們來看看它的內(nèi)容横漏。
從上面對 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 記錄刊愚。
我們來看看其生成過程:
先創(chuàng)建了 SignatureFile
對象,再寫入 ZipOutputStream
中踩验,關(guān)鍵在于SignatureFile
鸥诽,我們繼續(xù)看其構(gòu)造函數(shù)。
其實(shí)就兩步箕憾,一是寫屬性牡借,二是寫條目。而指紋的計(jì)算都是通過 Java 的 ManifestDigester
及 ManifestDigester.Entry
對象來完成袭异。
至此钠龙, .SF 文件完成計(jì)算并寫入。
2.3御铃、計(jì)算并寫入 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ù):
最后這一段就是加密后的 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ù):
2.3.2 數(shù)字證書
執(zhí)行 openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_certs -text
查看 .RSA 中保存的證書信息。截圖中可以看到證書包含了簽名算法溜徙、有效期湃缎、證書主體、證書簽發(fā)者蠢壹、公鑰等信息嗓违。
如何把加密數(shù)據(jù)和證書放到 .RSA 文件中的呢?
2.3.3 .RSA 文件計(jì)算過程
從源碼中可以看出先是調(diào)用 sf.generateBlock
生成 Block
靜態(tài)內(nèi)部類的對象图贸,最后寫入 ZipOutputStream
蹂季。
核心在于 sf.generateBlock
,其中只是執(zhí)行了return new Block(...)
疏日,所以關(guān)鍵還是 Block
的構(gòu)造函數(shù):
從源碼中可以看出偿洁,主要是根據(jù)私鑰算法得到對應(yīng)的簽名算法,再用簽名算法對待簽名數(shù)據(jù)(這里是 .SF 文件)進(jìn)行簽名沟优,最后把簽名數(shù)據(jù)和證書放在一起生成字節(jié)數(shù)組涕滋。
此致,.RSA 文件完成計(jì)算并寫入挠阁。
2.4宾肺、寫入除 .MF .RSA .SF 文件之外的所有文件
分為兩步溯饵,先寫入 META-INF 目錄內(nèi)的,再寫入 META-INF 目錄外的锨用。writeEntry
方法比較簡單丰刊,實(shí)際上是完成了文件從 zipFile
到 ZipOutputStream
的復(fù)制。
三增拥、總結(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 沒有被篡改