apk簽名過程及多渠道

apk簽名過程及多渠道

公司業(yè)務(wù)渠道較多共有70多個(gè)渠道,打包時(shí)間較長,所以抽時(shí)間研究一下美團(tuán)的多渠道打包。本文介紹常見的多渠道打包方式:productFlavors方式,apktool,美團(tuán)1.0,美團(tuán)2.0,騰訊 這些方式技術(shù)從舊到新,試圖說起多渠道打包的脈絡(luò)。

productFlavors

productFlavors不用切換項(xiàng)目分支就可以編譯調(diào)試不同項(xiàng)目版本的APK,并且可以快速打包所有項(xiàng)目版本的APK。例如是開發(fā)第三方Android OS的時(shí)候,由于要給不同的廠商做定制,并且適配不同的硬件平臺(tái),所以發(fā)版本的時(shí)候,經(jīng)常要切換項(xiàng)目分支,然后逐個(gè)編譯APK。</br>關(guān)于更多productFlavor介紹參考:productFlavors詳細(xì)使用 </br>
productFlavors多渠道打包具體詳情參見:Gradle實(shí)戰(zhàn):Android多渠道打包方案匯總 </br>

早期的多渠道打包基本上是采用這種方式。首先,在AndroidManifest.xml中添加渠道信息占位符:

<meta-data android:name="InstallChannel" android:value="${InstallChannel}" />

然后,通過Gradle Plugin提供的productFlavors標(biāo)簽,添加渠道信息:

productFlavors{
    "YingYongBao"{
        manifestPlaceholders = [InstallChannel : "YingYongBao"]
    }
    "360"{
        manifestPlaceholders = [InstallChannel : "360"]
    }
}

這樣,Gradle編譯生成多渠道包時(shí),會(huì)用不同的渠道信息替換AndroidManifest.xml中的占位符。我們?cè)诖a中,也就可以直接讀取AndroidManifest.xml中的渠道信息了。

但是,這種方式存在一些缺點(diǎn):

  1. 每生成一個(gè)渠道包,都要重新執(zhí)行一遍構(gòu)建流程,效率太低,只適用于渠道較少的場景。
  2. Gradle會(huì)為每個(gè)渠道包生成一個(gè)不同的BuildConfig.java類,記錄渠道信息,導(dǎo)致每個(gè)渠道包的DEX的CRC值都不同。一般情況下,這是沒有影響的。但是如果你使用了微信的Tinker熱補(bǔ)丁方案,那么就需要為不同的渠道包打不同的補(bǔ)丁,這完全是不可以接受的。(因?yàn)門inker是通過對(duì)比基礎(chǔ)包APK和新包APK生成差分補(bǔ)丁,然后再把補(bǔ)丁和基礎(chǔ)包APK一起合成新包APK。這就要求用于生成差分補(bǔ)丁的基礎(chǔ)包DEX和用于合成新包的基礎(chǔ)包DEX是完全一致的,即:每一個(gè)基礎(chǔ)渠道包的DEX文件是完全一致的,不然就會(huì)合成失?。?/li>

針對(duì)上述問題市面上出現(xiàn)了很多第三方,其中比較突出的是apktool,mcxiaoke的packer-ng-plugin,美圖的walle和騰訊的VasDolly

apk簽名過程及多渠道方案

現(xiàn)市面上存在的多渠道打包方式的原理大都是改apk文件,如此會(huì)造成簽名驗(yàn)證問題。如此要掌握多渠道,需要先了解apk的簽名過程。apk的簽名先后有v1,v2,v3三種。

簽名相關(guān)的基礎(chǔ)知識(shí)

在了解apk的簽名方式之前,我們先要了解簽名相關(guān)的基礎(chǔ)知識(shí)

數(shù)據(jù)摘要

數(shù)據(jù)摘要算法是一種能產(chǎn)生特定輸出格式的算法,其原理是根據(jù)一定的運(yùn)算規(guī)則對(duì)原始數(shù)據(jù)進(jìn)行某種形式的信息提取,被提取出的信息就是原始數(shù)據(jù)的消息摘要,也稱為數(shù)據(jù)指紋。
一般情況下,數(shù)據(jù)摘要算法具有以下特點(diǎn):

  • 無論輸入數(shù)據(jù)有多大(長),計(jì)算出來的數(shù)據(jù)摘要的長度總是固定的。例如:MD5算法計(jì)算出的數(shù)據(jù)摘要有128Bit。
    一般情況下(不考慮碰撞的情況下),只要原始數(shù)據(jù)不同,那么其對(duì)應(yīng)的數(shù)據(jù)摘要就不會(huì)相同。
  • 同時(shí),只要原始數(shù)據(jù)有任何改動(dòng),那么其數(shù)據(jù)摘要也會(huì)完全不同。即:相同的原始數(shù)據(jù)必有相同的數(shù)據(jù)摘要,不同的原始數(shù)據(jù),其數(shù)據(jù)摘要也必然不同。
  • 不可逆性 即只能正向提取原始數(shù)據(jù)的數(shù)據(jù)摘要,而無法從數(shù)據(jù)摘要中恢復(fù)出原始數(shù)據(jù)。
    著名的摘要算法有RSA公司的MD5算法和SHA系列算法。

