apk簽名過程及多渠道
公司業(yè)務(wù)渠道較多共有70多個渠道,打包時間較長恃轩,所以抽時間研究一下美團的多渠道打包脆侮。本文介紹常見的多渠道打包方式:productFlavors方式瞎领,apktool,美團1.0,美團2.0,騰訊 這些方式技術(shù)從舊到新随夸,試圖說起多渠道打包的脈絡(luò)九默。
productFlavors
productFlavors不用切換項目分支就可以編譯調(diào)試不同項目版本的APK,并且可以快速打包所有項目版本的APK宾毒。例如是開發(fā)第三方Android OS的時候驼修,由于要給不同的廠商做定制,并且適配不同的硬件平臺诈铛,所以發(fā)版本的時候乙各,經(jīng)常要切換項目分支,然后逐個編譯APK癌瘾。</br>關(guān)于更多productFlavor介紹參考:productFlavors詳細使用 </br>
productFlavors多渠道打包具體詳情參見:Gradle實戰(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編譯生成多渠道包時咬荷,會用不同的渠道信息替換AndroidManifest.xml中的占位符冠句。我們在代碼中,也就可以直接讀取AndroidManifest.xml中的渠道信息了幸乒。
但是懦底,這種方式存在一些缺點:
- 每生成一個渠道包,都要重新執(zhí)行一遍構(gòu)建流程罕扎,效率太低聚唐,只適用于渠道較少的場景。
- Gradle會為每個渠道包生成一個不同的BuildConfig.java類腔召,記錄渠道信息杆查,導(dǎo)致每個渠道包的DEX的CRC值都不同。一般情況下臀蛛,這是沒有影響的亲桦。但是如果你使用了微信的Tinker熱補丁方案,那么就需要為不同的渠道包打不同的補丁浊仆,這完全是不可以接受的客峭。(因為Tinker是通過對比基礎(chǔ)包APK和新包APK生成差分補丁,然后再把補丁和基礎(chǔ)包APK一起合成新包APK抡柿。這就要求用于生成差分補丁的基礎(chǔ)包DEX和用于合成新包的基礎(chǔ)包DEX是完全一致的舔琅,即:每一個基礎(chǔ)渠道包的DEX文件是完全一致的,不然就會合成失斏尘)
針對上述問題市面上出現(xiàn)了很多第三方搏明,其中比較突出的是apktool,mcxiaoke的packer-ng-plugin鼠锈,美圖的walle和騰訊的VasDolly
apk簽名過程及多渠道方案
現(xiàn)市面上存在的多渠道打包方式的原理大都是改apk文件,如此會造成簽名驗證問題星著。如此要掌握多渠道购笆,需要先了解apk的簽名過程。apk的簽名先后有v1,v2,v3三種虚循。
簽名相關(guān)的基礎(chǔ)知識
在了解apk的簽名方式之前同欠,我們先要了解簽名相關(guān)的基礎(chǔ)知識
數(shù)據(jù)摘要
數(shù)據(jù)摘要算法是一種能產(chǎn)生特定輸出格式的算法,其原理是根據(jù)一定的運算規(guī)則對原始數(shù)據(jù)進行某種形式的信息提取横缔,被提取出的信息就是原始數(shù)據(jù)的消息摘要铺遂,也稱為數(shù)據(jù)指紋。
一般情況下茎刚,數(shù)據(jù)摘要算法具有以下特點:
- 無論輸入數(shù)據(jù)有多大(長)襟锐,計算出來的數(shù)據(jù)摘要的長度總是固定的。例如:MD5算法計算出的數(shù)據(jù)摘要有128Bit膛锭。
一般情況下(不考慮碰撞的情況下)粮坞,只要原始數(shù)據(jù)不同,那么其對應(yīng)的數(shù)據(jù)摘要就不會相同初狰。 - 同時莫杈,只要原始數(shù)據(jù)有任何改動,那么其數(shù)據(jù)摘要也會完全不同奢入。即:相同的原始數(shù)據(jù)必有相同的數(shù)據(jù)摘要筝闹,不同的原始數(shù)據(jù),其數(shù)據(jù)摘要也必然不同腥光。
- 不可逆性 即只能正向提取原始數(shù)據(jù)的數(shù)據(jù)摘要关顷,而無法從數(shù)據(jù)摘要中恢復(fù)出原始數(shù)據(jù)。
著名的摘要算法有RSA公司的MD5算法和SHA系列算法武福。
數(shù)字簽名和數(shù)字證書
數(shù)字簽名和數(shù)字證書是成對出現(xiàn)的解寝,兩者不可分離(數(shù)字簽名主要用來校驗數(shù)據(jù)的完整性,數(shù)字證書主要用來確保公鑰的安全發(fā)放)艘儒。
要明白數(shù)字簽名的概念,必須要了解數(shù)據(jù)的加密夫偶、傳輸和校驗流程界睁。一般情況下,要實現(xiàn)數(shù)據(jù)的可靠通信兵拢,需要解決以下兩個問題:
- 確定數(shù)據(jù)的來源是其真正的發(fā)送者翻斟。
- 確保數(shù)據(jù)在傳輸過程中,沒有被篡改说铃,或者若被篡改了访惜,可以及時發(fā)現(xiàn)嘹履。
而數(shù)字簽名,就是為了解決這兩個問題而誕生的债热。
首先砾嫉,數(shù)據(jù)的發(fā)送者需要先申請一對公私鑰對,并將公鑰交給數(shù)據(jù)接收者窒篱。
然后焕刮,若數(shù)據(jù)發(fā)送者需要發(fā)送數(shù)據(jù)給接收者,則首先要根據(jù)原始數(shù)據(jù)墙杯,生成一份數(shù)字簽名配并,然后把原始數(shù)據(jù)和數(shù)字簽名一起發(fā)送給接收者。
數(shù)字簽名由以下兩步計算得來:
- 計算發(fā)送數(shù)據(jù)的數(shù)據(jù)摘要
- 用私鑰對提取的數(shù)據(jù)摘要進行加密
這樣高镐,數(shù)據(jù)接收者拿到的消息就包含了兩塊內(nèi)容: - 原始數(shù)據(jù)內(nèi)容
- 附加的數(shù)字簽名
接下來溉旋,接收者就會通過以下幾步,校驗數(shù)據(jù)的真實性:
- 用相同的摘要算法計算出原始數(shù)據(jù)的數(shù)據(jù)摘要嫉髓。
- 用預(yù)先得到的公鑰解密數(shù)字簽名观腊。
- 對比簽名得到的數(shù)據(jù)是否一致,如果一致岩喷,則說明數(shù)據(jù)沒有被篡改恕沫,否則數(shù)據(jù)就是臟數(shù)據(jù)了
因為私鑰只有發(fā)送者才有,所以其他人無法偽造數(shù)字簽名纱意。這樣通過數(shù)字簽名就確保了數(shù)據(jù)的可靠傳輸婶溯。
綜上所述,數(shù)字簽名就是只有發(fā)送者才能產(chǎn)生的別人無法偽造的一段數(shù)字串偷霉,這段數(shù)字串同時也是對發(fā)送者發(fā)送數(shù)據(jù)真實性的一個有效證明迄委。
想法雖好,但是上面的整個流程类少,有一個前提叙身,就是數(shù)據(jù)接收者能夠正確拿到發(fā)送者的公鑰。如果接收者拿到的公鑰被篡改了硫狞,那么壞人就會被當(dāng)成好人信轿,而真正的數(shù)據(jù)發(fā)送者發(fā)送的數(shù)據(jù)則會被視作臟數(shù)據(jù)。那怎么才能保證公鑰的安全性那残吩?這就要靠數(shù)字證書來解決了财忽。
數(shù)字證書是由有公信力的證書中心(CA)頒發(fā)給申請者的證書,主要包含了:證書的發(fā)布機構(gòu)泣侮、證書的有效期即彪、申請者的公鑰、申請者信息活尊、數(shù)字簽名使用的算法隶校,以及證書內(nèi)容的數(shù)字簽名。
可見深胳,數(shù)字證書也用到了數(shù)字簽名技術(shù)绰疤。只不過簽名的內(nèi)容是數(shù)據(jù)發(fā)送方的公鑰,以及一些其它證書信息稠屠。
這樣數(shù)據(jù)發(fā)送者發(fā)送的消息就包含了三部分內(nèi)容:
- 原始數(shù)據(jù)內(nèi)容
- 附加的數(shù)字簽名
- 申請的數(shù)字證書峦睡。
接收者拿到數(shù)據(jù)后,首先會根據(jù)CA的公鑰权埠,解碼出發(fā)送者的公鑰榨了。然后就與上面的校驗流程完全相同了。
所以攘蔽,數(shù)字證書主要解決了公鑰的安全發(fā)放問題龙屉。
因此,包含數(shù)字證書的整個簽名和校驗流程如下圖所示:
[圖片上傳失敗...(image-ed00c-1560483246318)]
V1簽名和多渠道打包方案
在android 7.0(N)之前是這種满俗。
V1簽名機制
默認情況下转捕,APK使用的就是V1簽名。解壓APK后唆垃,在META-INF目錄下五芝,可以看到三個文件: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則記錄了整個MANIFEST.MF文件的數(shù)據(jù)摘要的Base64編碼砖茸。</br>其余的普通屬性則和MANIFEST.MF中的屬性一一對應(yīng)隘擎,分別記錄了對應(yīng)數(shù)據(jù)塊的數(shù)據(jù)摘要的Base64編碼。例如:CERT.SF文件中skin_drawable_btm_line.xml對應(yīng)的SHA1-Digest凉夯,就是下面內(nèi)容的數(shù)據(jù)摘要的Base64編碼货葬。
Name: res/drawable/skin_drawable_btm_line.xml
SHA1-Digest: JqJbk6/AsWZMcGVehCXb33Cdtrk=
\r\n
這里要注意的是:最后一行的換行符是必不可少,需要參與計算的劲够。
CERT.RSA文件包含了對CERT.SF文件的數(shù)字簽名和開發(fā)者的數(shù)字證書宝惰。RSA就是計算數(shù)字簽名使用的非對稱加密算法。
V1簽名的詳細流程可參考SignApk.java再沧,整個簽名流程如下圖所示:
[圖片上傳失敗...(image-4ab8d7-1560483246319)]
整個簽名機制的最終產(chǎn)物就是MANIFEST.MF、CERT.SF尊残、CERT.RSA三個文件炒瘸。
v1校驗流程
在安裝APK時淤堵,Android系統(tǒng)會校驗簽名,檢查APK是否被篡改顷扩。代碼流程是:PackageManagerService.java -> PackageParser.java拐邪,PackageParser類負責(zé)V1簽名的具體校驗。整個校驗流程如下圖所示:
[圖片上傳失敗...(image-ea7c2f-1560483246319)]
若中間任何一步校驗失敗隘截,APK就不能安裝扎阶。
OK,了解了V1的簽名和校驗流程婶芭。我們來看下东臀,V1簽名是怎么保證APK文件不被篡改的?
首先犀农,如果破壞者修改了APK中的任何文件惰赋,那么被篡改文件的數(shù)據(jù)摘要的Base64編碼就和MANIFEST.MF文件的記錄值不一致,導(dǎo)致校驗失敗呵哨。
其次赁濒,如果破壞者同時修改了對應(yīng)文件在MANIFEST.MF文件中的Base64值,那么MANIFEST.MF中對應(yīng)數(shù)據(jù)塊的Base64值就和CERT.SF文件中的記錄值不一致孟害,導(dǎo)致校驗失敗拒炎。
最后,如果破壞者更進一步挨务,同時修改了對應(yīng)文件在CERT.SF文件中的Base64值击你,那么CERT.SF的數(shù)字簽名就和CERT.RSA記錄的簽名不一致,也會校驗失敗耘子。
那有沒有可能繼續(xù)偽造CERT.SF的數(shù)字簽名那果漾?理論上不可能,因為破壞者沒有開發(fā)者的私鑰谷誓。那破壞者是不是可以用自己的私鑰和數(shù)字證書重新簽名那绒障,這倒是完全可以!
綜上所述捍歪,任何對在MANIFEST.MF中有對應(yīng)數(shù)字摘要的文件修改都會導(dǎo)致簽名失敗户辱,除非重新簽名。任何對在MANIFEST.MF文件的修改也會導(dǎo)致簽名失敗糙臼。
如此針對v1我們可以從以下3個方面下手避免添加渠道信息后導(dǎo)致簽名失敗庐镐。
- 添加不被簽名包含的文件寫入多渠道信息。我們發(fā)現(xiàn)在META-INF中新建的文件是不會改變簽名結(jié)構(gòu)的变逃,如此可知META-INF中新建文件寫入渠道信息必逆,其中美團的第一代打包工具是這樣做的。
- 我們可以通過逆向手段,添加渠道信息名眉。即解壓apk粟矿,添加渠道信息,重新簽名损拢。市面上apktool是這樣弄的
- 修改apk文件陌粹。我們發(fā)現(xiàn)v1的apk分三部分:內(nèi)容快,中央目錄塊和中央結(jié)束塊(EOCD)福压,其中EOCD是生成apk時自動加進去的掏秩,不受簽名保護,如此可在其中添加渠道信息荆姆。市面上mcxiaoke的packer-ng-plugin和騰訊的VasDolly是采用這種原理
apktool
ApkTool是一個逆向分析工具蒙幻,可以把APK解開,添加代碼后胞枕,重新打包成APK杆煞,當(dāng)然這些都是通過腳本實現(xiàn)的。因此腐泻,基于ApkTool的多渠道打包方案分為以下幾步:
復(fù)制一份新的APK
通過ApkTool工具决乎,解壓APK(apktool d origin.apk)
刪除已有簽名信息
添加渠道信息(可以在APK的任何文件添加渠道信息)
通過ApkTool工具,重新打包生成新APK(apktool b newApkDir)
重新簽名
經(jīng)過測試派桩,這種方案完全是可行的构诚。
優(yōu)點:
不需要重新構(gòu)建新渠道包,僅需要復(fù)制修改就可以了铆惑。并且因為是重新簽名范嘱,所以同時支持V1和V2簽名。
缺點:
ApkTool工具不穩(wěn)定员魏,曾經(jīng)遇到過升級Gradle Plugin版本后丑蛤,低版本ApkTool解壓APK失敗的情況。
生成新渠道包時撕阎,需要重新解包受裹、打包和簽名,而這幾步操作又是相對比較耗時的虏束。經(jīng)過測試:生成企鵝電競10個渠道包需要16分鐘左右棉饶,雖然比Gradle Plugin方案減少很多耗時。但是若需要同時生成上百個渠道包镇匀,則需要幾個小時照藻,顯然不適合渠道非常多的業(yè)務(wù)場景。
修改apk
apktool存在諸多缺點汗侵,針對v1我采用的還是添加文件和修改apk來添加渠道信息的幸缕。修改文件原理教簡單群发,下面我們重點介紹修改apk
apk文件結(jié)構(gòu)
修改apk得先知道其結(jié)構(gòu)。APK文件本質(zhì)上是一個ZIP壓縮包发乔,而ZIP格式是固定的也物,主要由三部分構(gòu)成,如下圖所示:
[圖片上傳失敗...(image-74cf23-1560483246319)]
- 第一部分是內(nèi)容塊列疗,所有的壓縮文件都在這部分。每個壓縮文件都有一個local file header浪蹂,主要記錄了文件名抵栈、壓縮算法、壓縮前后的文件大小坤次、修改時間古劲、CRC32值等。
- 第二部分稱為中央目錄缰猴,包含了多個central directory file header(和第一部分的local file header一一對應(yīng))产艾,每個中央目錄文件頭主要記錄了壓縮算法、注釋信息滑绒、對應(yīng)local file header的偏移量等闷堡,方便快速定位數(shù)據(jù)。
- 最后一部分是EOCD疑故,主要記錄了中央目錄大小杠览、偏移量和ZIP注釋信息等,其詳細結(jié)構(gòu)如下圖所示:
[圖片上傳失敗...(image-58a6c5-1560483246319)]
根據(jù)之前的V1簽名和校驗機制可知纵势,V1簽名只會檢驗第一部分的所有壓縮文件踱阿,而不理會后兩部分內(nèi)容。因此钦铁,只要把渠道信息寫入到后兩塊內(nèi)容就可以通過V1校驗软舌,而EOCD的注釋字段無疑是最好的選擇。
向apk文件結(jié)構(gòu)中寫入渠道信息
既然找到了突破口牛曹,那么基于V1簽名的多渠道打包方案就應(yīng)運而生:在APK文件的注釋字段佛点,添加渠道信息。
整個方案包括以下幾步:
- 復(fù)制APK
- 找到EOCD數(shù)據(jù)塊
- 修改注釋長度
- 添加渠道信息
- 添加渠道信息長度
- 添加魔數(shù)
添加渠道信息后的EOCD數(shù)據(jù)塊如下所示:
[圖片上傳失敗...(image-91f24-1560483246319)]
這里添加魔數(shù)的好處是方便從后向前讀取數(shù)據(jù)躏仇,定位渠道信息恋脚。
因此,讀取渠道信息包括以下幾步:
- 定位到魔數(shù)
- 向前讀兩個字節(jié)焰手,確定渠道信息的長度LEN
- 繼續(xù)向前讀LEN字節(jié)糟描,就是渠道信息了。
通過16進制編輯器书妻,可以查看到添加渠道信息后的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。
雖說整個方案很清晰米诉,但是在找到EOCD數(shù)據(jù)塊這步遇到一個問題菱蔬。如果APK本身沒有注釋,那最后22字節(jié)就是EOCD史侣。但是若APK本身已經(jīng)包含了注釋字段拴泌,那怎么確定EOCD的起始位置那?這里借鑒了系統(tǒng)V2簽名確定EOCD位置的方案惊橱。整個計算流程如下圖所示:
[圖片上傳失敗...(image-b692b2-1560483246319)]
整個方案介紹完了蚪腐,該方案的最大優(yōu)點就是:不需要解壓縮APK,不需要重新簽名税朴,只需要復(fù)制APK回季,在注釋字段添加渠道信息。每個渠道包僅需幾秒的耗時正林,非常適合渠道較多的APK泡一。
但是好景不長,Android7.0之后新增了V2簽名卓囚,該簽名會校驗整個APK的數(shù)據(jù)摘要瘾杭,導(dǎo)致上述渠道打包方案失效。所以如果想繼續(xù)使用上述方案哪亿,需要關(guān)閉Gradle Plugin中的V2簽名選項粥烁,禁用V2簽名。
V2簽名和多渠道打包方案
為什么需要V2簽名
從前面的V1簽名介紹蝇棉,可以知道V1存在兩個弊端:
- MANIFEST.MF中的數(shù)據(jù)摘要是基于原始未壓縮文件計算的讨阻。因此在校驗時,需要先解壓出原始文件篡殷,才能進行校驗钝吮。而解壓操作無疑是耗時的。
- V1簽名僅僅校驗APK第一部分中的文件板辽,缺少對APK的完整性校驗奇瘦。因此,在簽名后劲弦,我們還可以修改APK文件耳标,例如:通過zipalign進行字節(jié)對齊后,仍然可以正常安裝邑跪。
正是基于這兩點次坡,Google提出了V2簽名呼猪,解決了上述兩個問題:
- V2簽名是對APK本身進行數(shù)據(jù)摘要計算,不存在解壓APK的操作砸琅,減少了校驗時間宋距。
- V2簽名是針對整個APK進行校驗(不包含簽名塊本身),因此對APK的任何修改(包括添加注釋症脂、zipalign字節(jié)對齊)都無法通過V2簽名的校驗谚赎。
關(guān)于第一點的耗時問題,這里有一份實驗室數(shù)據(jù)(Nexus 6P诱篷、Android 7.1.1)可供參考沸版。
APK安裝耗時對比 | 取5次平均耗時(秒) |
---|---|
V1簽名APK | 11.64 |
V2簽名APK | 4.42 |
可見,V2簽名對APK的安裝速度還是提升不少的兴蒸。
V2簽名機制
不同于V1,V2簽名會生成一個簽名塊细办,插入到APK中橙凳。因此,V2簽名后的APK結(jié)構(gòu)如下圖所示:
[圖片上傳失敗...(image-ba6827-1560483246319)]
APK簽名塊位于中央目錄之前笑撞,文件數(shù)據(jù)之后岛啸。V2簽名同時修改了EOCD中的中央目錄的偏移量,使簽名后的APK還符合ZIP結(jié)構(gòu)茴肥。
APK簽名塊的具體結(jié)構(gòu)如下圖所示:
[圖片上傳失敗...(image-54d006-1560483246319)]
- 首先是8字節(jié)的簽名塊大小坚踩,此大小不包含該字段本身的8字節(jié);
- 其次就是ID-Value序列瓤狐,就是一個4字節(jié)的ID和對應(yīng)的數(shù)據(jù)瞬铸;
- 然后又是一個8字節(jié)的簽名塊大小,與開始的8字節(jié)是相等的础锐;最后是16字節(jié)的簽名塊魔數(shù)嗓节。
- 其中,ID為0x7109871a對應(yīng)的Value就是V2簽名塊數(shù)據(jù)皆警。
V2簽名塊的生成可參考ApkSignerV2拦宣,整體結(jié)構(gòu)和流程如下圖所示:
[圖片上傳失敗...(image-f003cc-1560483246319)]
- 首先,根據(jù)多個簽名算法信姓,計算出整個APK的數(shù)據(jù)摘要鸵隧,組成左上角的APK數(shù)據(jù)摘要集;
- 接著意推,把最左側(cè)一列的數(shù)據(jù)摘要豆瘫、數(shù)字證書和額外屬性組裝起來,形成類似于V1簽名的“MF”文件(第二列第一行)左痢;
- 其次靡羡,再用相同的私鑰系洛,不同的簽名算法,計算出“MF”文件的數(shù)字簽名略步,形成類似于V1簽名的“SF”文件(第二列第二行)描扯;
- 然后,把第二列的類似MF文件趟薄、類似SF文件和開發(fā)者公鑰一起組裝成通過單個keystore簽名后的v2簽名塊(第三列第一行)绽诚。
- 最后,把多個keystore簽名后的簽名塊組裝起來杭煎,就是完整的V2簽名塊了(Android中允許使用多個keystore對apk進行簽名)恩够。
上述流程比較繁瑣。簡而言之羡铲,單個keystore簽名塊主要由三部分組成蜂桶,分別是上圖中第二列的三個數(shù)據(jù)塊:類似MF文件、類似SF文件和開發(fā)者公鑰也切,其結(jié)構(gòu)如下圖所示:
[圖片上傳失敗...(image-e251d8-1560483246319)]
除此之外扑媚,Google也優(yōu)化了計算數(shù)據(jù)摘要的算法,使得可以并行計算雷恃,如下圖所示:
[圖片上傳失敗...(image-6be7d4-1560483246319)]
數(shù)據(jù)摘要的計算包括以下幾步:
- 首先疆股,將上述APK中文件內(nèi)容塊、中央目錄旬痹、EOCD按照1MB大小分割成一些小塊。
- 然后讨越,計算每個小塊的數(shù)據(jù)摘要两残,基礎(chǔ)數(shù)據(jù)是0xa5 + 塊字節(jié)長度 + 塊內(nèi)容。
- 最后把跨,計算整體的數(shù)據(jù)摘要磕昼,基礎(chǔ)數(shù)據(jù)是0x5a + 數(shù)據(jù)塊的數(shù)量 + 每個數(shù)據(jù)塊的摘要內(nèi)容。
這樣节猿,每個數(shù)據(jù)塊的數(shù)據(jù)摘要就可以并行計算票从,加快了V2簽名和校驗的速度。
V2校驗流程
Android Gradle Plugin2.2之上默認會同時開啟V1和V2簽名滨嘱,同時包含V1和V2簽名的CERT.SF文件會有一個特殊的主屬性峰鄙,如下圖所示:
[圖片上傳失敗...(image-ecbad4-1560483246319)]
該屬性會強制APK走V2校驗流程(7.0之上),以充分利用V2簽名的優(yōu)勢(速度快和更完善的校驗機制)太雨。
因此吟榴,同時包含V1和V2簽名的APK的校驗流程如下所示:
[圖片上傳失敗...(image-11afa7-1560483246319)]
簡而言之:優(yōu)先校驗V2,沒有或者不認識V2囊扳,則校驗V1吩翻。
這里引申出另外一個問題:APK簽名時兜看,只有V2簽名,沒有V1簽名行不行狭瞎?
經(jīng)過嘗試细移,這種情況是可以編譯通過的,并且在Android 7.0之上也可以正確安裝和運行熊锭。但是7.0之下弧轧,因為不認識V2,又沒有V1簽名碗殷,所以會報沒有簽名的錯誤精绎。
OK,明確了Android平臺對V1和V2簽名的校驗選擇之后锌妻,我們來看下V2簽名的具體校驗流程(PackageManagerService.java -> PackageParser.java-> ApkSignatureSchemeV2Verifier.java)代乃,如下圖所示:
[圖片上傳失敗...(image-632fdb-1560483246319)]
其中,最強簽名算法是根據(jù)該算法使用的數(shù)據(jù)摘要算法來對比產(chǎn)生的仿粹,比如:SHA512 > SHA256襟己。
校驗成功的定義是至少找到一個keystore對應(yīng)的簽名塊,并且所有簽名塊都按照上述流程校驗成功牍陌。
下面我們來看下V2簽名是怎么保證APK不被篡改的?
首先员咽,如果破壞者修改了APK文件的任何部分(簽名塊本身除外)毒涧,那么APK的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中記錄的數(shù)據(jù)摘要不一致,導(dǎo)致校驗失敗贝室。
其次契讲,如果破壞者同時修改了“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要,那么“MF”數(shù)據(jù)塊的數(shù)字簽名就和“SF”數(shù)據(jù)塊中記錄的數(shù)字簽名不一致滑频,導(dǎo)致校驗失敗捡偏。
然后,如果破壞者使用自己的私鑰去加密生成“SF”數(shù)據(jù)塊峡迷,那么使用開發(fā)者的公鑰去解密“SF”數(shù)據(jù)塊中的數(shù)字簽名就會失斠啊;
最后绘搞,更進一步彤避,若破壞者甚至替換了開發(fā)者公鑰瓤的,那么使用數(shù)字證書中的公鑰校驗簽名塊中的公鑰就會失敗遥椿,這也正是數(shù)字證書的作用谬哀。
綜上所述络拌,任何對APK的修改睦擂,在安裝時都會失敗,除非對APK重新簽名吊输。但是相同包名声畏,不同簽名的APK也是不能同時安裝的。
其實也很簡單娄帖,原來Android系統(tǒng)在校驗APK的數(shù)據(jù)摘要時也祠,首先會把EOCD的中央目錄偏移量替換成簽名塊的偏移量,然后再計算數(shù)據(jù)摘要块茁。而簽名塊的偏移量不就是v2簽名之前的中央目錄偏移量嘛3菘馈!数焊!永淌,因此,這樣計算出的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要完全一致了佩耳。具體代碼邏輯遂蛀,可參考ApkSignatureSchemeV2Verifier.java的416 ~ 420行
基于V2簽名的多渠道打包方案
在上節(jié)V2簽名的校驗流程中,有一個很重要的細節(jié):Android系統(tǒng)只會關(guān)注ID為0x7109871a的V2簽名塊干厚,并且忽略其他的ID-Value李滴,同時V2簽名只會保護APK本身,不包含簽名塊蛮瞄。
因此所坯,基于V2簽名的多渠道打包方案就應(yīng)運而生:在APK簽名塊中添加一個ID-Value,存儲渠道信息挂捅。
整個方案包括以下幾步:
- 找到APK的EOCD塊
- 找到APK簽名塊
- 獲取已有的ID-Value Pair
- 添加包含渠道信息的ID-Value
- 基于所有的ID-Value生成新的簽名塊
- 修改EOCD的中央目錄的偏移量(上面已介紹過:修改EOCD的中央目錄偏移量芹助,不會導(dǎo)致數(shù)據(jù)摘要校驗失敗)
- 用新的簽名塊替代舊的簽名塊闲先,生成帶有渠道信息的APK
實際上状土,除了渠道信息,我們可以在APK簽名塊中添加任何輔助信息伺糠。
通過16進制編輯器蒙谓,可以查看到添加渠道信息后的APK(小端模式),如下所示:
[圖片上傳失敗...(image-20e35e-1560483246319)]
V3簽名和多渠道打包方案
在android 9.0(N)引入的
為什么要有v3
主要是為了換簽名
生成簽名的時训桶,可以指定一個有效時間累驮,這個時間默認為 25 年,并且 Google Play 也有硬性規(guī)定舵揭,上架的 App 簽名有效期必須在 2033-10-22 日期之后慰照。所以只要不是手欠修改了這個有效期,在當(dāng)下這個時刻琉朽,是不會有問題毒租,畢竟到現(xiàn)在還沒有一款 App 存在 25 年。當(dāng)然還有可能是公司被收購 需要改簽名
有些問題不在眼前,卻是真實存在的墅垮。對于一款上架的 App惕医,最重要的就是用戶,而當(dāng)簽名失效之后算色,我們只能被迫換簽名抬伺,此時因為簽名校驗無法通過,就會導(dǎo)致舊用戶無法覆蓋安裝灾梦。這些歷史用戶唯一的選擇峡钓,就是卸載后重新安裝。
好在這不僅僅是你我的問題若河,天塌下來有個子高的頂著能岩,所以別擔(dān)心,Google 已經(jīng)著手在解決這個問題了萧福。
v3簽名塊結(jié)構(gòu)
v3版本簽名塊也分成同樣的三部分拉鹃,與v2不同的是在SignerData部分,v3新增了attr塊鲫忍,其中是由更小的level塊組成膏燕。每個level塊中可以存儲一個證書信息。前一個level塊證書驗證下一個level證書悟民,以此類推坝辫。最后一個level塊的證書,要符合SignerData中本身的證書射亏,即用來簽名整個APK的公鑰所屬于的證書近忙。兩個版本的簽名塊結(jié)構(gòu)如下:
[圖片上傳失敗...(image-95af1f-1560483246319)]
v3驗證簽名流程
因為簽名的驗證就是發(fā)生在一個apk包的安裝過程中,所以為了更清楚驗證簽名的時機鸦泳,有必要了解整個安裝的分類與大致流程。Android安裝應(yīng)用主要有如下四種方式:
- 系統(tǒng)應(yīng)用安裝:開機時完成永品,沒有安裝界面
- 網(wǎng)絡(luò)下載的應(yīng)用安裝:通過市場應(yīng)用完成做鹰,沒有安裝界面
- ADB工具安裝:沒有安裝界面
- 第三方應(yīng)用安裝:通過packageinstall.apk應(yīng)用安裝,有安裝界面
但是其實無論通過哪種方式安裝都要通過PackageManagerService來完成安裝的主要工作鼎姐,最終在PMS中會去驗證簽名信息钾麸,流程如下
[圖片上傳失敗...(image-fd0b3a-1560483246319)]
安裝過程中如果發(fā)現(xiàn)有v3簽名塊,則必須使用v3簽名的驗證機制炕桨,不能繞過饭尝。否則才使用v2簽名的驗證機制,以此類推献宫。
驗證完整性
數(shù)據(jù)完整性校驗v3與v2版本相同钥平,原理如下:
[圖片上傳失敗...(image-29bcb8-1560483246319)]
簽名塊包括對apk第一部分,第二部分姊途,第三部分的二進制內(nèi)容做加密保護涉瘾,摘要算法以及簽名算法知态。簽名塊本身不做加密,這里需要特殊注意的是由于第三部分包含了對第二部分的引用偏移立叛,因此如果簽名塊做了改變负敏,比如在簽名過程中增加一種簽名算法,或者增加簽名者等信息就會導(dǎo)致這個引用偏移發(fā)生改變秘蛇,因此在算摘要的時候需要剔除這個因素要以第三部分對簽名塊的偏移來做計算其做。
驗證證書
v2版本簽名驗證證書步驟:
- 利用PublicKey解密Signature,得到SignerData的hash明文
- 計算SignerData的hash值
- 兩個值進行比較赁还,如果相同則認為APK沒有被修改過妖泄,解析出SignerData中的證書。否則安裝失敗
- 如果是第一次安裝秽浇,直接將證書保存在應(yīng)用信息中
- 如果是更新安裝浮庐,即設(shè)備中原來存在這個應(yīng)用,驗證之前的證書是否與本次解析的證書相同柬焕。若相同审残,則安裝成功,否則失敗
[圖片上傳失敗...(image-6bfa12-1560483246319)]
v3版本簽名驗證證書步驟:(前三步同v2)
- 利用PublicKey解密Signature斑举,得到SignerData的hash明文
- 計算SignerData的hash值
- 兩個值進行比較搅轿,如果相同則認為APK沒有被修改過,解析出SignerData中的證書富玷。否則安裝失敗
- 逐個解析出level塊證書并驗證璧坟,并保存為這個應(yīng)用的歷史證書
- 如果是第一次安裝,直接將證書與歷史證書一并保存在應(yīng)用信息中
- 如果是更新安裝赎懦,驗證之前的證書與歷史證書雀鹃,是否與本次解析的證書或者歷史證書中存在相同的證書,其中任意一個證書符合即可安裝
[圖片上傳失敗...(image-af6403-1560483246319)]
新特性場景舉例
其實就是當(dāng)開發(fā)者需要更換證書時励两,即可直接用新證書新的私鑰進行簽名黎茎。不過為了讓老應(yīng)用相信新的證書,則需要用老證書來保證当悔。舉個例子傅瞻,有兩個level塊:level 1與level 2:
- level 1放置老證書的信息
- level 2中放置新證書的信息以及這段數(shù)據(jù)的簽名
- level 2中的簽名是由老私鑰進行簽名的,則需要用老證書的公鑰來驗證
- 校驗原來的證書與level 1 相同盲憎,則相信本次更新的level 2 的證書嗅骄,即簽名APK的證書
- 完成安裝并記錄新證書信息
v3多渠道方案
略 原理和v2同
參考:
http://www.reibang.com/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/