Android 簽名

前序

1铸董、概念梳理

1.1 消息摘要(Message Digest)

消息摘要又稱數(shù)字摘要(Digital Digest)或數(shù)字指紋(Finger Print)庆杜。對(duì)不同長(zhǎng)度的數(shù)據(jù)通過(guò)算法得到一個(gè)固定的長(zhǎng)度輸入,常用的算法有MD5、SHA1、SHA256阐滩。

1.2 數(shù)字簽名

數(shù)字簽名的作用就是保證信息傳輸?shù)耐暾浴l(fā)送者的身份認(rèn)證县忌、防止交易中的抵賴發(fā)生掂榔。數(shù)字簽名技術(shù)是將摘要信息用發(fā)送者的私鑰加密继效,與原文一起傳送給接收者。接收者只有用發(fā)送者的公鑰才能解密被加密的摘要信息然后用HASH函數(shù)對(duì)收到的原文產(chǎn)生一個(gè)摘要信息装获,與解密的摘要信息對(duì)比瑞信。如果相同,則說(shuō)明收到的信息是完整的穴豫,在傳輸過(guò)程中沒(méi)有被修改凡简,否則說(shuō)明信息被修改過(guò),因此數(shù)字簽名能夠驗(yàn)證信息的完整性精肃。

1.3 數(shù)字證書(shū)

數(shù)字證書(shū)是一個(gè)經(jīng)證書(shū)授權(quán)(Certificate Authentication)中心數(shù)字簽名的包含公鑰擁有者信息以及公鑰的文件秤涩。數(shù)字證書(shū)的格式普遍采用的是 X.509 V3 國(guó)際標(biāo)準(zhǔn),一個(gè)標(biāo)準(zhǔn)的 X.509 數(shù)字證書(shū)通常包含以下內(nèi)容:

  1. 證書(shū)的發(fā)布機(jī)構(gòu)(Issuer):該證書(shū)是由哪個(gè)機(jī)構(gòu)(CA 中心)頒發(fā)的司抱。
  2. 證書(shū)的有效期(Validity):證書(shū)的有效期筐眷,或者說(shuō)使用期限。過(guò)了該日期习柠,證書(shū)就失效了匀谣。
  3. 證書(shū)所有人的公鑰(Public-Key):該證書(shū)所有人想要公布出去的公鑰。
  4. 證書(shū)所有人的名稱(Subject):這個(gè)證書(shū)是發(fā)給誰(shuí)的资溃,或者說(shuō)證書(shū)的所有者武翎,一般是某個(gè)人或者某個(gè)公司名稱、機(jī)構(gòu)的名稱溶锭、公司網(wǎng)站的網(wǎng)址等宝恶。
  5. 證書(shū)所使用的簽名算法(Signature algorithm):這個(gè)數(shù)字證書(shū)的數(shù)字簽名所使用的加密算法,這樣就可以使用證書(shū)發(fā)布機(jī)構(gòu)的證書(shū)里面的公鑰趴捅,根據(jù)這個(gè)算法對(duì)指紋進(jìn)行解密垫毙。
  6. 證書(shū)發(fā)行者對(duì)證書(shū)的數(shù)字簽名(Thumbprint):數(shù)字證書(shū)的hash 值(指紋),用于保證數(shù)字證書(shū)的完整性驻售,確保證書(shū)沒(méi)有被修改過(guò)露久。

數(shù)字證書(shū)的原理就是在證書(shū)發(fā)布時(shí)更米,CA 機(jī)構(gòu)會(huì)根據(jù)簽名算法(Signature algorithm)對(duì)整個(gè)證書(shū)計(jì)算其 hash 值(指紋)并和證書(shū)放在一起欺栗,使用者打開(kāi)證書(shū)時(shí),自己也根據(jù)簽名算法計(jì)算一下證書(shū)的 hash 值(指紋)征峦,如果和證書(shū)中記錄的指紋對(duì)的上迟几,就說(shuō)明證書(shū)沒(méi)有被修改過(guò)。

數(shù)字證書(shū)本身也用到了數(shù)字簽名技術(shù)栏笆,只不過(guò)簽名的內(nèi)容是整個(gè)證書(shū)(里面包含了證書(shū)所有者的公鑰以及其他一些內(nèi)容)类腮。與普通數(shù)字簽名不同的是,數(shù)字證書(shū)的簽名者不是隨隨便便一個(gè)普通機(jī)構(gòu)蛉加,而是 CA 機(jī)構(gòu)蚜枢。

1.4 JKS格式

JKS 是 java 用來(lái)存儲(chǔ)密鑰的容器缸逃。可以同時(shí)容納n個(gè)公鑰或私鑰厂抽,后綴一般是 .jks或者.keystore或 .truststore等需频。

  • 在Java 8之前,這些文件的默認(rèn)格式為JKS(android .keystore 也是jsk格式的證書(shū))筷凤。
  • 從Java 9開(kāi)始昭殉,默認(rèn)的密鑰庫(kù)格式為PKCS12。
  • Android簽名keystore文件也是jks格式藐守,且1.8之后要求轉(zhuǎn)換到p12格式挪丢。
  • JKS是二進(jìn)制格式,同時(shí)包含證書(shū)和私鑰卢厂,一般有密碼保護(hù)乾蓬,只能存儲(chǔ)非對(duì)稱密鑰對(duì)(私鑰 + x509公鑰證書(shū))。
  • 當(dāng)應(yīng)用程序需要通過(guò)SSL / TLS進(jìn)行通信時(shí)足淆,在大多數(shù)情況下將使用java keystore和java truststore巢块。
  • 密鑰庫(kù)和私鑰用不同的密碼進(jìn)行保護(hù)
  • JKS和PKCS12之間的最大區(qū)別是JKS是Java專用的格式,而PKCS12是存儲(chǔ)加密的私鑰和證書(shū)的標(biāo)準(zhǔn)化且與語(yǔ)言無(wú)關(guān)的方式巧号。