數(shù)字簽名和數(shù)字證書

數(shù)字簽名和數(shù)字證書是成對(duì)出現(xiàn)的,兩者不可分離(數(shù)字簽名主要用來校驗(yàn)數(shù)據(jù)的完整性,數(shù)字證書主要用來確保公鑰的安全發(fā)放)。
要明白數(shù)字簽名的概念,必須要了解數(shù)據(jù)的加密、傳輸和校驗(yàn)流程。一般情況下,要實(shí)現(xiàn)數(shù)據(jù)的可靠通信,需要解決以下兩個(gè)問題:

  1. 確定數(shù)據(jù)的來源是其真正的發(fā)送者。
  2. 確保數(shù)據(jù)在傳輸過程中,沒有被篡改,或者若被篡改了,可以及時(shí)發(fā)現(xiàn)。

而數(shù)字簽名,就是為了解決這兩個(gè)問題而誕生的。
首先,數(shù)據(jù)的發(fā)送者需要先申請(qǐng)一對(duì)公私鑰對(duì),并將公鑰交給數(shù)據(jù)接收者。
然后,若數(shù)據(jù)發(fā)送者需要發(fā)送數(shù)據(jù)給接收者,則首先要根據(jù)原始數(shù)據(jù),生成一份數(shù)字簽名,然后把原始數(shù)據(jù)和數(shù)字簽名一起發(fā)送給接收者。
數(shù)字簽名由以下兩步計(jì)算得來:

  1. 計(jì)算發(fā)送數(shù)據(jù)的數(shù)據(jù)摘要
  2. 用私鑰對(duì)提取的數(shù)據(jù)摘要進(jìn)行加密
    這樣,數(shù)據(jù)接收者拿到的消息就包含了兩塊內(nèi)容:
  3. 原始數(shù)據(jù)內(nèi)容
  4. 附加的數(shù)字簽名

接下來,接收者就會(huì)通過以下幾步,校驗(yàn)數(shù)據(jù)的真實(shí)性:

  1. 用相同的摘要算法計(jì)算出原始數(shù)據(jù)的數(shù)據(jù)摘要。
  2. 用預(yù)先得到的公鑰解密數(shù)字簽名。
  3. 對(duì)比簽名得到的數(shù)據(jù)是否一致,如果一致,則說明數(shù)據(jù)沒有被篡改,否則數(shù)據(jù)就是臟數(shù)據(jù)了

因?yàn)樗借€只有發(fā)送者才有,所以其他人無法偽造數(shù)字簽名。這樣通過數(shù)字簽名就確保了數(shù)據(jù)的可靠傳輸。
綜上所述,數(shù)字簽名就是只有發(fā)送者才能產(chǎn)生的別人無法偽造的一段數(shù)字串,這段數(shù)字串同時(shí)也是對(duì)發(fā)送者發(fā)送數(shù)據(jù)真實(shí)性的一個(gè)有效證明。

想法雖好,但是上面的整個(gè)流程,有一個(gè)前提,就是數(shù)據(jù)接收者能夠正確拿到發(fā)送者的公鑰。如果接收者拿到的公鑰被篡改了,那么壞人就會(huì)被當(dāng)成好人,而真正的數(shù)據(jù)發(fā)送者發(fā)送的數(shù)據(jù)則會(huì)被視作臟數(shù)據(jù)。那怎么才能保證公鑰的安全性那?這就要靠數(shù)字證書來解決了。

數(shù)字證書是由有公信力的證書中心(CA)頒發(fā)給申請(qǐng)者的證書,主要包含了:證書的發(fā)布機(jī)構(gòu)、證書的有效期、申請(qǐng)者的公鑰、申請(qǐng)者信息、數(shù)字簽名使用的算法,以及證書內(nèi)容的數(shù)字簽名。

可見,數(shù)字證書也用到了數(shù)字簽名技術(shù)。只不過簽名的內(nèi)容是數(shù)據(jù)發(fā)送方的公鑰,以及一些其它證書信息。
這樣數(shù)據(jù)發(fā)送者發(fā)送的消息就包含了三部分內(nèi)容:

  1. 原始數(shù)據(jù)內(nèi)容
  2. 附加的數(shù)字簽名
  3. 申請(qǐng)的數(shù)字證書。

接收者拿到數(shù)據(jù)后,首先會(huì)根據(jù)CA的公鑰,解碼出發(fā)送者的公鑰。然后就與上面的校驗(yàn)流程完全相同了。

所以,數(shù)字證書主要解決了公鑰的安全發(fā)放問題。
因此,包含數(shù)字證書的整個(gè)簽名和校驗(yàn)流程如下圖所示:

[圖片上傳失敗...(image-ed00c-1560483246318)]

V1簽名和多渠道打包方案

在android 7.0(N)之前是這種。

V1簽名機(jī)制

默認(rèn)情況下,APK使用的就是V1簽名。解壓APK后,在META-INF目錄下,可以看到三個(gè)文件:MANIFEST.MF、CERT.SF、CERT.RSA。它們都是V1簽名的產(chǎn)物。

其中,MANIFEST.MF文件內(nèi)容如下所示:

[圖片上傳失敗...(image-da5e67-1560483246319)]

它記錄了APK中所有原始文件的數(shù)據(jù)摘要的Base64編碼,而數(shù)據(jù)摘要算法就是SHA1。

CERT.SF文件內(nèi)容如下所示:

[圖片上傳失敗...(image-7ba85f-1560483246319)]

SHA1-Digest-Manifest-Main-Attributes主屬性記錄了MANIFEST.MF文件所有主屬性的數(shù)據(jù)摘要的Base64編碼。</br>SHA1-Digest-Manifest則記錄了整個(gè)MANIFEST.MF文件的數(shù)據(jù)摘要的Base64編碼。</br>其余的普通屬性則和MANIFEST.MF中的屬性一一對(duì)應(yīng),分別記錄了對(duì)應(yīng)數(shù)據(jù)塊的數(shù)據(jù)摘要的Base64編碼。例如:CERT.SF文件中skin_drawable_btm_line.xml對(duì)應(yīng)的SHA1-Digest,就是下面內(nèi)容的數(shù)據(jù)摘要的Base64編碼。

Name: res/drawable/skin_drawable_btm_line.xml
SHA1-Digest: JqJbk6/AsWZMcGVehCXb33Cdtrk=
\r\n

這里要注意的是:最后一行的換行符是必不可少,需要參與計(jì)算的。

CERT.RSA文件包含了對(duì)CERT.SF文件的數(shù)字簽名和開發(fā)者的數(shù)字證書。RSA就是計(jì)算數(shù)字簽名使用的非對(duì)稱加密算法。

V1簽名的詳細(xì)流程可參考SignApk.java,整個(gè)簽名流程如下圖所示:

[圖片上傳失敗...(image-4ab8d7-1560483246319)]

整個(gè)簽名機(jī)制的最終產(chǎn)物就是MANIFEST.MF、CERT.SF、CERT.RSA三個(gè)文件。

v1校驗(yàn)流程

在安裝APK時(shí),Android系統(tǒng)會(huì)校驗(yàn)簽名,檢查APK是否被篡改。代碼流程是:PackageManagerService.java -> PackageParser.java,PackageParser類負(fù)責(zé)V1簽名的具體校驗(yàn)。整個(gè)校驗(yàn)流程如下圖所示:

[圖片上傳失敗...(image-ea7c2f-1560483246319)]

若中間任何一步校驗(yàn)失敗,APK就不能安裝。

OK,了解了V1的簽名和校驗(yàn)流程。我們來看下,V1簽名是怎么保證APK文件不被篡改的?
首先,如果破壞者修改了APK中的任何文件,那么被篡改文件的數(shù)據(jù)摘要的Base64編碼就和MANIFEST.MF文件的記錄值不一致,導(dǎo)致校驗(yàn)失敗。
其次,如果破壞者同時(shí)修改了對(duì)應(yīng)文件在MANIFEST.MF文件中的Base64值,那么MANIFEST.MF中對(duì)應(yīng)數(shù)據(jù)塊的Base64值就和CERT.SF文件中的記錄值不一致,導(dǎo)致校驗(yàn)失敗。
最后,如果破壞者更進(jìn)一步,同時(shí)修改了對(duì)應(yīng)文件在CERT.SF文件中的Base64值,那么CERT.SF的數(shù)字簽名就和CERT.RSA記錄的簽名不一致,也會(huì)校驗(yàn)失敗。
那有沒有可能繼續(xù)偽造CERT.SF的數(shù)字簽名那?理論上不可能,因?yàn)槠茐恼邲]有開發(fā)者的私鑰。那破壞者是不是可以用自己的私鑰和數(shù)字證書重新簽名那,這倒是完全可以!

綜上所述,任何對(duì)在MANIFEST.MF中有對(duì)應(yīng)數(shù)字摘要的文件修改都會(huì)導(dǎo)致簽名失敗,除非重新簽名。任何對(duì)在MANIFEST.MF文件的修改也會(huì)導(dǎo)致簽名失敗。
如此針對(duì)v1我們可以從以下3個(gè)方面下手避免添加渠道信息后導(dǎo)致簽名失敗。

  1. 添加不被簽名包含的文件寫入多渠道信息。我們發(fā)現(xiàn)在META-INF中新建的文件是不會(huì)改變簽名結(jié)構(gòu)的,如此可知META-INF中新建文件寫入渠道信息,其中美團(tuán)的第一代打包工具是這樣做的。
  2. 我們可以通過逆向手段,添加渠道信息。即解壓apk,添加渠道信息,重新簽名。市面上apktool是這樣弄的
  3. 修改apk文件。我們發(fā)現(xiàn)v1的apk分三部分:內(nèi)容快,中央目錄塊和中央結(jié)束塊(EOCD),其中EOCD是生成apk時(shí)自動(dòng)加進(jìn)去的,不受簽名保護(hù),如此可在其中添加渠道信息。市面上mcxiaoke的packer-ng-plugin和騰訊的VasDolly是采用這種原理

