通過(guò)應(yīng)用簽名,開(kāi)發(fā)者可以標(biāo)識(shí)應(yīng)用創(chuàng)作者并更新其應(yīng)用兑宇,而無(wú)需創(chuàng)建復(fù)雜的接口和權(quán)限碍侦。
在 Google Play 上,應(yīng)用簽名可以將 Google 對(duì)開(kāi)發(fā)者的信任和開(kāi)發(fā)者對(duì)自己的應(yīng)用的信任聯(lián)系在一起隶糕。
在 Android 上瓷产,應(yīng)用簽名是將應(yīng)用放入其應(yīng)用沙盒的第一步。在安裝 APK 的時(shí)候需要校驗(yàn)包的完整性枚驻,同時(shí)濒旦,對(duì)于覆蓋安裝的場(chǎng)景還要檢驗(yàn)新舊是否匹配,這兩者都是通過(guò) Android 簽名機(jī)制來(lái)進(jìn)行保證的再登。
APK 簽名方案
Android 支持以下三種應(yīng)用簽名方案:
- v1 方案:基于 JAR 簽名尔邓。
- v2 方案:APK 簽名方案 v2(在 Android 7.0 中引入)晾剖。
- v3 方案:APK 簽名方案 v3(在 Android 9 中引入)。
為了最大限度地提高兼容性铃拇,請(qǐng)按照 v1钞瀑、v2、v3 的先后順序采用所有方案對(duì)應(yīng)用進(jìn)行簽名慷荔。與只通過(guò) v1 方案簽名的應(yīng)用相比雕什,還通過(guò) v2+ 方案簽名的應(yīng)用能夠更快速地安裝到 Android 7.0 及更高版本的設(shè)備上。更低版本的 Android 平臺(tái)會(huì)忽略 v2+ 簽名显晶,這就需要應(yīng)用包含 v1 簽名贷岸。
JAR 簽名(v1 方案)
-
源碼解析
1.1 計(jì)算并寫(xiě)入 META-INF/MANIFEST.MF 清單文件
總結(jié):
**針對(duì)每個(gè)待簽名 zip 包中的文件(除了 META-INF 下簽名相關(guān)的如 .MF SIG- *.SF *.DSA .RSA .EC文件),計(jì)算其數(shù)據(jù)指紋并寫(xiě)入 META-INF/MANIFEST.MF 清單文件中
1.2 計(jì)算并寫(xiě)入 META-INF/*.SF 簽名文件
1.3 計(jì)算并寫(xiě)入 META-INF/*.RSA 簽名結(jié)果文件
1.4 寫(xiě)入除 .MF .RSA .SF 文件之外的所有文件
- 有何痛點(diǎn)磷雇?v1 簽名不保護(hù) APK 的某些部分偿警,例如 ZIP 元數(shù)據(jù);APK 驗(yàn)證程序必須解壓所有已壓縮的條目唯笙,而這需要花費(fèi)更多時(shí)間和內(nèi)存螟蒸;
APK 簽名方案 v2 和 v3(v2+ 方案)
搭載 Android 7.0 及更高版本的設(shè)備支持 APK 簽名方案 v2(v2 方案)及更高版本的方案(在 Android 9 中,v2 方案已更新為 v3 方案崩掘,以便在簽名分塊中包含其他信息七嫌,但在其他方面保持相同的工作方式)。該方案會(huì)對(duì) APK 的內(nèi)容進(jìn)行哈希處理和簽名苞慢,然后將生成的“APK 簽名分塊”插入到 APK 中诵原。
在驗(yàn)證期間,v2+ 方案會(huì)將 APK 文件視為 blob挽放,并對(duì)整個(gè)文件進(jìn)行簽名檢查绍赛。對(duì) APK 進(jìn)行的任何修改(包括對(duì) ZIP 元數(shù)據(jù)進(jìn)行的修改)都會(huì)使 APK 簽名作廢。這種形式的 APK 驗(yàn)證不僅速度要快得多辑畦,而且能夠發(fā)現(xiàn)更多種未經(jīng)授權(quán)的修改吗蚌。
APK 簽名驗(yàn)證過(guò)程:驗(yàn)證程序會(huì)對(duì)照存儲(chǔ)在“APK 簽名分塊”中的 v2+ 簽名對(duì) APK 的全文件哈希進(jìn)行驗(yàn)證。 該哈希涵蓋除“APK 簽名分塊”(其中包含 v2+ 簽名)之外的所有內(nèi)容纯出。在“APK 簽名分塊”以外對(duì) APK 進(jìn)行的任何修改都會(huì)使 APK 的 v2+ 簽名作廢蚯妇。v2+ 簽名被刪除的 APK 也會(huì)被拒絕,因?yàn)?v1 簽名指明相應(yīng) APK 帶有 v2 簽名潦刃,所以 Android 7.0 及更高版本會(huì)拒絕使用 v1 簽名驗(yàn)證 APK侮措。
APK 簽名方案 V2
APK Signature Scheme v2 的兩個(gè)主要目標(biāo)是:
1. 檢測(cè)對(duì) APK 的任何未經(jīng)授權(quán)的修改懈叹。這是通過(guò)使簽名覆蓋被簽名的 APK 的每個(gè)字節(jié)來(lái)實(shí)現(xiàn)的乖杠。
2. 啟用更快的簽名和完整性驗(yàn)證。這是通過(guò)在驗(yàn)證簽名之前只需要最少量的 APK 解析來(lái)實(shí)現(xiàn)的澄成,從而完全繞過(guò) ZIP 條目解壓縮胧洒,并通過(guò)使用哈希樹(shù)使完整性驗(yàn)證可并行化畏吓。
APK 簽名方案 v2 是一種全文件簽名方案,該方案能夠發(fā)現(xiàn)對(duì) APK 的受保護(hù)部分進(jìn)行的所有更改卫漫,從而有助于加快驗(yàn)證速度并增強(qiáng)完整性保證菲饼。
首先來(lái)了解一個(gè)名詞:APK 簽名分塊,這個(gè)知識(shí)點(diǎn)將在下面用到列赎。
APK 簽名分塊格式:
“APK 簽名分塊”的格式如下(所有數(shù)字字段均采用小端字節(jié)序):
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 時(shí)包吝,首先要通過(guò)以下方法找到“ZIP 中央目錄”的起始位置:在文件末尾找到“ZIP 中央目錄結(jié)尾”記錄饼煞,然后從該記錄中讀取“中央目錄”的起始偏移量。通過(guò) magic 值诗越,可以快速確定“中央目錄”前方可能是“APK 簽名分塊”砖瞧。然后,通過(guò) size of block 值嚷狞,可以高效地找到該分塊在文件中的起始位置块促。
使用 APK 簽名方案 v2 進(jìn)行簽名時(shí),會(huì)在 APK 文件中插入一個(gè) APK 簽名分塊床未,該分塊位于“ZIP 中央目錄”部分之前并緊鄰該部分竭翠。在“APK 簽名分塊”內(nèi),v2 簽名和簽名者身份信息會(huì)存儲(chǔ)在 APK 簽名方案 v2 分塊中即硼。
簽名前和簽名后的 APK:該分塊包含多個(gè)“ID-值”對(duì)逃片,所采用的封裝方式有助于更輕松地在 APK 中找到該分塊。APK 的 v2 簽名會(huì)存儲(chǔ)為一個(gè)“ID-值”對(duì)只酥,其中 ID 為 0x7109871a褥实。
APK 簽名方案 v2 是在 Android 7.0 (Nougat) 中引入的墓捻。為了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的設(shè)備上安裝伪煤,應(yīng)先使用 JAR 簽名功能對(duì) APK 進(jìn)行簽名,然后再使用 v2 方案對(duì)其進(jìn)行簽名禁谦。
為了保護(hù) APK 內(nèi)容绝编,APK 包含以下 4 個(gè)部分:
- ZIP 條目的內(nèi)容(從偏移量 0 處開(kāi)始一直到“APK 簽名分塊”的起始位置)
- APK 簽名分塊
- ZIP 中央目錄
- ZIP 中央目錄結(jié)尾
APK 簽名方案 v2 負(fù)責(zé)保護(hù)第 1僻澎、3、4 部分的完整性十饥,以及第 2 部分包含的“APK 簽名方案 v2 分塊”中的 signed data
分塊的完整性窟勃。
第 1、3 和 4 部分的完整性通過(guò)其內(nèi)容的一個(gè)或多個(gè)摘要來(lái)保護(hù)逗堵,這些摘要存儲(chǔ)在 signed data
分塊中秉氧,而這些分塊則通過(guò)一個(gè)或多個(gè)簽名來(lái)保護(hù)。
第 1蜒秤、3 和 4 部分的摘要采用以下計(jì)算方式汁咏,類似于兩級(jí) Merkle 樹(shù)亚斋。每個(gè)部分都會(huì)被拆分成多個(gè)大小為 1MB(220 個(gè)字節(jié))的連續(xù)塊。每個(gè)部分的最后一個(gè)塊可能會(huì)短一些攘滩。每個(gè)塊的摘要均通過(guò)字節(jié) 0xa5
的串聯(lián)帅刊、塊的長(zhǎng)度(采用小端字節(jié)序的 uint32 值,以字節(jié)數(shù)計(jì))和塊的內(nèi)容進(jìn)行計(jì)算漂问。頂級(jí)摘要通過(guò)字節(jié) 0x5a
的串聯(lián)赖瞒、塊數(shù)(采用小端字節(jié)序的 uint32 值)以及塊的摘要的連接(按照塊在 APK 中顯示的順序)進(jìn)行計(jì)算。摘要以分塊方式計(jì)算蚤假,以便通過(guò)并行處理來(lái)加快計(jì)算速度冒黑。
簡(jiǎn)單來(lái)講,數(shù)據(jù)摘要就是對(duì)一段數(shù)據(jù)進(jìn)行散列算法計(jì)算得出的一段密文數(shù)據(jù)勤哗,過(guò)程不可逆抡爹,也就是不可解密。
也就是說(shuō)芒划,V2 摘要簽名分兩級(jí)冬竟,第一級(jí)是對(duì) APK 文件的 1、3 民逼、4 部分進(jìn)行摘要泵殴,第二級(jí)是對(duì)第一級(jí)的摘要集合進(jìn)行摘要,然后利用秘鑰進(jìn)行簽名拼苍。安裝的時(shí)候笑诅,塊摘要可以并行處理,這樣可以提高校驗(yàn)速度疮鲫。
總的來(lái)說(shuō)吆你,APK 就是先摘要,再簽名俊犯。
先看下摘要的定義:Message Digest:摘要是對(duì)消息數(shù)據(jù)執(zhí)行一個(gè)單向Hash妇多,從而生成一0xa5作為數(shù)據(jù)的個(gè)固定長(zhǎng)度的Hash值,這個(gè)值就是消息摘要燕侠,至于常聽(tīng)到的MD5者祖、SHA1都是摘要算法的一種。理論上說(shuō)绢彤,摘要一定會(huì)有碰撞七问,但只要保證有限長(zhǎng)度內(nèi)碰撞率很低就可以,這樣就能利用摘要來(lái)保證消息的完整性茫舶,只要消息被篡改械巡,摘要一定會(huì)發(fā)生改變。但是,如果消息跟摘要同時(shí)被修改坟比,那就無(wú)從得知了。
而數(shù)字簽名是什么呢(公鑰數(shù)字簽名)嚷往,利用非對(duì)稱加密技術(shù)葛账,通過(guò)私鑰對(duì)摘要進(jìn)行加密,產(chǎn)生一個(gè)字符串皮仁,這個(gè)字符串+公鑰證書(shū)就可以看做消息的數(shù)字簽名籍琳,如RSA就是常用的非對(duì)稱加密算法。在沒(méi)有私鑰的前提下贷祈,非對(duì)稱加密算法能確保別人無(wú)法偽造簽名趋急,因此數(shù)字簽名也是對(duì)發(fā)送者信息真實(shí)性的一個(gè)有效證明。不過(guò)由于Android的keystore證書(shū)是自簽名的势誊,沒(méi)有第三方權(quán)威機(jī)構(gòu)認(rèn)證呜达,用戶可以自行生成keystore,Android簽名方案無(wú)法保證APK不被二次簽名粟耻。
源碼解析
- 有何痛點(diǎn)查近?
APK 簽名方案 v1、v2 和 v1&v2 APK 包結(jié)構(gòu)對(duì)比
apk包對(duì)比:可以看到挤忙,如果只有 V2 簽名霜威,那么APK包內(nèi)容幾乎是沒(méi)有改動(dòng)的,META_INF 中不會(huì)有新增文件册烈。
而 V1 簽名是通過(guò) META-INF 中的三個(gè)文件保證簽名及信息的完整性戈泼,如下圖:
META-INF 那三個(gè)文件:APK 簽名如何保證 APK 信息完整性
V1 簽名如何保證 APK 信息完整性?
V1 簽名主要包含三部分內(nèi)容赏僧,如果狹義上說(shuō)簽名跟公鑰的話大猛,僅僅在 .rsa 文件中,V1 簽名的三個(gè)文件其實(shí)是一套機(jī)制淀零。
MANIFEST.MF:摘要文件胎署,存儲(chǔ)文件名與文件 SHA1 摘要(Base64 格式)鍵值對(duì),格式如下窑滞,其主要作用是保證每個(gè)文件的完整性琼牧。
從文件開(kāi)頭到第一個(gè)空行之間(圖中的 1-3 行)是 manifest 文件主屬性,從第 5 行開(kāi)始就是其所包含的條目(entry)哀卫。
條目是由 條目名稱 和 條目屬性 組成巨坊,條目名稱就是 Name:
之后的值如下圖中的 AndroidManifest.xml
,條目屬性是一個(gè) name-value
格式的 map
如圖中的{"SHA1-Digest":"BeF7Z..."}
此改。
如果對(duì) APK 中的資源文件進(jìn)行了替換趾撵,那么該資源的摘要必定發(fā)生改變,如果沒(méi)有修改 MANIFEST.MF 中的信息,那么在安裝時(shí)候 V1 校驗(yàn)就會(huì)失敗占调,無(wú)法安裝暂题,不過(guò)如果篡改文件的同時(shí),也修改其 MANIFEST.MF 中的摘要值究珊,那么 MANIFEST.MF 校驗(yàn)就可以繞過(guò)薪者。
CERT.SF:二次摘要文件,存儲(chǔ)文件名與 MANIFEST.MF 摘要條目的 SHA1 摘要(Base64 格式)鍵值對(duì)剿涮,格式如下:
*.SF 文件:a.
Signature-Version
是簽名版本言津。
b.
SHA-256-Digest-Manifest-Main-Attributes
是 MANIFEST.MF 文件屬性 的數(shù)據(jù)指紋 Base64 值。
c.SHA-256-Digest-Manifest
是整個(gè) MANIFEST.MF 的數(shù)據(jù)指紋 Base64 值取试。
d.Created-By
指明文件生成工具悬槽,例如:Created-By: 1.0 (Android)。
e. 第 8 行開(kāi)始的各個(gè)條目瞬浓,就是對(duì) MANIFEST.MF 各個(gè)條目的數(shù)據(jù)指紋 Base64 值初婆。
如果把 MANIFEST.MF 當(dāng)做是對(duì) APK 中各個(gè)文件的 hash 記錄,那么 .SF 就是 MANIFEST.MF 及其各個(gè)條目的 hash 記錄猿棉⊙萄罚總的來(lái)說(shuō),CERT.SF 感覺(jué)有點(diǎn)像冗余铺根,更像對(duì)文件完整性的二次保證宪躯,同繞過(guò) MANIFEST.MF 一樣,.SF 校驗(yàn)也很容易被繞過(guò)位迂。
可以看到 CERT.RSA 與 CERT.SF 是相互對(duì)應(yīng)的访雪,兩者名字前綴必須一致!
CERT.RSA 證書(shū)(公鑰)及簽名文件掂林,存儲(chǔ) keystore 的公鑰臣缀、發(fā)行信息、以及對(duì) CERT.SF 文件摘要的簽名信息(利用 keystore 的私鑰進(jìn)行加密過(guò))泻帮。
.RSA 是 PKCS#7[9] 標(biāo)準(zhǔn)格式的文件精置,我們只關(guān)心它所保存的以下兩種數(shù)據(jù):
a. 用私鑰
對(duì) .SF 文件指紋
進(jìn)行非對(duì)稱加密
后得到的 加密數(shù)據(jù)
b. 攜帶公鑰
以及各種身份信息的** 數(shù)字證書(shū)**
加密數(shù)據(jù):
通過(guò) openssl asn1parse 格式化查看加密后的數(shù)據(jù)及其偏移量,從下圖中可以看出加密后數(shù)據(jù)處在 PKCS#7 的最后
執(zhí)行 openssl asn1parse -i -inform der -in CERT.RSA 得到如下 ASN1 格式數(shù)據(jù):
** 1250** 是字節(jié)偏移量(十進(jìn)制)
** d=5** 表示所處 PKCS#7 數(shù)據(jù)結(jié)構(gòu)的層級(jí)是第 5 層
** hl=4** 表示頭所占字節(jié)數(shù)為 4 個(gè)字節(jié)
** l=256** 表示數(shù)據(jù)字節(jié)數(shù)為 256(對(duì)應(yīng) SHA-256 指紋算法)最后一段是加密后的 16 進(jìn)制數(shù)據(jù)锣杂。
執(zhí)行 dd if=STRANGEW.RSA of=signed-sha256.bin bs=1 skip=$[ 1115 + 4 ] count=256
把加密數(shù)據(jù)導(dǎo)出來(lái)到 signed-sha256.bin 文件脂倦。
之后查看 16 進(jìn)制的這個(gè)signed-sha256.bin
文件數(shù)據(jù):
數(shù)字證書(shū):
執(zhí)行 openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_certs -text
查看 .RSA 中保存的證書(shū)信息。截圖中可以看到證書(shū)包含了簽名算法元莫、有效期赖阻、證書(shū)主體、證書(shū)簽發(fā)者踱蠢、公鑰等信息火欧。
看下 CERT.RSA 文件內(nèi)容:
問(wèn)題:如何把加密數(shù)據(jù)和證書(shū)放到 .RSA 文件中的呢?
CERT.RSA 文件里面存儲(chǔ)了證書(shū)公鑰、過(guò)期日期苇侵、發(fā)行人赶盔、加密算法等信息,根據(jù)公鑰及加密算法榆浓,Android 系統(tǒng)就能計(jì)算出 CERT.SF 的摘要信息于未,其嚴(yán)格的格式如下:
從 CERT.RSA 中,我們能獲的證書(shū)的指紋信息哀军,在微信分享、第三方 SDK 申請(qǐng)的時(shí)候經(jīng)常用到打却,其實(shí)就是公鑰 + 開(kāi)發(fā)者信息的一個(gè)簽名:
除了 CERT.RSA 文件杉适,其余兩個(gè)簽名文件其實(shí)跟 keystore 沒(méi)什么關(guān)系,主要是文件自身的摘要及二次摘要柳击,用不同的 keystore 進(jìn)行簽名猿推,生成的 MANIFEST.MF 與 CERT.SF 都是一樣的,不同的只有 CERT.RSA 簽名文件捌肴。也就是說(shuō)前兩者主要保證各個(gè)文件的完整性蹬叭,CERT.RSA 從整體上保證 APK 的來(lái)源及完整性,不過(guò) META_INF 中的文件不在校驗(yàn)范圍中状知,這也是 V1 的一個(gè)缺點(diǎn)秽五。
最后總結(jié)一下 MANIFEST.MF、CERT.SF饥悴、CERT.RSA 如何各司其職構(gòu)成了 APK 的簽名:
a. 解析出 CERT.RSA 文件中的證書(shū)坦喘、公鑰,解密 CERT.RSA 中的加密數(shù)據(jù)
b. 解密結(jié)果和 CERT.SF 的指紋進(jìn)行對(duì)比西设,保證 CERT.SF 沒(méi)有被篡改
c. 而 CERT.SF 中的內(nèi)容再和 MANIFEST.MF 指紋對(duì)比瓣铣,保證 MANIFEST.MF 文件沒(méi)有被篡改
d. MANIFEST.MF 中的內(nèi)容和 APK 所有文件指紋逐一對(duì)比,保證 APK 沒(méi)有被篡改
V2 簽名如何保證 APK 信息完整性贷揽?
前面說(shuō)過(guò) V1 簽名中文件的完整性很容易被繞過(guò)棠笑,可以理解單個(gè)文件完整性校驗(yàn)的意義并不是很大,安裝的時(shí)候反而耗時(shí)禽绪,不如采用更加簡(jiǎn)單的便捷的校驗(yàn)方式蓖救。V2 簽名就不針對(duì)單個(gè)文件校驗(yàn)了,而是針對(duì)APK進(jìn)行校驗(yàn)印屁,將 APK 分成 1M 的塊藻糖,對(duì)每個(gè)塊計(jì)算值摘要,之后針對(duì)所有摘要進(jìn)行摘要库车,再利用摘要進(jìn)行簽名巨柒。
也就是說(shuō),V2 摘要簽名分兩級(jí),第一級(jí)是對(duì)APK文件的 1洋满、3 晶乔、4 部分進(jìn)行摘要,第二級(jí)是對(duì)第一級(jí)的摘要集合進(jìn)行摘要牺勾,然后利用秘鑰進(jìn)行簽名正罢。安裝的時(shí)候,塊摘要可以并行處理驻民,這樣可以提高校驗(yàn)速度翻具。
其他
通過(guò)上面的描述,可以看出因?yàn)锳PK包的區(qū)塊1回还、3裆泳、4都是受保護(hù)的,任何修改在簽名后對(duì)它們的修改柠硕,都會(huì)在安裝過(guò)程中被簽名校驗(yàn)檢測(cè)失敗工禾,而區(qū)塊2(APK Signing Block)是不受簽名校驗(yàn)規(guī)則保護(hù)的,那是否可以在這個(gè)不受簽名保護(hù)的區(qū)塊2(APK Signing Block)上做文章呢蝗柔?我們先來(lái)看看對(duì)區(qū)塊2格式的描述:
區(qū)塊2中APK Signing Block是由這幾部分組成:2個(gè)用來(lái)標(biāo)示這個(gè)區(qū)塊長(zhǎng)度的8字節(jié) + 這個(gè)區(qū)塊的魔數(shù)(APK Sig Block 42
)+ 這個(gè)區(qū)塊所承載的數(shù)據(jù)(ID-value)闻葵。
我們重點(diǎn)來(lái)看一下這個(gè)ID-value,它由一個(gè)8字節(jié)的長(zhǎng)度標(biāo)示+4字節(jié)的ID+它的負(fù)載組成癣丧。V2的簽名信息是以ID(0x7109871a
)的ID-value來(lái)保存在這個(gè)區(qū)塊中槽畔,不知大家有沒(méi)有注意這是一組ID-value,也就是說(shuō)它是可以有若干個(gè)這樣的ID-value來(lái)組成胁编。
看上圖:APK 簽名驗(yàn)證過(guò)程竟痰。
1. 尋找APK Signing Block,如果能夠找到掏呼,則進(jìn)行驗(yàn)證坏快,驗(yàn)證成功則繼續(xù)進(jìn)行安裝,如果失敗了則終止安裝
2. 如果未找到APK Signing Block憎夷,則執(zhí)行原來(lái)的簽名驗(yàn)證機(jī)制莽鸿,也是驗(yàn)證成功則繼續(xù)進(jìn)行安裝,如果失敗了則終止安裝
最后拾给,通過(guò)驗(yàn)證在已經(jīng)被 V2 應(yīng)用簽名方案簽名后的 APK 中添加自定義的 ID-value祥得,是不需要再次經(jīng)過(guò)簽名就能安裝的。
詳見(jiàn)請(qǐng)見(jiàn):新一代開(kāi)源Android渠道包生成工具Walle