1.5 PKCS#12 / PFX 格式
  • PKCS#12 是公鑰加密標(biāo)準(zhǔn)族奢,通用格式(rsa公司標(biāo)準(zhǔn))。規(guī)定了可包含所有私鑰丹鸿、公鑰和證書(shū)越走。文件格式是加密過(guò)的。
  • PKCS#12 或 PFX 格式是其以二進(jìn)制格式存儲(chǔ)靠欢,也稱為 PFX 文件廊敌,在windows中可以直接導(dǎo)入到密鑰區(qū)。也可用于導(dǎo)入和導(dǎo)出證書(shū)和私鑰门怪。
  • PKCS#12 由 PFX 進(jìn)化而來(lái)的骡澈,用于交換公共的和私有的對(duì)象的標(biāo)準(zhǔn)格式。
  • 文件通常具有擴(kuò)展名掷空,例如.pkcs12 .pfx .p12肋殴。
  • 密鑰庫(kù)和私鑰用相同密碼進(jìn)行保護(hù)

一、簽名方式

1坦弟、v1 簽名

1.1 簽名內(nèi)容

V1 方案基于簽名的 JAR 护锤,解壓v1簽名的apk包,解壓后會(huì)有 META-INF 文件夾酿傍。其中有 MANIFEST.MF烙懦、CERT.SF、CERT.RSA 三個(gè)文件赤炒,它們是簽名過(guò)程中生成的文件氯析。

apk解壓內(nèi)容

三個(gè)文件作用如下:

  • MANIFEST.MF

該文件中存放的是對(duì)apk 中每一個(gè)文件使用SHA1或者SHA256消息摘要算法獲得摘要亏较,并使用base64進(jìn)行編碼后的內(nèi)容。Name 對(duì)應(yīng)的是APK中文件路徑掩缓,SHA-256-Digest 對(duì)應(yīng)的是摘要內(nèi)容宴杀。

MANIFEST.MF
  • CERT.SF
  • SHA1-Digest-Manifest-Main-Attributes:對(duì) MANIFEST.MF 頭部的塊 做 SHA1(或者SHA256)后再用 Base64 編碼。
  • SHA1-Digest-Manifest:對(duì)整個(gè) MANIFEST.MF 文件做 SHA1(或者 SHA256)后再用 Base64 編碼拾因。
  • SHA1-Digest:對(duì) MANIFEST.MF 的各個(gè)條目做 SHA1(或者 SHA256)后再用 Base64 編碼旺罢。
CERT.SF
  • CERT.RSA

把生成的 CERT.SF 文件用私鑰計(jì)算出簽名, 然后將簽名以及包含公鑰信息的數(shù)字證書(shū)一同寫(xiě)入 CERT.RSA 中保存。Android APK 中的 CERT.RSA 證書(shū)是自簽名的绢记,并不需要第三方權(quán)威機(jī)構(gòu)發(fā)布或者認(rèn)證的證書(shū)扁达,用戶可以在本地機(jī)器自行生成這個(gè)自簽名證書(shū)。Android 目前不對(duì)應(yīng)用證書(shū)進(jìn)行 CA 認(rèn)證蠢熄。

CERT.RSA

查看 .RSA 文件內(nèi)容

pkcs7 -inform DER -in xx/META-INF/CERT.RSA -text -noout -print_certs
1.2 簽名流程
  1. 計(jì)算每一個(gè)原始文件的 SHA-1 摘要跪解,寫(xiě)入到MANIFEST.MF中;

  2. 計(jì)算整個(gè)MANIFEST.MF文件的 SHA-1 摘要签孔,寫(xiě)入到CERT.SF中叉讥;

  3. 計(jì)算MANIFEST.MF中,每一塊的SHA-1 摘要饥追,寫(xiě)入到CERT.SF中图仓;

  4. 計(jì)算整個(gè)CERT.SF文件的摘要,使用開(kāi)發(fā)者私鑰計(jì)算出摘要的簽名但绕;

  5. 將簽名和開(kāi)發(fā)者證書(shū)(X.509)寫(xiě)入CERT.RSA


    簽名流程
1.3 簽名校驗(yàn)

簽名驗(yàn)證是發(fā)生在 APK 的安裝過(guò)程中救崔,一共分為三步:

  1. 檢查 APK 中包含的所有文件,對(duì)應(yīng)的摘要值與 MANIFEST.MF 文件中記錄的值一致捏顺。
  2. 使用證書(shū)文件(RSA 文件)檢驗(yàn)簽名文件(SF 文件)沒(méi)有被修改過(guò)六孵。
  3. 使用簽名文件(SF 文件)檢驗(yàn) MF 文件沒(méi)有被修改過(guò)。
1.4 總結(jié)
  1. V1 需要對(duì) apk 中的每個(gè)文件都計(jì)算摘要并驗(yàn)證幅骄,如果文件很多劫窒,校驗(yàn)時(shí)間會(huì)很長(zhǎng)。
  2. V1 簽名只會(huì)校驗(yàn) Zip 文件中的部分文件拆座,META-INFO 文件夾就不會(huì)參與校驗(yàn)主巍。