apktool

ApkTool是一個(gè)逆向分析工具,可以把APK解開,添加代碼后,重新打包成APK,當(dāng)然這些都是通過腳本實(shí)現(xiàn)的。因此,基于ApkTool的多渠道打包方案分為以下幾步:

復(fù)制一份新的APK
通過ApkTool工具,解壓APK(apktool d origin.apk)
刪除已有簽名信息
添加渠道信息(可以在APK的任何文件添加渠道信息)
通過ApkTool工具,重新打包生成新APK(apktool b newApkDir)
重新簽名
經(jīng)過測(cè)試,這種方案完全是可行的。

優(yōu)點(diǎn):
不需要重新構(gòu)建新渠道包,僅需要復(fù)制修改就可以了。并且因?yàn)槭侵匦潞灻?,所以同時(shí)支持V1和V2簽名。

缺點(diǎn):
ApkTool工具不穩(wěn)定,曾經(jīng)遇到過升級(jí)Gradle Plugin版本后,低版本ApkTool解壓APK失敗的情況。
生成新渠道包時(shí),需要重新解包、打包和簽名,而這幾步操作又是相對(duì)比較耗時(shí)的。經(jīng)過測(cè)試:生成企鵝電競10個(gè)渠道包需要16分鐘左右,雖然比Gradle Plugin方案減少很多耗時(shí)。但是若需要同時(shí)生成上百個(gè)渠道包,則需要幾個(gè)小時(shí),顯然不適合渠道非常多的業(yè)務(wù)場景。

修改apk

apktool存在諸多缺點(diǎn),針對(duì)v1我采用的還是添加文件和修改apk來添加渠道信息的。修改文件原理教簡單,下面我們重點(diǎn)介紹修改apk

apk文件結(jié)構(gòu)

修改apk得先知道其結(jié)構(gòu)。APK文件本質(zhì)上是一個(gè)ZIP壓縮包,而ZIP格式是固定的,主要由三部分構(gòu)成,如下圖所示:

[圖片上傳失敗...(image-74cf23-1560483246319)]

  • 第一部分是內(nèi)容塊,所有的壓縮文件都在這部分。每個(gè)壓縮文件都有一個(gè)local file header,主要記錄了文件名、壓縮算法、壓縮前后的文件大小、修改時(shí)間、CRC32值等。
  • 第二部分稱為中央目錄,包含了多個(gè)central directory file header(和第一部分的local file header一一對(duì)應(yīng)),每個(gè)中央目錄文件頭主要記錄了壓縮算法、注釋信息、對(duì)應(yīng)local file header的偏移量等,方便快速定位數(shù)據(jù)。
  • 最后一部分是EOCD,主要記錄了中央目錄大小、偏移量和ZIP注釋信息等,其詳細(xì)結(jié)構(gòu)如下圖所示:

[圖片上傳失敗...(image-58a6c5-1560483246319)]

根據(jù)之前的V1簽名和校驗(yàn)機(jī)制可知,V1簽名只會(huì)檢驗(yàn)第一部分的所有壓縮文件,而不理會(huì)后兩部分內(nèi)容。因此,只要把渠道信息寫入到后兩塊內(nèi)容就可以通過V1校驗(yàn),而EOCD的注釋字段無疑是最好的選擇。

向apk文件結(jié)構(gòu)中寫入渠道信息

既然找到了突破口,那么基于V1簽名的多渠道打包方案就應(yīng)運(yùn)而生:在APK文件的注釋字段,添加渠道信息。

整個(gè)方案包括以下幾步:

  1. 復(fù)制APK
  2. 找到EOCD數(shù)據(jù)塊
  3. 修改注釋長度
  4. 添加渠道信息
  5. 添加渠道信息長度
  6. 添加魔數(shù)
    添加渠道信息后的EOCD數(shù)據(jù)塊如下所示:

[圖片上傳失敗...(image-91f24-1560483246319)]

這里添加魔數(shù)的好處是方便從后向前讀取數(shù)據(jù),定位渠道信息。
因此,讀取渠道信息包括以下幾步:

  1. 定位到魔數(shù)
  2. 向前讀兩個(gè)字節(jié),確定渠道信息的長度LEN
  3. 繼續(xù)向前讀LEN字節(jié),就是渠道信息了。

