APK簽名機(jī)制之——V2簽名機(jī)制詳解

轉(zhuǎn)載請(qǐng)注明出處:http://www.itdecent.cn/p/308515c94dc6
github:https://github.com/rushgit/zhongwenjun.github.com
csdn:https://blog.csdn.net/zwjemperor

通過前一篇Apk簽名機(jī)制之——JAR簽名機(jī)制詳解的分析我們知道,JAR簽名需要對(duì)apk內(nèi)所有文件進(jìn)行hash校驗(yàn),當(dāng)資源較多時(shí)簽名驗(yàn)證速度較慢。為了加快驗(yàn)證速度并加強(qiáng)完整性保證,Andorid在7.0引入一種全文件簽名方案V2。下面來(lái)看V2方案的具體設(shè)計(jì)原理。

1. V2簽名設(shè)計(jì)思想

在了解V2簽名結(jié)構(gòu)前,先來(lái)了解下zip(apk)文件的結(jié)構(gòu)。

1.1 ZIP文件結(jié)構(gòu)

zip包結(jié)構(gòu)

zip文件分為3部分:

  1. 數(shù)據(jù)區(qū)

    此區(qū)塊包含了zip中所有文件的記錄,是一個(gè)列表,每條記錄包含:文件名、壓縮前后size、壓縮后的數(shù)據(jù)等;

  2. 中央目錄

    存放目錄信息,也是一個(gè)列表,每條記錄包含:文件名、壓縮前后size、本地文件頭的起始偏移量等。通過本地文件頭的起始偏移量即可找到壓縮后的數(shù)據(jù);

  3. 中央目錄結(jié)尾記錄

    標(biāo)識(shí)中央目錄結(jié)尾,包含:中央目錄條目數(shù)、size、起始偏移量、zip文件注釋內(nèi)容等。

通過中央目錄起始偏移量和size即可定位到中央目錄,再遍歷中央目錄條目,根據(jù)本地文件頭的起始偏移量即可在數(shù)據(jù)區(qū)中找到相應(yīng)的壓縮數(shù)據(jù)。

1.2 V2簽名原理

Apk簽名機(jī)制之——JAR簽名機(jī)制詳解中我們已經(jīng)知道,JAR簽名是在apk文件中添加META-INF目錄,即需要修改數(shù)據(jù)區(qū)、中央目錄,因?yàn)樘砑游募髸?huì)導(dǎo)致中央目錄大小和偏移量發(fā)生變化,還需要修改中央目錄結(jié)尾記錄。V2方案為加強(qiáng)數(shù)據(jù)完整性保證,不在數(shù)據(jù)區(qū)中央目錄中插入數(shù)據(jù),選擇在 數(shù)據(jù)區(qū)中央目錄 之間插入一個(gè)APK簽名分塊,從而保證了原始zip(apk)數(shù)據(jù)的完整性。具體如下所示:

v2簽名前后差異

v2 簽名塊負(fù)責(zé)保護(hù)第 1、3、4 部分的完整性,以及第 2 部分包含的APK 簽名方案 v2分塊中的signed data分塊的完整性。

1.3 如何定位 APK簽名方案V2分塊?

v2_block.png

APK簽名分塊包含了4部分:分塊長(zhǎng)度、ID-VALUE序列、分塊長(zhǎng)度、固定magic值。其中APK 簽名方案 v2分塊存放在ID為0x7109871a的鍵值對(duì)中。在進(jìn)行簽名校驗(yàn)時(shí),先找到zip中央目錄結(jié)尾記錄,從該記錄中找到中央目錄起始偏移量,再通過magic值即可確定前方可能是APK簽名分塊,再通過前后兩個(gè)分塊長(zhǎng)度字段,即可確定APK簽名分塊的位置,最后通過ID(0x7109871a)定位APK 簽名方案 v2分塊位置。

1.4 APK簽名方案V2分塊 格式

APK簽名方案V2分塊 格式

APK 簽名方案 v2分塊是一個(gè)簽名序列,說明可以使用多個(gè)簽名者對(duì)同一個(gè)APK進(jìn)行簽名。每個(gè)簽名信息中均包含了三個(gè)部分的內(nèi)容:

  • 帶長(zhǎng)度前綴的signed data

    其中包含了通過一系列算法計(jì)算的摘要列表、證書信息,以及extra信息(可選);

  • 帶長(zhǎng)度前綴的signatures序列

    通過一系列算法對(duì)signed data的簽名列表。簽名時(shí)使用了多個(gè)簽名算法,在簽名校驗(yàn)時(shí)會(huì)是選擇系統(tǒng)支持的安全系數(shù)最高的簽名進(jìn)行校驗(yàn);

  • 證書公鑰