2、v2 簽名

APK Signature Scheme v2 是一個(gè)完整的文件簽名方案懂拾,它通過(guò)檢測(cè) APK 受保護(hù)部分的任何更改來(lái)提高驗(yàn)證速度并加強(qiáng)完整性保證煤禽。Android 7 引入铐达、Android 11 強(qiáng)制岖赋。

使用 APK 簽名方案 v2 進(jìn)行簽名,會(huì)將 APK 簽名塊插入到 APK 文件中瓮孙,緊挨在 ZIP 中央目錄部分之前唐断。在 APK 簽名塊內(nèi)选脊,v2 簽名和簽名者身份信息存儲(chǔ)在APK 簽名方案 v2 塊中。

image.png

APK 簽名方案 v2 是在 Android 7.0 (Nougat) 中引入的脸甘。要使 APK 可安裝在 Android 6.0 (Marshmallow) 和更舊的設(shè)備上恳啥,應(yīng)先使用 v1 方案對(duì) APK 進(jìn)行簽名,然后再使用 v2 方案進(jìn)行簽名丹诀。

2.1 APK 簽名塊

為了保持與 v1 APK 格式的向后兼容性钝的,v2 和更新的 APK 簽名存儲(chǔ)在 APK 簽名塊中,這是一個(gè)新容器铆遭,用于支持 APK 簽名方案 v2硝桩。在 APK 文件中,APK 簽名塊位于 ZIP 中央目錄之前枚荣,該目錄位于文件末尾碗脊。

該塊包含以某種方式包裝的 ID-值對(duì),以便更容易在 APK 中找到該塊橄妆。 APK 的 v2 簽名存儲(chǔ)為 ID 值對(duì)衙伶,ID 為 0x7109871a。

格式
APK Signing Block 的格式如下(所有數(shù)字字段都是 little-endian):

  • size of block(以字節(jié)為單位)(不包括此字段)(uint64)
  • uint64-length-prefixed ID-value 對(duì)的序列:
    -- ID (uint32)
    -- value (可變長(zhǎng)度:對(duì)的長(zhǎng)度 - 4 個(gè)字節(jié))
    -- size of block(以字節(jié)為單位):——與第一個(gè)字段(uint64)相同
    -- magic: “APK Sig Block 42”(16 字節(jié))

APK 通過(guò)首先找到 ZIP Central Directory 的開(kāi)頭來(lái)解析(通過(guò)在文件末尾找到 Central Directory 的 ZIP End of Central Directory 記錄害碾,然后從記錄中讀取 Central Directory 的起始偏移量)矢劲。 magic值提供了一種快速方法來(lái)確定中央目錄之前的內(nèi)容可能是 APK 簽名塊。然后size of block有效地指向文件中塊的開(kāi)始慌随。

解釋塊時(shí)應(yīng)忽略具有未知 ID 的 ID-值對(duì)卧须。

1.3 APK 簽名方案 v2 塊

APK 由一個(gè)或多個(gè)簽名者/身份簽名,每個(gè)簽名者/身份都由一個(gè)簽名密鑰表示儒陨。此信息存儲(chǔ)為 APK 簽名方案 v2 塊花嘶。對(duì)于每個(gè)簽名者,將存儲(chǔ)以下信息:

  • (簽名算法蹦漠,摘要椭员,簽名)元組。存儲(chǔ)摘要以將簽名驗(yàn)證與 APK 內(nèi)容的完整性檢查分離笛园。
  • 代表簽名者身份的 X.509 證書(shū)鏈隘击。
  • 作為鍵值對(duì)的附加屬性。

對(duì)于每個(gè)簽名者研铆,APK 會(huì)使用提供的列表中支持的簽名進(jìn)行驗(yàn)證埋同。具有未知簽名算法的簽名將被忽略。當(dāng)遇到多個(gè)支持的簽名時(shí)棵红,由每個(gè)實(shí)現(xiàn)選擇使用哪個(gè)簽名凶赁。這使得將來(lái)能夠以向后兼容的方式引入更強(qiáng)大的簽名方法。建議的方法是驗(yàn)證最強(qiáng)的簽名。

格式

APK 簽名方案 v2 塊存儲(chǔ)在 ID 0x7109871a下的 APK 簽名塊中虱肄。

APK Signature Scheme v2 Block的格式如下(所有數(shù)值均為little-endian致板,所有長(zhǎng)度前綴字段使用uint32表示長(zhǎng)度):

  • 長(zhǎng)度前綴signer的長(zhǎng)度前綴序列:
    • 以長(zhǎng)度為前綴的有signed data
      • 長(zhǎng)度前綴digests的長(zhǎng)度前綴序列:
        • signature algorithm ID (uint32)
        • (長(zhǎng)度前綴) digest
      • X.509 certificates的長(zhǎng)度前綴序列:
        • 以長(zhǎng)度為前綴的 X.509 certificate (ASN.1 DER 形式)
      • 以長(zhǎng)度為前綴的additional attributes的長(zhǎng)度前綴序列:
        • ID (uint32)
        • value (可變長(zhǎng)度:附加屬性的長(zhǎng)度 - 4 個(gè)字節(jié))
    • 長(zhǎng)度前綴signatures的長(zhǎng)度前綴序列:
      • signature algorithm ID (uint32)
      • 簽名signed data上的長(zhǎng)度前綴signature
    • 長(zhǎng)度前綴public key (SubjectPublicKeyInfo,ASN.1 DER 形式)