通過16進(jìn)制編輯器,可以查看到添加渠道信息后的APK(小端模式),如下所示:

[圖片上傳失敗...(image-d8a279-1560483246319)]

6C 74 6C 6F 76 75 7A 68是魔數(shù),04 00表示渠道信息長度為4,6C 65 6F 6E就是渠道信息leon了。0E 00就是APK注釋長度了,正好是15。

雖說整個(gè)方案很清晰,但是在找到EOCD數(shù)據(jù)塊這步遇到一個(gè)問題。如果APK本身沒有注釋,那最后22字節(jié)就是EOCD。但是若APK本身已經(jīng)包含了注釋字段,那怎么確定EOCD的起始位置那?這里借鑒了系統(tǒng)V2簽名確定EOCD位置的方案。整個(gè)計(jì)算流程如下圖所示:

[圖片上傳失敗...(image-b692b2-1560483246319)]

整個(gè)方案介紹完了,該方案的最大優(yōu)點(diǎn)就是:不需要解壓縮APK,不需要重新簽名,只需要復(fù)制APK,在注釋字段添加渠道信息。每個(gè)渠道包僅需幾秒的耗時(shí),非常適合渠道較多的APK。

但是好景不長,Android7.0之后新增了V2簽名,該簽名會(huì)校驗(yàn)整個(gè)APK的數(shù)據(jù)摘要,導(dǎo)致上述渠道打包方案失效。所以如果想繼續(xù)使用上述方案,需要關(guān)閉Gradle Plugin中的V2簽名選項(xiàng),禁用V2簽名。

V2簽名和多渠道打包方案

為什么需要V2簽名

從前面的V1簽名介紹,可以知道V1存在兩個(gè)弊端:

    • MANIFEST.MF中的數(shù)據(jù)摘要是基于原始未壓縮文件計(jì)算的。因此在校驗(yàn)時(shí),需要先解壓出原始文件,才能進(jìn)行校驗(yàn)。而解壓操作無疑是耗時(shí)的。
    • V1簽名僅僅校驗(yàn)APK第一部分中的文件,缺少對(duì)APK的完整性校驗(yàn)。因此,在簽名后,我們還可以修改APK文件,例如:通過zipalign進(jìn)行字節(jié)對(duì)齊后,仍然可以正常安裝。

正是基于這兩點(diǎn),Google提出了V2簽名,解決了上述兩個(gè)問題:

  1. V2簽名是對(duì)APK本身進(jìn)行數(shù)據(jù)摘要計(jì)算,不存在解壓APK的操作,減少了校驗(yàn)時(shí)間。
  2. V2簽名是針對(duì)整個(gè)APK進(jìn)行校驗(yàn)(不包含簽名塊本身),因此對(duì)APK的任何修改(包括添加注釋、zipalign字節(jié)對(duì)齊)都無法通過V2簽名的校驗(yàn)。
    關(guān)于第一點(diǎn)的耗時(shí)問題,這里有一份實(shí)驗(yàn)室數(shù)據(jù)(Nexus 6P、Android 7.1.1)可供參考。
APK安裝耗時(shí)對(duì)比 取5次平均耗時(shí)(秒)
V1簽名APK 11.64
V2簽名APK 4.42

可見,V2簽名對(duì)APK的安裝速度還是提升不少的。

V2簽名機(jī)制

不同于V1,V2簽名會(huì)生成一個(gè)簽名塊,插入到APK中。因此,V2簽名后的APK結(jié)構(gòu)如下圖所示:

[圖片上傳失敗...(image-ba6827-1560483246319)]

APK簽名塊位于中央目錄之前,文件數(shù)據(jù)之后。V2簽名同時(shí)修改了EOCD中的中央目錄的偏移量,使簽名后的APK還符合ZIP結(jié)構(gòu)。

APK簽名塊的具體結(jié)構(gòu)如下圖所示:

[圖片上傳失敗...(image-54d006-1560483246319)]

  1. 首先是8字節(jié)的簽名塊大小,此大小不包含該字段本身的8字節(jié);
  2. 其次就是ID-Value序列,就是一個(gè)4字節(jié)的ID和對(duì)應(yīng)的數(shù)據(jù);
  3. 然后又是一個(gè)8字節(jié)的簽名塊大小,與開始的8字節(jié)是相等的;最后是16字節(jié)的簽名塊魔數(shù)。
  4. 其中,ID為0x7109871a對(duì)應(yīng)的Value就是V2簽名塊數(shù)據(jù)。

V2簽名塊的生成可參考ApkSignerV2,整體結(jié)構(gòu)和流程如下圖所示:

[圖片上傳失敗...(image-f003cc-1560483246319)]

  1. 首先,根據(jù)多個(gè)簽名算法,計(jì)算出整個(gè)APK的數(shù)據(jù)摘要,組成左上角的APK數(shù)據(jù)摘要集;
  2. 接著,把最左側(cè)一列的數(shù)據(jù)摘要、數(shù)字證書和額外屬性組裝起來,形成類似于V1簽名的“MF”文件(第二列第一行);
  3. 其次,再用相同的私鑰,不同的簽名算法,計(jì)算出“MF”文件的數(shù)字簽名,形成類似于V1簽名的“SF”文件(第二列第二行);
  4. 然后,把第二列的類似MF文件、類似SF文件和開發(fā)者公鑰一起組裝成通過單個(gè)keystore簽名后的v2簽名塊(第三列第一行)。
  5. 最后,把多個(gè)keystore簽名后的簽名塊組裝起來,就是完整的V2簽名塊了(Android中允許使用多個(gè)keystore對(duì)apk進(jìn)行簽名)。

上述流程比較繁瑣。簡而言之,單個(gè)keystore簽名塊主要由三部分組成,分別是上圖中第二列的三個(gè)數(shù)據(jù)塊:類似MF文件、類似SF文件和開發(fā)者公鑰,其結(jié)構(gòu)如下圖所示:

[圖片上傳失敗...(image-e251d8-1560483246319)]

除此之外,Google也優(yōu)化了計(jì)算數(shù)據(jù)摘要的算法,使得可以并行計(jì)算,如下圖所示:

[圖片上傳失敗...(image-6be7d4-1560483246319)]

數(shù)據(jù)摘要的計(jì)算包括以下幾步:

  1. 首先,將上述APK中文件內(nèi)容塊、中央目錄、EOCD按照1MB大小分割成一些小塊。
  2. 然后,計(jì)算每個(gè)小塊的數(shù)據(jù)摘要,基礎(chǔ)數(shù)據(jù)是0xa5 + 塊字節(jié)長度 + 塊內(nèi)容。
  3. 最后,計(jì)算整體的數(shù)據(jù)摘要,基礎(chǔ)數(shù)據(jù)是0x5a + 數(shù)據(jù)塊的數(shù)量 + 每個(gè)數(shù)據(jù)塊的摘要內(nèi)容。

這樣,每個(gè)數(shù)據(jù)塊的數(shù)據(jù)摘要就可以并行計(jì)算,加快了V2簽名和校驗(yàn)的速度。

V2校驗(yàn)流程

Android Gradle Plugin2.2之上默認(rèn)會(huì)同時(shí)開啟V1和V2簽名,同時(shí)包含V1和V2簽名的CERT.SF文件會(huì)有一個(gè)特殊的主屬性,如下圖所示:

[圖片上傳失敗...(image-ecbad4-1560483246319)]

該屬性會(huì)強(qiáng)制APK走V2校驗(yàn)流程(7.0之上),以充分利用V2簽名的優(yōu)勢(shì)(速度快和更完善的校驗(yàn)機(jī)制)。
因此,同時(shí)包含V1和V2簽名的APK的校驗(yàn)流程如下所示:

[圖片上傳失敗...(image-11afa7-1560483246319)]

簡而言之:優(yōu)先校驗(yàn)V2,沒有或者不認(rèn)識(shí)V2,則校驗(yàn)V1。

這里引申出另外一個(gè)問題:APK簽名時(shí),只有V2簽名,沒有V1簽名行不行?
經(jīng)過嘗試,這種情況是可以編譯通過的,并且在Android 7.0之上也可以正確安裝和運(yùn)行。但是7.0之下,因?yàn)椴徽J(rèn)識(shí)V2,又沒有V1簽名,所以會(huì)報(bào)沒有簽名的錯(cuò)誤。

OK,明確了Android平臺(tái)對(duì)V1和V2簽名的校驗(yàn)選擇之后,我們來看下V2簽名的具體校驗(yàn)流程(PackageManagerService.java -> PackageParser.java-> ApkSignatureSchemeV2Verifier.java),如下圖所示:

[圖片上傳失敗...(image-632fdb-1560483246319)]

其中,最強(qiáng)簽名算法是根據(jù)該算法使用的數(shù)據(jù)摘要算法來對(duì)比產(chǎn)生的,比如:SHA512 > SHA256。

校驗(yàn)成功的定義是至少找到一個(gè)keystore對(duì)應(yīng)的簽名塊,并且所有簽名塊都按照上述流程校驗(yàn)成功。

下面我們來看下V2簽名是怎么保證APK不被篡改的?

首先,如果破壞者修改了APK文件的任何部分(簽名塊本身除外),那么APK的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中記錄的數(shù)據(jù)摘要不一致,導(dǎo)致校驗(yàn)失敗。

其次,如果破壞者同時(shí)修改了“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要,那么“MF”數(shù)據(jù)塊的數(shù)字簽名就和“SF”數(shù)據(jù)塊中記錄的數(shù)字簽名不一致,導(dǎo)致校驗(yàn)失敗。

然后,如果破壞者使用自己的私鑰去加密生成“SF”數(shù)據(jù)塊,那么使用開發(fā)者的公鑰去解密“SF”數(shù)據(jù)塊中的數(shù)字簽名就會(huì)失??;