1.5 摘要計(jì)算過程

前面說了v2 簽名塊負(fù)責(zé)保護(hù)第 1、3、4 部分的完整性,以及第 2 部分包含的APK 簽名方案 v2分塊中的 signed data 分塊的完整性。第1、3、4部分的完整性是通過內(nèi)容摘要來(lái)保護(hù)的,這些摘要保存在signed data分塊中,而signed data分塊的完整性是通過簽名來(lái)保證的。下面來(lái)看摘要的計(jì)算過程:

摘要計(jì)算

第 1、3 和 4 部分的摘要采用以下計(jì)算方式,類似于兩級(jí) Merkle 樹

  1. 拆分chunk

    將每個(gè)部分拆分成多個(gè)大小為 1 MB大小的chunk,最后一個(gè)chunk可能小于1M。之所以分塊,是為了可以通過并行計(jì)算摘要以加快計(jì)算速度;

  2. 計(jì)算chunk摘要

    字節(jié) 0xa5 + 塊的長(zhǎng)度(字節(jié)數(shù)) + 塊的內(nèi)容 進(jìn)行計(jì)算;

  3. 計(jì)算整體摘要

    字節(jié) 0x5a + chunk數(shù) + 塊的摘要的連接(按塊在 APK 中的順序)進(jìn)行計(jì)算。

    這里要注意的是中央目錄結(jié)尾記錄中包含了中央目錄的起始偏移量,插入APK簽名分塊后,中央目錄的起始偏移量將發(fā)生變化。故在校驗(yàn)簽名計(jì)算摘要時(shí),需要把中央目錄的起始偏移量當(dāng)作APK簽名分塊的起始偏移量。

1.6 v2 驗(yàn)證過程

來(lái)源:APK 簽名方案 v2 驗(yàn)證

  1. 找到APK 簽名分塊并驗(yàn)證以下內(nèi)容:
    a. APK 簽名分塊的兩個(gè)大小字段包含相同的值。
    b. ZIP 中央目錄結(jié)尾緊跟在ZIP 中央目錄記錄后面。
    c. ZIP 中央目錄結(jié)尾之后沒有任何數(shù)據(jù)。
  2. 找到APK 簽名分塊中的第一個(gè)APK 簽名方案 v2 分塊。如果 v2 分塊存在,則繼續(xù)執(zhí)行第 3 步。否則,回退至使用 v1 方案驗(yàn)證 APK。
  3. 對(duì)APK 簽名方案 v2 分塊中的每個(gè) signer 執(zhí)行以下操作:
    a. 從 signatures 中選擇安全系數(shù)最高的受支持 signature algorithm ID。安全系數(shù)排序取決于各個(gè)實(shí)現(xiàn)/平臺(tái)版本。
    b. 使用 public key 并對(duì)照signed data 驗(yàn)證 signatures 中對(duì)應(yīng)的 signature。(現(xiàn)在可以安全地解析 signed data 了。)
    c. 驗(yàn)證 digests 和 signatures 中的簽名算法 ID 列表(有序列表)是否相同。(這是為了防止刪除/添加簽名。)
    d. 使用簽名算法所用的同一種摘要算法計(jì)算 APK 內(nèi)容的摘要。
    e. 驗(yàn)證計(jì)算出的摘要是否與 digests 中對(duì)應(yīng)的 digest 相同。
    f. 驗(yàn)證 certificates 中第一個(gè) certificate 的 SubjectPublicKeyInfo 是否與 public key 相同。
  4. 如果找到了至少一個(gè) signer,并且對(duì)于每個(gè)找到的 signer,第 3 步都取得了成功,APK 驗(yàn)證將會(huì)成功。

2. 兼容機(jī)制&防回滾機(jī)制

2.1 兼容機(jī)制

因?yàn)閂2簽名機(jī)制是在Android 7.0中引入的,為了使APK可在Android 7.0以下版本中安裝,應(yīng)先用JAR簽名對(duì)APK進(jìn)行簽名,再用V2方案進(jìn)行簽名。要注意順序一定是先JAR簽名再V2簽名,因?yàn)镴AR簽名需要修改zip數(shù)據(jù)區(qū)中央目錄的內(nèi)容,先使用V2簽名再JAR簽名會(huì)破壞V2簽名的完整性。