簽名算法 ID
0x0101—帶有 SHA2-256 摘要咏窿、SHA2-256 MGF1斟或、32 字節(jié)鹽的 RSASSA-PSS,預(yù)告片:0xbc
0x0102—帶有 SHA2-512 摘要集嵌、SHA2-512 MGF1萝挤、64 字節(jié)鹽的 RSASSA-PSS,預(yù)告片:0xbc
0x0103—帶有 SHA2-256 摘要的 RSASSA-PKCS1-v1_5根欧。這適用于需要確定性簽名的構(gòu)建系統(tǒng)平斩。
0x0104—帶有 SHA2-512 摘要的 RSASSA-PKCS1-v1_5。這適用于需要確定性簽名的構(gòu)建系統(tǒng)咽块。
0x0201 - 帶有 SHA2-256 摘要的 ECDSA
0x0202—帶有 SHA2-512 摘要的 ECDSA
0x0301 - 帶有 SHA2-256 摘要的 DSA
Android平臺(tái)支持以上所有簽名算法绘面。簽名工具可能支持算法的一個(gè)子集。

支持的鍵大小和 EC 曲線:

  • RSA:1024侈沪、2048揭璃、4096、8192亭罪、16384
  • EC:NIST P-256瘦馍、P-384、P-521
  • 動(dòng)態(tài)搜索廣告:1024应役、2048情组、3072

完整性保護(hù)的內(nèi)容
為了保護(hù) APK 內(nèi)容,一個(gè) APK 包含四個(gè)部分:

  1. ZIP 條目的內(nèi)容(從偏移量 0 到 APK 簽名塊的開(kāi)始)
  2. APK 簽名塊
  3. ZIP 中央目錄
  4. 中央目錄的 ZIP 結(jié)尾

3箩祥、v3 簽名

Android 9 支持APK 密鑰輪換院崇,這使應(yīng)用能夠在 APK 更新中更改其簽名密鑰。為了使輪換切實(shí)可行袍祖,APK 必須指明新舊簽名密鑰之間的信任級(jí)別底瓣。為了支持密鑰輪換,將APK 簽名方案從 v2 更新到 v3蕉陋,以允許使用新舊密鑰捐凭。 V3 向 APK 簽名塊添加了有關(guān)支持的 SDK 版本和旋轉(zhuǎn)證明結(jié)構(gòu)的信息。

3.1 APK 簽名塊

為了保持與 v1 APK 格式的向后兼容性凳鬓,v2 和 v3 APK 簽名存儲(chǔ)在 APK 簽名塊中茁肠,位于 ZIP 中央目錄之前。

V3 APK 簽名塊格式與 v2 相同缩举。 APK 的 v3 簽名以 ID 值對(duì)的形式存儲(chǔ)垦梆,ID 為 0xf05368c0匹颤。

3.2 APK 簽名方案 v3 塊

v3 方案的設(shè)計(jì)與v2 方案非常相似。它具有相同的通用格式奶赔,并支持相同的簽名算法 ID、密鑰大小和 EC 曲線杠氢。

但是站刑,v3 方案添加了有關(guān)支持的 SDK 版本和旋轉(zhuǎn)證明結(jié)構(gòu)的信息。

格式

APK 簽名方案 v3 塊存儲(chǔ)在 APK 簽名塊中鼻百,ID 0xf05368c0 绞旅。

APK 簽名方案 v3 塊的格式遵循 v2 的格式:

  • 長(zhǎng)度前綴signer的長(zhǎng)度前綴序列:
    • 以長(zhǎng)度為前綴的有signed data
      • 長(zhǎng)度前綴digests的長(zhǎng)度前綴序列:
        • signature algorithm ID (4字節(jié))
        • digest (長(zhǎng)度前綴)
      • X.509 certificates的長(zhǎng)度前綴序列:
        • 以長(zhǎng)度為前綴的 X.509 certificate (ASN.1 DER 形式)
      • minSDK (uint32) - 如果平臺(tái)版本低于此數(shù)字,則應(yīng)忽略此簽名者温艇。
      • maxSDK (uint32) - 如果平臺(tái)版本高于此數(shù)字因悲,則應(yīng)忽略此簽名者。
      • 以長(zhǎng)度為前綴的additional attributes的長(zhǎng)度前綴序列:
        • ID (uint32)
        • value (可變長(zhǎng)度:附加屬性的長(zhǎng)度 - 4 個(gè)字節(jié))
        • ID - **0x3ba06f8c**
        • value -旋轉(zhuǎn)證明結(jié)構(gòu)
    • minSDK (uint32) - 簽名數(shù)據(jù)部分中 minSDK 值的副本 - 如果當(dāng)前平臺(tái)不在范圍內(nèi)勺爱,則用于跳過(guò)此簽名的驗(yàn)證晃琳。必須匹配帶符號(hào)的數(shù)據(jù)值。
    • maxSDK (uint32) - 簽名數(shù)據(jù)部分中 maxSDK 值的副本 - 如果當(dāng)前平臺(tái)不在范圍內(nèi)琐鲁,則用于跳過(guò)此簽名的驗(yàn)證卫旱。必須匹配帶符號(hào)的數(shù)據(jù)值。
    • 長(zhǎng)度前綴signatures的長(zhǎng)度前綴序列:
      • signature algorithm ID (uint32)
      • 簽名signed data上的長(zhǎng)度前綴signature
    • 長(zhǎng)度前綴public key (SubjectPublicKeyInfo围段,ASN.1 DER 形式)
3.3 旋轉(zhuǎn)證明和自信任舊證書(shū)結(jié)構(gòu)