最后,更進(jìn)一步,若破壞者甚至替換了開發(fā)者公鑰,那么使用數(shù)字證書中的公鑰校驗(yàn)簽名塊中的公鑰就會(huì)失敗,這也正是數(shù)字證書的作用。

綜上所述,任何對(duì)APK的修改,在安裝時(shí)都會(huì)失敗,除非對(duì)APK重新簽名。但是相同包名,不同簽名的APK也是不能同時(shí)安裝的。

其實(shí)也很簡單,原來Android系統(tǒng)在校驗(yàn)APK的數(shù)據(jù)摘要時(shí),首先會(huì)把EOCD的中央目錄偏移量替換成簽名塊的偏移量,然后再計(jì)算數(shù)據(jù)摘要。而簽名塊的偏移量不就是v2簽名之前的中央目錄偏移量嘛?。?!,因此,這樣計(jì)算出的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要完全一致了。具體代碼邏輯,可參考ApkSignatureSchemeV2Verifier.java的416 ~ 420行

基于V2簽名的多渠道打包方案

在上節(jié)V2簽名的校驗(yàn)流程中,有一個(gè)很重要的細(xì)節(jié):Android系統(tǒng)只會(huì)關(guān)注ID為0x7109871a的V2簽名塊,并且忽略其他的ID-Value,同時(shí)V2簽名只會(huì)保護(hù)APK本身,不包含簽名塊。

因此,基于V2簽名的多渠道打包方案就應(yīng)運(yùn)而生:在APK簽名塊中添加一個(gè)ID-Value,存儲(chǔ)渠道信息。

整個(gè)方案包括以下幾步:

  1. 找到APK的EOCD塊
  2. 找到APK簽名塊
  3. 獲取已有的ID-Value Pair
  4. 添加包含渠道信息的ID-Value
  5. 基于所有的ID-Value生成新的簽名塊
  6. 修改EOCD的中央目錄的偏移量(上面已介紹過:修改EOCD的中央目錄偏移量,不會(huì)導(dǎo)致數(shù)據(jù)摘要校驗(yàn)失敗)
  7. 用新的簽名塊替代舊的簽名塊,生成帶有渠道信息的APK

實(shí)際上,除了渠道信息,我們可以在APK簽名塊中添加任何輔助信息。

通過16進(jìn)制編輯器,可以查看到添加渠道信息后的APK(小端模式),如下所示:

[圖片上傳失敗...(image-20e35e-1560483246319)]

V3簽名和多渠道打包方案

在android 9.0(N)引入的

為什么要有v3

主要是為了換簽名

生成簽名的時(shí),可以指定一個(gè)有效時(shí)間,這個(gè)時(shí)間默認(rèn)為 25 年,并且 Google Play 也有硬性規(guī)定,上架的 App 簽名有效期必須在 2033-10-22 日期之后。所以只要不是手欠修改了這個(gè)有效期,在當(dāng)下這個(gè)時(shí)刻,是不會(huì)有問題,畢竟到現(xiàn)在還沒有一款 App 存在 25 年。當(dāng)然還有可能是公司被收購 需要改簽名
有些問題不在眼前,卻是真實(shí)存在的。對(duì)于一款上架的 App,最重要的就是用戶,而當(dāng)簽名失效之后,我們只能被迫換簽名,此時(shí)因?yàn)楹灻r?yàn)無法通過,就會(huì)導(dǎo)致舊用戶無法覆蓋安裝。這些歷史用戶唯一的選擇,就是卸載后重新安裝。
好在這不僅僅是你我的問題,天塌下來有個(gè)子高的頂著,所以別擔(dān)心,Google 已經(jīng)著手在解決這個(gè)問題了。

v3簽名塊結(jié)構(gòu)

v3版本簽名塊也分成同樣的三部分,與v2不同的是在SignerData部分,v3新增了attr塊,其中是由更小的level塊組成。每個(gè)level塊中可以存儲(chǔ)一個(gè)證書信息。前一個(gè)level塊證書驗(yàn)證下一個(gè)level證書,以此類推。最后一個(gè)level塊的證書,要符合SignerData中本身的證書,即用來簽名整個(gè)APK的公鑰所屬于的證書。兩個(gè)版本的簽名塊結(jié)構(gòu)如下:

[圖片上傳失敗...(image-95af1f-1560483246319)]

v3驗(yàn)證簽名流程

因?yàn)楹灻尿?yàn)證就是發(fā)生在一個(gè)apk包的安裝過程中,所以為了更清楚驗(yàn)證簽名的時(shí)機(jī),有必要了解整個(gè)安裝的分類與大致流程。Android安裝應(yīng)用主要有如下四種方式:

  • 系統(tǒng)應(yīng)用安裝:開機(jī)時(shí)完成,沒有安裝界面
  • 網(wǎng)絡(luò)下載的應(yīng)用安裝:通過市場應(yīng)用完成,沒有安裝界面
  • ADB工具安裝:沒有安裝界面
  • 第三方應(yīng)用安裝:通過packageinstall.apk應(yīng)用安裝,有安裝界面

