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]

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é)一下:

數(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.MFCERT.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ì)可以看如下截圖中的注釋届垫,最重要的是計(jì)算 META-INF/MANIFEST.MF 清單文件释液、META-INF/.SF* 待簽名文件、META-INF/.RSA* 簽名結(jié)果文件装处。

signJar 簽名流程

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

2.1妄迁、計(jì)算并寫入 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 計(jì)算

針對每個待簽名 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

生成 MANIFEST.MF 條目屬性

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

計(jì)算指紋

總結(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)容横漏。

.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 文件計(jì)算并寫入

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

.SF 文件計(jì)算

其實(shí)就兩步箕憾,一是寫屬性牡借,二是寫條目。而指紋的計(jì)算都是通過 Java 的 ManifestDigesterManifestDigester.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ù):

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 文件計(jì)算過程

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

.RSA 文件計(jì)算并寫入

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

.RSA 文件計(jì)算

從源碼中可以看出偿洁,主要是根據(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í)際上是完成了文件從 zipFileZipOutputStream的復(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 沒有被篡改


  1. https://en.wikipedia.org/wiki/Hash_function ?

  2. https://en.wikipedia.org/wiki/List_of_hash_functions ?

  3. https://en.wikipedia.org/wiki/SHA-2 ?

  4. https://en.wikipedia.org/wiki/Encryption ?

  5. https://en.wikipedia.org/wiki/Digital_signature ?

  6. https://en.wikipedia.org/wiki/Public_key_certificate ?

  7. https://zh.wikipedia.org/wiki/%E6%95%B8%E4%BD%8D%E7%B0%BD%E7%AB%A0 ?

  8. http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/classes/sun/security/tools/jarsigner ?

  9. https://en.wikipedia.org/wiki/PKCS ?

  10. http://qistoph.blogspot.hk/2012/01/manual-verify-pkcs7-signed-data-with.html ?

  11. https://wiki.openssl.org/index.php/Manual:Asn1parse(1) ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玖院,老刑警劉巖气笙,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異痢士,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門星掰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫩舟,你說我怎么就攤上這事氢烘。” “怎么了家厌?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵播玖,是天一觀的道長。 經(jīng)常有香客問我饭于,道長蜀踏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任掰吕,我火速辦了婚禮果覆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘殖熟。我一直安慰自己随静,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著燎猛,像睡著了一般恋捆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上重绷,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天沸停,我揣著相機(jī)與錄音,去河邊找鬼昭卓。 笑死愤钾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的候醒。 我是一名探鬼主播能颁,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼倒淫!你這毒婦竟也來了伙菊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤敌土,失蹤者是張志新(化名)和其女友劉穎镜硕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體返干,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兴枯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了矩欠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片财剖。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖癌淮,靈堂內(nèi)的尸體忽然破棺而出躺坟,到底是詐尸還是另有隱情,我是刑警寧澤该默,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布瞳氓,位于F島的核電站策彤,受9級特大地震影響栓袖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜店诗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一裹刮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧庞瘸,春花似錦捧弃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘴办。三九已至,卻和暖如春买鸽,著一層夾襖步出監(jiān)牢的瞬間涧郊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工眼五, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妆艘,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓看幼,卻偏偏與公主長得像批旺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诵姜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容