輪換證明結(jié)構(gòu)允許應(yīng)用輪換其簽名證書(shū)顾翼,而不會(huì)被與其通信的其他應(yīng)用程序阻止。為此奈泪,應(yīng)用簽名包含兩條新數(shù)據(jù):

  • 向第三方斷言應(yīng)用程序的簽名證書(shū)可以在其前身受信任的任何地方被信任
  • 應(yīng)用程序本身仍然信任的應(yīng)用程序的舊簽名證書(shū)

簽名數(shù)據(jù)部分中的旋轉(zhuǎn)證明屬性由一個(gè)單鏈表組成适贸,每個(gè)節(jié)點(diǎn)都包含一個(gè)簽名證書(shū),用于對(duì)應(yīng)用程序的先前版本進(jìn)行簽名涝桅。此屬性旨在包含概念性的旋轉(zhuǎn)證明和自信任舊證書(shū)數(shù)據(jù)結(jié)構(gòu)拜姿。該列表按版本排序,其中最早的簽名證書(shū)對(duì)應(yīng)于根節(jié)點(diǎn)冯遂。旋轉(zhuǎn)證明數(shù)據(jù)結(jié)構(gòu)是通過(guò)讓每個(gè)節(jié)點(diǎn)中的證書(shū)簽署列表中的下一個(gè)證書(shū)來(lái)構(gòu)建的砾隅,從而為每個(gè)新密鑰注入證據(jù),證明它應(yīng)該與舊密鑰一樣受信任债蜜。

self-trusted-old-certs 數(shù)據(jù)結(jié)構(gòu)是通過(guò)向每個(gè)節(jié)點(diǎn)添加標(biāo)志來(lái)構(gòu)建的晴埂,這些標(biāo)志指示其在集合中的成員資格和屬性。例如寻定,可能存在一個(gè)標(biāo)志儒洛,指示給定節(jié)點(diǎn)上的簽名證書(shū)是可信的,以獲得 Android 簽名權(quán)限狼速。此標(biāo)志允許由舊證書(shū)簽名的其他應(yīng)用程序仍被授予由使用新簽名證書(shū)簽名的應(yīng)用程序定義的簽名權(quán)限琅锻。因?yàn)檎麄€(gè)循環(huán)證明屬性駐留在 v3 signer字段的已簽名數(shù)據(jù)部分中,所以它受到用于簽署包含 apk 的密鑰的保護(hù)。

這種格式排除了多個(gè)簽名密鑰不同祖先簽名證書(shū)聚合為一個(gè)(多個(gè)起始節(jié)點(diǎn)到一個(gè)公共接收器)恼蓬。

格式

旋轉(zhuǎn)證明存儲(chǔ)在 ID 0x3ba06f8c下的 APK 簽名方案 v3 塊中惊完。它的格式是:

  • 長(zhǎng)度前綴levels的長(zhǎng)度前綴序列:
    • 以長(zhǎng)度為前綴的signed data (由先前的證書(shū) - 如果存在)
      • 以長(zhǎng)度為前綴的 X.509 certificate (ASN.1 DER 形式)
      • signature algorithm ID (uint32) - 證書(shū)在上一級(jí)使用的算法
    • flags (uint32) - 指示此證書(shū)是否應(yīng)位于 self-trusted-old-certs 結(jié)構(gòu)中以及用于哪些操作的標(biāo)志。
    • signature algorithm ID (uint32) - 必須與下一級(jí)簽名數(shù)據(jù)部分中的 ID 匹配处硬。
    • 上述signed data的長(zhǎng)度前綴signature
3.4 多個(gè)證書(shū)

Android 當(dāng)前將使用多個(gè)證書(shū)簽名的 APK 視為具有與組成證書(shū)分開(kāi)的唯一簽名身份小槐。因此,簽名數(shù)據(jù)部分中的旋轉(zhuǎn)證明屬性形成了一個(gè)有向無(wú)環(huán)圖荷辕,可以更好地將其視為一個(gè)單鏈表凿跳,給定版本的每一組簽名者代表一個(gè)節(jié)點(diǎn)。這為旋轉(zhuǎn)證明結(jié)構(gòu)(下面的多簽名者版本)增加了額外的復(fù)雜性疮方。特別是控嗜,排序成為一個(gè)問(wèn)題。更重要的是骡显,不再能夠獨(dú)立簽署 APK疆栏,因?yàn)樾D(zhuǎn)證明結(jié)構(gòu)必須讓舊的簽名證書(shū)簽署新的證書(shū)集,而不是一個(gè)接一個(gè)地簽署它們惫谤。例如承边,由密鑰 A 簽名的 APK 希望由兩個(gè)新密鑰 B 和 C 簽名,B 簽名者不能只包含 A 或 B 的簽名石挂,因?yàn)檫@與 B 和 C 的簽名身份不同博助。這將意味著簽名者必須在構(gòu)建這樣的結(jié)構(gòu)之前進(jìn)行協(xié)調(diào)。

3.5 多簽名者旋轉(zhuǎn)證明屬性
  • 長(zhǎng)度前綴sets的長(zhǎng)度前綴序列:
    • signed data (由前一組 - 如果存在)
      • 以長(zhǎng)度為前綴的certificates序列
        • 以長(zhǎng)度為前綴的 X.509 certificate (ASN.1 DER 形式)
      • signature algorithm IDs序列 (uint32) - 一個(gè)用于前一組中的每個(gè)證書(shū)痹愚,順序相同富岳。
    • flags (uint32) - 指示這組證書(shū)是否應(yīng)該在 self-trusted-old-certs 結(jié)構(gòu)中以及針對(duì)哪些操作的標(biāo)志。
    • 長(zhǎng)度前綴signatures的長(zhǎng)度前綴序列:
      • signature algorithm ID (uint32) - 必須與簽名數(shù)據(jù)部分中的匹配
      • 上述signed data的長(zhǎng)度前綴signature