實(shí)際上我們?cè)诰幾gAPK時(shí)并不需要關(guān)心這個(gè)過程,在Android Plugin for Gradle 2.2中,gradle默認(rèn)會(huì)同時(shí)使用JAR簽名和V2方案對(duì)APK進(jìn)行簽名,如果想要關(guān)閉JAR簽名或V2簽名,可以在build.gradle中進(jìn)行配置:

android {
    ...
    defaultConfig { ... }
    signingConfigs {
        release {
            ...
            // v1SigningEnabled false
            v2SigningEnabled false
        }
    }
}

在 Android 7.0 中,會(huì)優(yōu)先以 v2方案驗(yàn)證 APK,在Android 7.0以下版本中,系統(tǒng)會(huì)忽略 v2 簽名,僅驗(yàn)證 v1 簽名。Android 7.0+的校驗(yàn)過程如下:

兼容機(jī)制

2.2 防回滾機(jī)制

因?yàn)樵诮?jīng)過V2簽名的APK中同時(shí)帶有JAR簽名,攻擊者可能將APK的V2簽名刪除,使得Android系統(tǒng)只校驗(yàn)JAR簽名。為防范此類攻擊,V2方案規(guī)定:

V2簽名的APK如果還帶JAR簽名,其 META-INF/.SF 文件的首部中必須包含 X-Android-APK-Signed 屬性。該屬性的值是一組以英文逗號(hào)分隔的 APK 簽名方案 ID(v2 方案的 ID 為 2)。在驗(yàn)證 v1 簽名時(shí),對(duì)于此組中驗(yàn)證程序首選的 APK 簽名方案(例如,v2 方案),如果 APK 沒有相應(yīng)的簽名,APK 驗(yàn)證程序必須要拒絕這些 APK。此項(xiàng)保護(hù)依賴于內(nèi)容 META-INF/.SF 文件受 v1 簽名保護(hù)這一事實(shí)。