但是其實(shí)無論通過哪種方式安裝都要通過PackageManagerService來完成安裝的主要工作,最終在PMS中會(huì)去驗(yàn)證簽名信息,流程如下

[圖片上傳失敗...(image-fd0b3a-1560483246319)]

安裝過程中如果發(fā)現(xiàn)有v3簽名塊,則必須使用v3簽名的驗(yàn)證機(jī)制,不能繞過。否則才使用v2簽名的驗(yàn)證機(jī)制,以此類推。

驗(yàn)證完整性

數(shù)據(jù)完整性校驗(yàn)v3與v2版本相同,原理如下:

[圖片上傳失敗...(image-29bcb8-1560483246319)]

簽名塊包括對(duì)apk第一部分,第二部分,第三部分的二進(jìn)制內(nèi)容做加密保護(hù),摘要算法以及簽名算法。簽名塊本身不做加密,這里需要特殊注意的是由于第三部分包含了對(duì)第二部分的引用偏移,因此如果簽名塊做了改變,比如在簽名過程中增加一種簽名算法,或者增加簽名者等信息就會(huì)導(dǎo)致這個(gè)引用偏移發(fā)生改變,因此在算摘要的時(shí)候需要剔除這個(gè)因素要以第三部分對(duì)簽名塊的偏移來做計(jì)算。

驗(yàn)證證書

v2版本簽名驗(yàn)證證書步驟:

  • 利用PublicKey解密Signature,得到SignerData的hash明文
  • 計(jì)算SignerData的hash值
  • 兩個(gè)值進(jìn)行比較,如果相同則認(rèn)為APK沒有被修改過,解析出SignerData中的證書。否則安裝失敗
  • 如果是第一次安裝,直接將證書保存在應(yīng)用信息中
  • 如果是更新安裝,即設(shè)備中原來存在這個(gè)應(yīng)用,驗(yàn)證之前的證書是否與本次解析的證書相同。若相同,則安裝成功,否則失敗

[圖片上傳失敗...(image-6bfa12-1560483246319)]

v3版本簽名驗(yàn)證證書步驟:(前三步同v2)

  • 利用PublicKey解密Signature,得到SignerData的hash明文
  • 計(jì)算SignerData的hash值
  • 兩個(gè)值進(jìn)行比較,如果相同則認(rèn)為APK沒有被修改過,解析出SignerData中的證書。否則安裝失敗
  • 逐個(gè)解析出level塊證書并驗(yàn)證,并保存為這個(gè)應(yīng)用的歷史證書
  • 如果是第一次安裝,直接將證書與歷史證書一并保存在應(yīng)用信息中
  • 如果是更新安裝,驗(yàn)證之前的證書與歷史證書,是否與本次解析的證書或者歷史證書中存在相同的證書,其中任意一個(gè)證書符合即可安裝

[圖片上傳失敗...(image-af6403-1560483246319)]

新特性場景舉例

其實(shí)就是當(dāng)開發(fā)者需要更換證書時(shí),即可直接用新證書新的私鑰進(jìn)行簽名。不過為了讓老應(yīng)用相信新的證書,則需要用老證書來保證。舉個(gè)例子,有兩個(gè)level塊:level 1與level 2:

  • level 1放置老證書的信息
  • level 2中放置新證書的信息以及這段數(shù)據(jù)的簽名
  • level 2中的簽名是由老私鑰進(jìn)行簽名的,則需要用老證書的公鑰來驗(yàn)證
  • 校驗(yàn)原來的證書與level 1 相同,則相信本次更新的level 2 的證書,即簽名APK的證書
  • 完成安裝并記錄新證書信息
v3多渠道方案

略 原理和v2同

參考:

http://www.itdecent.cn/p/332525b09a88
https://github.com/Meituan-Dianping/walle/
https://segmentfault.com/a/1190000015554496
https://juejin.im/entry/5a586bfaf265da3e2c3808c5
https://blog.csdn.net/u010818425/article/details/52319382
https://github.com/Tencent/VasDolly
https://cloud.tencent.com/developer/article/1004884
http://picksomething.cn/2018/05/08/Android%E5%A4%9A%E6%B8%A0%E9%81%93%E6%89%B9%E9%>87%8F%E6%89%93%E5%8C%85%EF%BC%8C%E6%94%AF%E6%8C%81%E5%8F%8B%E7%9B%9F%E5%92%8C%E7%AC%>AC%E4%B8%89%E6%96%B9%E5%8A%A0%E5%9B%BA/
http://twei.site/2016/08/31/MarkdownPad-2-%E6%94%AF%E6%8C%81%E8%A1%A8%E6%A0%BC/

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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