3.6 旋轉(zhuǎn)證明結(jié)構(gòu)中的多個(gè)祖先

v3 方案也不處理兩個(gè)不同的密鑰輪換到同一個(gè)應(yīng)用程序的同一個(gè)簽名密鑰拯腮。這與收購(gòu)的情況不同窖式,收購(gòu)公司希望移動(dòng)被收購(gòu)的應(yīng)用程序以使用其簽名密鑰來(lái)共享權(quán)限。此次收購(gòu)被視為受支持的用例动壤,因?yàn)樾聭?yīng)用程序?qū)⑼ㄟ^(guò)其包名稱來(lái)區(qū)分萝喘,并且可能包含其自己的旋轉(zhuǎn)證明結(jié)構(gòu)。不支持的情況琼懊,即同一個(gè)應(yīng)用程序有兩條不同的路徑來(lái)獲得相同的證書(shū)阁簸,打破了密鑰輪換設(shè)計(jì)中的許多假設(shè)。

3.7 確認(rèn)

在 Android 9 及更高版本中哼丈,可以根據(jù) APK 簽名方案 v3启妹、v2 方案或 v1 方案驗(yàn)證 APK。舊平臺(tái)忽略 v3 簽名并嘗試驗(yàn)證 v2 簽名醉旦,然后是 v1饶米。

3.8 APK 簽名方案 v3 驗(yàn)證
  1. 找到 APK 簽名塊并驗(yàn)證:
    1. APK 簽名塊的兩個(gè)大小字段包含相同的值桨啃。
    2. ZIP Central Directory 后緊跟 ZIP End of Central Directory 記錄。
    3. 中央目錄的 ZIP 結(jié)尾后面沒(méi)有更多數(shù)據(jù)檬输。
  2. 在 APK 簽名塊內(nèi)找到第一個(gè) APK 簽名方案 v3 塊照瘾。如果存在 v3 塊,請(qǐng)繼續(xù)執(zhí)行步驟 3丧慈。否則析命,回退到使用 v2 方案驗(yàn)證 APK。
  3. 對(duì)于具有當(dāng)前平臺(tái)范圍內(nèi)的最小和最大 SDK 版本的 APK 簽名方案 v3 塊中的每個(gè)signer
    1. signatures中選擇支持的最強(qiáng)signature algorithm ID 伊滋。強(qiáng)度排序取決于每個(gè)實(shí)現(xiàn)/平臺(tái)版本碳却。
    2. 使用public key驗(yàn)證signatures signed data中的相應(yīng)signature 队秩。 (現(xiàn)在解析signed data是安全的笑旺。)
    3. 驗(yàn)證簽名數(shù)據(jù)中的最小和最大 SDK 版本是否與為signer指定的版本匹配。
    4. 驗(yàn)證digestssignatures中簽名算法 ID 的有序列表是否相同馍资。 (這是為了防止簽名剝離/添加筒主。)
    5. 使用與簽名算法使用的摘要算法相同的摘要算法計(jì)算 APK 內(nèi)容的摘要。
    6. 驗(yàn)證計(jì)算出的摘要與來(lái)自digests的相應(yīng)digest相同鸟蟹。
    7. 驗(yàn)證第一個(gè)certificatecertificates是否與public key相同乌妙。
    8. 如果signer的旋轉(zhuǎn)證明屬性存在,則驗(yàn)證該結(jié)構(gòu)是有效的建钥,并且此signer是列表中的最后一個(gè)證書(shū)藤韵。
  4. 如果在當(dāng)前平臺(tái)的范圍內(nèi)恰好找到一個(gè)signer ,并且該signer的步驟 3 成功熊经,則驗(yàn)證成功泽艘。

注意:如果第 3 步或第 4 步發(fā)生故障,則不得使用 v1 或 v2 方案驗(yàn)證 APK镐依。

4匹涮、v4 簽名

Android 11 通過(guò) APK 簽名方案 v4 支持與流式傳輸兼容的簽名方案。v4 簽名基于根據(jù) APK 的所有字節(jié)計(jì)算得出的 Merkle 哈希樹(shù)槐壳。它完全遵循 fs-verity 哈希樹(shù)的結(jié)構(gòu)(例如然低,對(duì)鹽進(jìn)行零填充,以及對(duì)最后一個(gè)分塊進(jìn)行零填充务唐。)Android 11 將簽名存儲(chǔ)在單獨(dú)的 <apk name>.apk.idsig 文件中雳攘。v4 簽名需要 v2v3 簽名作為補(bǔ)充。

5枫笛、簽名驗(yàn)證流程

簽名驗(yàn)證流程

APK 的整個(gè)文件哈希根據(jù)存儲(chǔ)在 APK 簽名塊中的 v2+ 簽名進(jìn)行驗(yàn)證来农。哈希涵蓋了除包含 v2+ 簽名的 APK 簽名塊之外的所有內(nèi)容。在 APK 簽名塊之外對(duì) APK 進(jìn)行的任何修改都會(huì)使 APK 的 v2+ 簽名無(wú)效崇堰。帶有剝離 v2+ 簽名的 APK 也會(huì)被拒絕沃于,因?yàn)樗鼈兊?v1 簽名指定 APK 是 v2 簽名的涩咖,這使得 Android 7.0 和更新版本拒絕使用它們的 v1 簽名驗(yàn)證 APK。