含v2簽名的META-INF/*.SF

攻擊者還可能試圖刪除APK 簽名方案 v2 分塊中安全系數(shù)較高的簽名,從而使系統(tǒng)驗(yàn)證安全系數(shù)較低的簽名。為防范此類攻擊:

對(duì) APK 進(jìn)行簽名時(shí)使用的簽名算法 ID 的列表會(huì)存儲(chǔ)在通過各個(gè)簽名保護(hù)的 signed data 分塊中。

3. 總結(jié)

通過Apk簽名機(jī)制之——JAR簽名機(jī)制詳解和本篇文章的分析,我們知道了:

  • JAR簽名是針對(duì)ZIP文件所有文件依次進(jìn)行簽名;
  • V2方案是針對(duì)APK整體文件進(jìn)行簽名

JAR簽名的劣勢(shì)

  • 需對(duì)所有文件進(jìn)行hash校驗(yàn),速度較慢;
  • 只保證了APK內(nèi)各文件的完整性,APK(zip包)其它內(nèi)容的完整性未保證。

V2簽名的優(yōu)勢(shì)

  • 只需進(jìn)行一次hash校驗(yàn),速度快;

    不需要計(jì)算所有文件的摘要,以分塊形式進(jìn)行hash,支持并行計(jì)算。

  • 除保證了APK內(nèi)各文件的完整性,APK(zip包)中數(shù)據(jù)區(qū)中央目錄中央目錄結(jié)尾記錄的完整性均得到了保證。

    zip文件的這三個(gè)區(qū)塊均有擴(kuò)展字段,JAR簽名因?yàn)橹恍r?yàn)文件hash,這部分的完整性未保證。

4. 回顧

現(xiàn)在我們可以解答Apk簽名的基本概念和用法前言中提出的問題了:

簽名校驗(yàn)的機(jī)制是什么?具體校驗(yàn)的是什么內(nèi)容?

APK簽名是為了保證APK的完整性和來(lái)源的真實(shí)性,分為JAR簽名和V2簽名兩種方案。核心思想均是計(jì)算APK內(nèi)容的hash,再使用簽名算法對(duì)hash進(jìn)行簽名。校驗(yàn)時(shí)通過簽名者公鑰解密簽名,再與校驗(yàn)者計(jì)算的APK內(nèi)容hash進(jìn)行比對(duì),一致則校驗(yàn)通過。


申請(qǐng)第三方SDK(如微信支付)時(shí)填入的SAH1值是什么?

簽名證書的指紋,在申請(qǐng)第三方SDK時(shí),需填入APK包名和證書指紋,SDK開發(fā)者后臺(tái)會(huì)根據(jù)這兩個(gè)值生成一個(gè)key。第三方SDK在初始化時(shí),會(huì)從系統(tǒng)中獲取當(dāng)前APK的包名、簽名證書指紋以及key,然后將此指紋上傳到其服務(wù)器,然后校驗(yàn)包名、簽名證書指紋是否與此key綁定,校驗(yàn)通過后才進(jìn)行授權(quán)。


目前眾多的快速批量打包方案又是如何繞過簽名檢驗(yàn)的?

在V2方案出現(xiàn)之前,快速批量打包方案有3類:

  1. 反編譯APK后修改渠道值,再重新打包

    這種方案實(shí)際上是重新簽名,因有反編譯、重新打包、簽名的過程,速度相對(duì)后兩種方案較慢;

  2. 將渠道信息以文件形式寫入META-INF目錄中

    因?yàn)镸ETA-INF目錄是用來(lái)存放簽名的,其本身無(wú)法加入簽名校驗(yàn)中,在META-INF目錄中添加文件不會(huì)破壞原有簽名。此方案需同時(shí)修改zip數(shù)據(jù)區(qū)中央目錄中央目錄結(jié)尾記錄;

  3. 將渠道信息寫到zip中央目錄結(jié)尾記錄的comment字段中

    通過前面分析zip文件結(jié)構(gòu),可以發(fā)現(xiàn)中央目錄結(jié)尾記錄最后注釋字段,這部分內(nèi)容在JAR簽名方案中同樣不在簽名校驗(yàn)范圍中,故添加注釋也不會(huì)破壞原有簽名。此方案只需修改中央目錄結(jié)尾記錄;

v2簽名后apk分塊結(jié)構(gòu)

在V2方案出現(xiàn)之后,因同時(shí)保證了數(shù)據(jù)區(qū)中央目錄中央目錄結(jié)尾記錄的完整性,故方案2、3均不適用了。那是不是就沒有快速批量打包的可能了呢?當(dāng)然不是,可以從APK簽名分塊中著手。再回過頭來(lái)看一下APK簽名分塊的結(jié)構(gòu):

  • size of block,以字節(jié)數(shù)(不含此字段)計(jì) (uint64)
  • 帶 uint64 長(zhǎng)度前綴的“ID-值”對(duì)序列:
    • ID (uint32)
    • value(可變長(zhǎng)度:“ID-值”對(duì)的長(zhǎng)度 - 4 個(gè)字節(jié))
  • size of block,以字節(jié)數(shù)計(jì) - 與第一個(gè)字段相同 (uint64)
  • magic“APK 簽名分塊 42”(16 個(gè)字節(jié))

APK簽名分塊中有一個(gè)ID-VALUE序列, 簽名信息(APK 簽名方案 v2 分塊)只存儲(chǔ)在ID 為 0x7109871a的ID-VALUE中,通過分析簽名校驗(yàn)源碼可以發(fā)現(xiàn),其它ID-VALUE數(shù)據(jù)是未被解析的,也就是說除APK 簽名方案 v2 分塊外,其余ID-VALUE是不影響簽名校驗(yàn)的。故可以定義一個(gè)新的ID-VALUE,將渠道信息寫入APK簽名分塊中。因?yàn)閂2方案只保證了第1、3、4部分和第 2 部分(APK簽名分塊)包含的APK 簽名方案 v2分塊中的 signed data 分塊的完整性。新寫入的ID-VALUE不受保護(hù),所以此方案可行。實(shí)際上美團(tuán)新一代渠道包生成工具Walle就是以這個(gè)方案實(shí)現(xiàn)的。


好了,到這里APK簽名機(jī)制的全部?jī)?nèi)部就分析完了,相信大家看完這三篇文章之后,對(duì)JAR簽名和V2簽名機(jī)制都有了大致的了解,有興趣的同學(xué)可以閱讀簽名和校驗(yàn)的源碼進(jìn)一步分析。


  1. Apk簽名的基本概念和用法
  2. Apk簽名機(jī)制之——JAR簽名機(jī)制詳解
  3. Apk簽名機(jī)制之——V2簽名機(jī)制詳解(本篇)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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