需要注意的是繁莹,對(duì)于覆蓋安裝的情況檩互,簽名校驗(yàn)只支持升級(jí),而不支持降級(jí)咨演。也就是說(shuō)設(shè)備上安裝了一個(gè)使用 v1 簽名的 APK闸昨,可以使用 v2 簽名的 APK 進(jìn)行覆蓋安裝,反之則不允許薄风。

5饵较、簽名方式對(duì)比
簽名方式 引入版本 方案優(yōu)勢(shì)
v1 一開(kāi)始就有 基于 JAR 簽名
v2 Android 7 v2 是一個(gè)完整的文件簽名方案,它通過(guò)檢測(cè) APK 受保護(hù)部分的任何更改來(lái)提高驗(yàn)證速度并加強(qiáng)完整性保證
v3 Android 9 支持APK 密鑰輪換遭赂,這使應(yīng)用能夠在 APK 更新中更改其簽名密鑰
v4 Android 11 支持與流式傳輸兼容的簽名方案

為了獲得最大的兼容性循诉,請(qǐng)使用所有方案對(duì)應(yīng)用程序進(jìn)行簽名,首先使用 v1撇他,然后使用 v2茄猫,然后使用 v3。 Android 7.0+ 和更新的設(shè)備安裝使用 v2+ 方案簽名的應(yīng)用程序比僅使用 v1 方案簽名的應(yīng)用程序更快困肩。較舊的 Android 平臺(tái)忽略 v2+ 簽名划纽,因此需要應(yīng)用程序包含 v1 簽名。

6锌畸、獲取 apk 簽名類型

查看簽名方式

sdk 目錄:cd /Users/zyb/Library/Android/sdk/build-tools/26.0.2/

apksigner verify -v <apk>

二勇劣、獲取簽名指紋

1、簽名文件

keytool -list -v -keystore <keystore-file>
指紋

注意這里命令行輸出有一行提示潭枣,提示 .jks 向 .pkcs12 轉(zhuǎn)換比默。

2、apk 文件

keytool -printcert -jarfile xx/xx.apk

3卸耘、.RSA文件

keytool -printcert -file xx/META-INF/CERT.RSA

4退敦、AS sigingReport task

./gradlew sigingReport

源碼:

// gradle-4.2.0-sources
public class SigningReportTask extends DefaultTask {
       ···
    private static SigningInfo getSigningInfo(
            @NonNull SigningConfig signingConfig,
            @NonNull Map<SigningConfig, SigningInfo> cache) {
        SigningInfo signingInfo = cache.get(signingConfig);

        if (signingInfo == null) {
            signingInfo = new SigningInfo();

            if (signingConfig.isSigningReady()) {
                try {
                    @SuppressWarnings(
                            "ConstantConditions") // Called isSigningReady() above, so these will not be null.
                    CertificateInfo certificateInfo =
                            KeystoreHelper.getCertificateInfo(
                                    signingConfig.getStoreType(),
                                    signingConfig.getStoreFile(),
                                    signingConfig.getStorePassword(),
                                    signingConfig.getKeyPassword(),
                                    signingConfig.getKeyAlias());
                    signingInfo.md5 = getFingerprint(certificateInfo.getCertificate(), "MD5");
                    signingInfo.sha1 = getFingerprint(certificateInfo.getCertificate(), "SHA1");
                    signingInfo.sha256 =
                            getFingerprint(certificateInfo.getCertificate(), "SHA-256");
                    signingInfo.notAfter = certificateInfo.getCertificate().getNotAfter();
                } catch (KeytoolException e) {
                    signingInfo.error = e.getMessage();
                } catch (FileNotFoundException e) {
                    signingInfo.error = "Missing keystore";
                }
            }

            cache.put(signingConfig, signingInfo);
        }

        return signingInfo;
    }

5、代碼方式

import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.os.Build
import java.security.MessageDigest

/**
 * Created on 2022/12/5 6:39 下午
 * @author shilong
 *
 * desc: 獲取app 簽名信息
 */
object AppSignatureUtil {
    private const val MD5 = "MD5"
    private const val SHA1 = "SHA1"
    private const val SHA256 = "SHA256"

    const val TAG = "APP_SIGNATURE"

    /**
     * 獲取簽名信息
     */
    @SuppressLint("PackageManagerGetSignatures")
    private fun getSignatures(context: Context, packageName: String): Array<out Signature>? {
        return try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                val packageInfo = context.packageManager?.getPackageInfo(
                    packageName,
                    PackageManager.GET_SIGNING_CERTIFICATES
                )
                packageInfo?.signingInfo?.signingCertificateHistory
            } else {
                val packageInfo = context.packageManager?.getPackageInfo(
                    packageName,
                    PackageManager.GET_SIGNATURES
                )
                packageInfo?.signatures
            }
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    /**
     * 獲取相應(yīng)類型的字符串(把簽名的byte[]信息轉(zhuǎn)換成 95:F4:D4:FG 這樣的字符串形式)
     */
    private fun getSignatureByteString(signature: Signature, type: String): String {
        return try {
            val hexBytes = signature.toByteArray()
            val digest = MessageDigest.getInstance(type)
            val digestBytes = digest.digest(hexBytes)
            val sb = StringBuilder()
            for (digestByte in digestBytes) {
                sb.append(
                    (Integer.toHexString((digestByte.toInt() and 0xFF) or 0x100))
                        .substring(1, 3).uppercase()
                ).append(":")
            }
            sb.substring(0, sb.length - 1)
        } catch (e: Exception) {
            "error!"
        }
    }

    private fun getFingerprint(context: Context, type: String): List<String> {
        val packageName = context.packageName
        val signatures = getSignatures(context, packageName)
        val list = mutableListOf<String>()
        signatures?.forEach { signature ->
            when (type) {
                MD5 -> {
                    val fingerprint = getSignatureByteString(signature, MD5)
                    list.add(fingerprint)
                }
                SHA1 -> {
                    val fingerprint = getSignatureByteString(signature, SHA1)
                    list.add(fingerprint)
                }
                SHA256 -> {
                    val fingerprint = getSignatureByteString(signature, SHA256)
                    list.add(fingerprint)
                }
            }
        }
        return list
    }


    /**
     * 獲取簽名的MD5值
     */
    fun getMD5(context: Context): String {
        val list = getFingerprint(context, MD5)
        if (list.isEmpty()) {
            return ""
        }
        return list[0]
    }

    /**
     * 獲取簽名 sha1 值
     */
    fun getSHA1(context: Context): String {
        val list = getFingerprint(context, SHA1)
        if (list.isEmpty()) {
            return ""
        }
        return list[0]
    }

    /**
     * 獲取簽名 sha256 值
     */
    fun getSHA256(context: Context): String {
        val list = getFingerprint(context, SHA256)
        if (list.isEmpty()) {
            return ""
        }
        return list[0]
    }
}

apk 里沒(méi)有簽名文件

  • 查看簽名方式

sdk 目錄:cd /Users/zyb/Library/Android/sdk/build-tools/26.0.2/

apksigner verify -v <apk>

App 支持的最低API級(jí)別太高minSdkVersion>=24(android 7)蚣抗,
只有minSdkVersion<=23(android 6)侈百,才支持v1簽名。

注:
只有v2簽名的apk翰铡,包體中不含簽名文件钝域。只有v1簽名或v1+v2簽名的apk,包體中有簽名文件CERT.SF锭魔、Cert.RSA

無(wú)法同時(shí)使用簽名方案v1和v2問(wèn)題

三例证、關(guān)于簽名的開(kāi)發(fā)

1、多渠道

  • v1:在zip的EOCD數(shù)據(jù)塊加入渠道信息
  • v2:walle

2迷捧、共享UID功能

// manifest
android:sharedUserId

API 級(jí)別 29 中已棄用此常量织咧。
共享用戶 ID 會(huì)在軟件包管理器中導(dǎo)致具有不確定性的行為胀葱。因此,強(qiáng)烈建議您不要使用它笙蒙,并且我們?cè)谖磥?lái)的 Android 版本中會(huì)將其移除抵屿。相反,應(yīng)用應(yīng)使用適當(dāng)?shù)耐ㄐ艡C(jī)制(例如服務(wù)和 content provider)捅位,在共享組件之間實(shí)現(xiàn)互操作性轧葛。請(qǐng)注意,現(xiàn)有應(yīng)用無(wú)法移除此值艇搀,因?yàn)椴恢С植皇褂霉蚕碛脩?ID尿扯。這類應(yīng)用應(yīng)添加 android:sharedUserMaxSdkVersion="32",以免在新用戶安裝時(shí)使用共享用戶 ID焰雕。

將與其他應(yīng)用共享的 Linux 用戶 ID 的名稱衷笋。 默認(rèn)情況下,Android 會(huì)為每個(gè)應(yīng)用分配其唯一用戶 ID淀散。 不過(guò)右莱,如果針對(duì)兩個(gè)或多個(gè)應(yīng)用將此屬性設(shè)置為相同的值蚜锨,則這些應(yīng)用都將共享相同的 ID档插,前提是這些應(yīng)用的證書(shū)集完全相同。具有相同用戶 ID 的應(yīng)用可以訪問(wèn)彼此的數(shù)據(jù)亚再,如果需要的話郭膛,還可以在同一進(jìn)程中運(yùn)行。

四氛悬、引用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末如捅,一起剝皮案震驚了整個(gè)濱河市棍现,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镜遣,老刑警劉巖己肮,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異悲关,居然都是意外死亡谎僻,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門寓辱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)艘绍,“玉大人,你說(shuō)我怎么就攤上這事秫筏∮站希” “怎么了挎挖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)航夺。 經(jīng)常有香客問(wèn)我肋乍,道長(zhǎng),這世上最難降的妖魔是什么敷存? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任墓造,我火速辦了婚禮,結(jié)果婚禮上锚烦,老公的妹妹穿的比我還像新娘觅闽。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布舶掖。 她就那樣靜靜地躺著宙攻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孕锄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天苞尝,我揣著相機(jī)與錄音畸肆,去河邊找鬼。 笑死宙址,一個(gè)胖子當(dāng)著我的面吹牛轴脐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抡砂,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼大咱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了注益?” 一聲冷哼從身側(cè)響起碴巾,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丑搔,沒(méi)想到半個(gè)月后厦瓢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡低匙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年旷痕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顽冶。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欺抗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出强重,到底是詐尸還是另有隱情绞呈,我是刑警寧澤贸人,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站佃声,受9級(jí)特大地震影響艺智,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜圾亏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一十拣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧志鹃,春花似錦夭问、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至陕见,卻和暖如春秘血,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背评甜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工灰粮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜕着。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓谋竖,卻偏偏與公主長(zhǎng)得像红柱,于是被迫代替她去往敵國(guó)和親承匣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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