前序
1、概念梳理
1.1 消息摘要(Message Digest)
消息摘要又稱數(shù)字摘要(Digital Digest)或數(shù)字指紋(Finger Print)。對不同長度的數(shù)據(jù)通過算法得到一個固定的長度輸入,常用的算法有MD5、SHA1、SHA256。
1.2 數(shù)字簽名
數(shù)字簽名的作用就是保證信息傳輸?shù)耐暾?、發(fā)送者的身份認證、防止交易中的抵賴發(fā)生。數(shù)字簽名技術是將摘要信息用發(fā)送者的私鑰加密,與原文一起傳送給接收者。接收者只有用發(fā)送者的公鑰才能解密被加密的摘要信息然后用HASH函數(shù)對收到的原文產生一個摘要信息,與解密的摘要信息對比。如果相同,則說明收到的信息是完整的,在傳輸過程中沒有被修改,否則說明信息被修改過,因此數(shù)字簽名能夠驗證信息的完整性。
1.3 數(shù)字證書
數(shù)字證書是一個經證書授權(Certificate Authentication)中心數(shù)字簽名的包含公鑰擁有者信息以及公鑰的文件。數(shù)字證書的格式普遍采用的是 X.509 V3 國際標準,一個標準的 X.509 數(shù)字證書通常包含以下內容:
- 證書的發(fā)布機構(Issuer):該證書是由哪個機構(CA 中心)頒發(fā)的。
- 證書的有效期(Validity):證書的有效期,或者說使用期限。過了該日期,證書就失效了。
- 證書所有人的公鑰(Public-Key):該證書所有人想要公布出去的公鑰。
- 證書所有人的名稱(Subject):這個證書是發(fā)給誰的,或者說證書的所有者,一般是某個人或者某個公司名稱、機構的名稱、公司網站的網址等。
- 證書所使用的簽名算法(Signature algorithm):這個數(shù)字證書的數(shù)字簽名所使用的加密算法,這樣就可以使用證書發(fā)布機構的證書里面的公鑰,根據(jù)這個算法對指紋進行解密。
- 證書發(fā)行者對證書的數(shù)字簽名(Thumbprint):數(shù)字證書的hash 值(指紋),用于保證數(shù)字證書的完整性,確保證書沒有被修改過。
數(shù)字證書的原理就是在證書發(fā)布時,CA 機構會根據(jù)簽名算法(Signature algorithm)對整個證書計算其 hash 值(指紋)并和證書放在一起,使用者打開證書時,自己也根據(jù)簽名算法計算一下證書的 hash 值(指紋),如果和證書中記錄的指紋對的上,就說明證書沒有被修改過。
數(shù)字證書本身也用到了數(shù)字簽名技術,只不過簽名的內容是整個證書(里面包含了證書所有者的公鑰以及其他一些內容)。與普通數(shù)字簽名不同的是,數(shù)字證書的簽名者不是隨隨便便一個普通機構,而是 CA 機構。
1.4 JKS格式
JKS 是 java 用來存儲密鑰的容器??梢酝瑫r容納n個公鑰或私鑰,后綴一般是 .jks或者.keystore或 .truststore等。
- 在Java 8之前,這些文件的默認格式為JKS(android .keystore 也是jsk格式的證書)。
- 從Java 9開始,默認的密鑰庫格式為PKCS12。
- Android簽名keystore文件也是jks格式,且1.8之后要求轉換到p12格式。
- JKS是二進制格式,同時包含證書和私鑰,一般有密碼保護,只能存儲非對稱密鑰對(私鑰 + x509公鑰證書)。
- 當應用程序需要通過SSL / TLS進行通信時,在大多數(shù)情況下將使用java keystore和java truststore。
- 密鑰庫和私鑰用不同的密碼進行保護
- JKS和PKCS12之間的最大區(qū)別是JKS是Java專用的格式,而PKCS12是存儲加密的私鑰和證書的標準化且與語言無關的方式。
1.5 PKCS#12 / PFX 格式
- PKCS#12 是公鑰加密標準,通用格式(rsa公司標準)。規(guī)定了可包含所有私鑰、公鑰和證書。文件格式是加密過的。
- PKCS#12 或 PFX 格式是其以二進制格式存儲,也稱為 PFX 文件,在windows中可以直接導入到密鑰區(qū)。也可用于導入和導出證書和私鑰。
- PKCS#12 由 PFX 進化而來的,用于交換公共的和私有的對象的標準格式。
- 文件通常具有擴展名,例如.pkcs12 .pfx .p12。
- 密鑰庫和私鑰用相同密碼進行保護
一、簽名方式
1、v1 簽名
1.1 簽名內容
V1 方案基于簽名的 JAR ,解壓v1簽名的apk包,解壓后會有 META-INF 文件夾。其中有 MANIFEST.MF、CERT.SF、CERT.RSA 三個文件,它們是簽名過程中生成的文件。

三個文件作用如下:
- MANIFEST.MF
該文件中存放的是對apk 中每一個文件使用SHA1或者SHA256消息摘要算法獲得摘要,并使用base64進行編碼后的內容。Name 對應的是APK中文件路徑,SHA-256-Digest 對應的是摘要內容。

- CERT.SF
- SHA1-Digest-Manifest-Main-Attributes:對 MANIFEST.MF 頭部的塊 做 SHA1(或者SHA256)后再用 Base64 編碼。
- SHA1-Digest-Manifest:對整個 MANIFEST.MF 文件做 SHA1(或者 SHA256)后再用 Base64 編碼。
- SHA1-Digest:對 MANIFEST.MF 的各個條目做 SHA1(或者 SHA256)后再用 Base64 編碼。

- CERT.RSA
把生成的 CERT.SF 文件用私鑰計算出簽名, 然后將簽名以及包含公鑰信息的數(shù)字證書一同寫入 CERT.RSA 中保存。Android APK 中的 CERT.RSA 證書是自簽名的,并不需要第三方權威機構發(fā)布或者認證的證書,用戶可以在本地機器自行生成這個自簽名證書。Android 目前不對應用證書進行 CA 認證。

查看 .RSA 文件內容
pkcs7 -inform DER -in xx/META-INF/CERT.RSA -text -noout -print_certs
1.2 簽名流程
計算每一個原始文件的 SHA-1 摘要,寫入到MANIFEST.MF中;
計算整個MANIFEST.MF文件的 SHA-1 摘要,寫入到CERT.SF中;
計算MANIFEST.MF中,每一塊的SHA-1 摘要,寫入到CERT.SF中;
計算整個CERT.SF文件的摘要,使用開發(fā)者私鑰計算出摘要的簽名;
-
將簽名和開發(fā)者證書(X.509)寫入CERT.RSA
簽名流程
1.3 簽名校驗
簽名驗證是發(fā)生在 APK 的安裝過程中,一共分為三步:
- 檢查 APK 中包含的所有文件,對應的摘要值與 MANIFEST.MF 文件中記錄的值一致。
- 使用證書文件(RSA 文件)檢驗簽名文件(SF 文件)沒有被修改過。
- 使用簽名文件(SF 文件)檢驗 MF 文件沒有被修改過。
1.4 總結
- V1 需要對 apk 中的每個文件都計算摘要并驗證,如果文件很多,校驗時間會很長。
- V1 簽名只會校驗 Zip 文件中的部分文件,META-INFO 文件夾就不會參與校驗。
2、v2 簽名
APK Signature Scheme v2 是一個完整的文件簽名方案,它通過檢測 APK 受保護部分的任何更改來提高驗證速度并加強完整性保證。Android 7 引入、Android 11 強制。
使用 APK 簽名方案 v2 進行簽名,會將 APK 簽名塊插入到 APK 文件中,緊挨在 ZIP 中央目錄部分之前。在 APK 簽名塊內,v2 簽名和簽名者身份信息存儲在APK 簽名方案 v2 塊中。

APK 簽名方案 v2 是在 Android 7.0 (Nougat) 中引入的。要使 APK 可安裝在 Android 6.0 (Marshmallow) 和更舊的設備上,應先使用 v1 方案對 APK 進行簽名,然后再使用 v2 方案進行簽名。
2.1 APK 簽名塊
為了保持與 v1 APK 格式的向后兼容性,v2 和更新的 APK 簽名存儲在 APK 簽名塊中,這是一個新容器,用于支持 APK 簽名方案 v2。在 APK 文件中,APK 簽名塊位于 ZIP 中央目錄之前,該目錄位于文件末尾。
該塊包含以某種方式包裝的 ID-值對,以便更容易在 APK 中找到該塊。 APK 的 v2 簽名存儲為 ID 值對,ID 為 0x7109871a。
格式
APK Signing Block 的格式如下(所有數(shù)字字段都是 little-endian):
- size of block(以字節(jié)為單位)(不包括此字段)(uint64)
- uint64-length-prefixed ID-value 對的序列:
-- ID (uint32)
-- value (可變長度:對的長度 - 4 個字節(jié))
-- size of block(以字節(jié)為單位):——與第一個字段(uint64)相同
-- magic: “APK Sig Block 42”(16 字節(jié))
APK 通過首先找到 ZIP Central Directory 的開頭來解析(通過在文件末尾找到 Central Directory 的 ZIP End of Central Directory 記錄,然后從記錄中讀取 Central Directory 的起始偏移量)。 magic值提供了一種快速方法來確定中央目錄之前的內容可能是 APK 簽名塊。然后size of block有效地指向文件中塊的開始。
解釋塊時應忽略具有未知 ID 的 ID-值對。
1.3 APK 簽名方案 v2 塊
APK 由一個或多個簽名者/身份簽名,每個簽名者/身份都由一個簽名密鑰表示。此信息存儲為 APK 簽名方案 v2 塊。對于每個簽名者,將存儲以下信息:
- (簽名算法,摘要,簽名)元組。存儲摘要以將簽名驗證與 APK 內容的完整性檢查分離。
- 代表簽名者身份的 X.509 證書鏈。
- 作為鍵值對的附加屬性。
對于每個簽名者,APK 會使用提供的列表中支持的簽名進行驗證。具有未知簽名算法的簽名將被忽略。當遇到多個支持的簽名時,由每個實現(xiàn)選擇使用哪個簽名。這使得將來能夠以向后兼容的方式引入更強大的簽名方法。建議的方法是驗證最強的簽名。
格式
APK 簽名方案 v2 塊存儲在 ID 0x7109871a下的 APK 簽名塊中。
APK Signature Scheme v2 Block的格式如下(所有數(shù)值均為little-endian,所有長度前綴字段使用uint32表示長度):
- 長度前綴
signer的長度前綴序列:- 以長度為前綴的有
signed data:- 長度前綴
digests的長度前綴序列:-
signature algorithm ID(uint32) - (長度前綴)
digest
-
- X.509
certificates的長度前綴序列:- 以長度為前綴的 X.509
certificate(ASN.1 DER 形式)
- 以長度為前綴的 X.509
- 以長度為前綴的
additional attributes的長度前綴序列:-
ID(uint32) -
value(可變長度:附加屬性的長度 - 4 個字節(jié))
-
- 長度前綴
- 長度前綴
signatures的長度前綴序列:-
signature algorithm ID(uint32) - 簽名
signed data上的長度前綴signature
-
- 長度前綴
public key(SubjectPublicKeyInfo,ASN.1 DER 形式)
- 以長度為前綴的有
簽名算法 ID
0x0101—帶有 SHA2-256 摘要、SHA2-256 MGF1、32 字節(jié)鹽的 RSASSA-PSS,預告片:0xbc
0x0102—帶有 SHA2-512 摘要、SHA2-512 MGF1、64 字節(jié)鹽的 RSASSA-PSS,預告片:0xbc
0x0103—帶有 SHA2-256 摘要的 RSASSA-PKCS1-v1_5。這適用于需要確定性簽名的構建系統(tǒng)。
0x0104—帶有 SHA2-512 摘要的 RSASSA-PKCS1-v1_5。這適用于需要確定性簽名的構建系統(tǒng)。
0x0201 - 帶有 SHA2-256 摘要的 ECDSA
0x0202—帶有 SHA2-512 摘要的 ECDSA
0x0301 - 帶有 SHA2-256 摘要的 DSA
Android平臺支持以上所有簽名算法。簽名工具可能支持算法的一個子集。
支持的鍵大小和 EC 曲線:
- RSA:1024、2048、4096、8192、16384
- EC:NIST P-256、P-384、P-521
- 動態(tài)搜索廣告:1024、2048、3072
完整性保護的內容
為了保護 APK 內容,一個 APK 包含四個部分:
- ZIP 條目的內容(從偏移量 0 到 APK 簽名塊的開始)
- APK 簽名塊
- ZIP 中央目錄
- 中央目錄的 ZIP 結尾
3、v3 簽名
Android 9 支持APK 密鑰輪換,這使應用能夠在 APK 更新中更改其簽名密鑰。為了使輪換切實可行,APK 必須指明新舊簽名密鑰之間的信任級別。為了支持密鑰輪換,將APK 簽名方案從 v2 更新到 v3,以允許使用新舊密鑰。 V3 向 APK 簽名塊添加了有關支持的 SDK 版本和旋轉證明結構的信息。
3.1 APK 簽名塊
為了保持與 v1 APK 格式的向后兼容性,v2 和 v3 APK 簽名存儲在 APK 簽名塊中,位于 ZIP 中央目錄之前。
V3 APK 簽名塊格式與 v2 相同。 APK 的 v3 簽名以 ID 值對的形式存儲,ID 為 0xf05368c0。
3.2 APK 簽名方案 v3 塊
v3 方案的設計與v2 方案非常相似。它具有相同的通用格式,并支持相同的簽名算法 ID、密鑰大小和 EC 曲線。
但是,v3 方案添加了有關支持的 SDK 版本和旋轉證明結構的信息。
格式
APK 簽名方案 v3 塊存儲在 APK 簽名塊中,ID 0xf05368c0 。
APK 簽名方案 v3 塊的格式遵循 v2 的格式:
- 長度前綴
signer的長度前綴序列:- 以長度為前綴的有
signed data:- 長度前綴
digests的長度前綴序列:-
signature algorithm ID(4字節(jié)) -
digest(長度前綴)
-
- X.509
certificates的長度前綴序列:- 以長度為前綴的 X.509
certificate(ASN.1 DER 形式)
- 以長度為前綴的 X.509
-
minSDK(uint32) - 如果平臺版本低于此數(shù)字,則應忽略此簽名者。 -
maxSDK(uint32) - 如果平臺版本高于此數(shù)字,則應忽略此簽名者。 - 以長度為前綴的
additional attributes的長度前綴序列:-
ID(uint32) -
value(可變長度:附加屬性的長度 - 4 個字節(jié)) ID - **0x3ba06f8c**-
value -旋轉證明結構
-
- 長度前綴
-
minSDK(uint32) - 簽名數(shù)據(jù)部分中 minSDK 值的副本 - 如果當前平臺不在范圍內,則用于跳過此簽名的驗證。必須匹配帶符號的數(shù)據(jù)值。 -
maxSDK(uint32) - 簽名數(shù)據(jù)部分中 maxSDK 值的副本 - 如果當前平臺不在范圍內,則用于跳過此簽名的驗證。必須匹配帶符號的數(shù)據(jù)值。 - 長度前綴
signatures的長度前綴序列:-
signature algorithm ID(uint32) - 簽名
signed data上的長度前綴signature
-
- 長度前綴
public key(SubjectPublicKeyInfo,ASN.1 DER 形式)
- 以長度為前綴的有
3.3 旋轉證明和自信任舊證書結構
輪換證明結構允許應用輪換其簽名證書,而不會被與其通信的其他應用程序阻止。為此,應用簽名包含兩條新數(shù)據(jù):
- 向第三方斷言應用程序的簽名證書可以在其前身受信任的任何地方被信任
- 應用程序本身仍然信任的應用程序的舊簽名證書
簽名數(shù)據(jù)部分中的旋轉證明屬性由一個單鏈表組成,每個節(jié)點都包含一個簽名證書,用于對應用程序的先前版本進行簽名。此屬性旨在包含概念性的旋轉證明和自信任舊證書數(shù)據(jù)結構。該列表按版本排序,其中最早的簽名證書對應于根節(jié)點。旋轉證明數(shù)據(jù)結構是通過讓每個節(jié)點中的證書簽署列表中的下一個證書來構建的,從而為每個新密鑰注入證據(jù),證明它應該與舊密鑰一樣受信任。
self-trusted-old-certs 數(shù)據(jù)結構是通過向每個節(jié)點添加標志來構建的,這些標志指示其在集合中的成員資格和屬性。例如,可能存在一個標志,指示給定節(jié)點上的簽名證書是可信的,以獲得 Android 簽名權限。此標志允許由舊證書簽名的其他應用程序仍被授予由使用新簽名證書簽名的應用程序定義的簽名權限。因為整個循環(huán)證明屬性駐留在 v3 signer字段的已簽名數(shù)據(jù)部分中,所以它受到用于簽署包含 apk 的密鑰的保護。
這種格式排除了多個簽名密鑰和不同祖先簽名證書聚合為一個(多個起始節(jié)點到一個公共接收器)。
格式
旋轉證明存儲在 ID 0x3ba06f8c下的 APK 簽名方案 v3 塊中。它的格式是:
- 長度前綴
levels的長度前綴序列:- 以長度為前綴的
signed data(由先前的證書 - 如果存在)- 以長度為前綴的 X.509
certificate(ASN.1 DER 形式) -
signature algorithm ID(uint32) - 證書在上一級使用的算法
- 以長度為前綴的 X.509
-
flags(uint32) - 指示此證書是否應位于 self-trusted-old-certs 結構中以及用于哪些操作的標志。 -
signature algorithm ID(uint32) - 必須與下一級簽名數(shù)據(jù)部分中的 ID 匹配。 - 上述
signed data的長度前綴signature
- 以長度為前綴的
3.4 多個證書
Android 當前將使用多個證書簽名的 APK 視為具有與組成證書分開的唯一簽名身份。因此,簽名數(shù)據(jù)部分中的旋轉證明屬性形成了一個有向無環(huán)圖,可以更好地將其視為一個單鏈表,給定版本的每一組簽名者代表一個節(jié)點。這為旋轉證明結構(下面的多簽名者版本)增加了額外的復雜性。特別是,排序成為一個問題。更重要的是,不再能夠獨立簽署 APK,因為旋轉證明結構必須讓舊的簽名證書簽署新的證書集,而不是一個接一個地簽署它們。例如,由密鑰 A 簽名的 APK 希望由兩個新密鑰 B 和 C 簽名,B 簽名者不能只包含 A 或 B 的簽名,因為這與 B 和 C 的簽名身份不同。這將意味著簽名者必須在構建這樣的結構之前進行協(xié)調。
3.5 多簽名者旋轉證明屬性
- 長度前綴
sets的長度前綴序列:-
signed data(由前一組 - 如果存在)- 以長度為前綴的
certificates序列- 以長度為前綴的 X.509
certificate(ASN.1 DER 形式)
- 以長度為前綴的 X.509
-
signature algorithm IDs序列 (uint32) - 一個用于前一組中的每個證書,順序相同。
- 以長度為前綴的
-
flags(uint32) - 指示這組證書是否應該在 self-trusted-old-certs 結構中以及針對哪些操作的標志。 - 長度前綴
signatures的長度前綴序列:-
signature algorithm ID(uint32) - 必須與簽名數(shù)據(jù)部分中的匹配 - 上述
signed data的長度前綴signature
-
-
3.6 旋轉證明結構中的多個祖先
v3 方案也不處理兩個不同的密鑰輪換到同一個應用程序的同一個簽名密鑰。這與收購的情況不同,收購公司希望移動被收購的應用程序以使用其簽名密鑰來共享權限。此次收購被視為受支持的用例,因為新應用程序將通過其包名稱來區(qū)分,并且可能包含其自己的旋轉證明結構。不支持的情況,即同一個應用程序有兩條不同的路徑來獲得相同的證書,打破了密鑰輪換設計中的許多假設。
3.7 確認
在 Android 9 及更高版本中,可以根據(jù) APK 簽名方案 v3、v2 方案或 v1 方案驗證 APK。舊平臺忽略 v3 簽名并嘗試驗證 v2 簽名,然后是 v1。
3.8 APK 簽名方案 v3 驗證
- 找到 APK 簽名塊并驗證:
- APK 簽名塊的兩個大小字段包含相同的值。
- ZIP Central Directory 后緊跟 ZIP End of Central Directory 記錄。
- 中央目錄的 ZIP 結尾后面沒有更多數(shù)據(jù)。
- 在 APK 簽名塊內找到第一個 APK 簽名方案 v3 塊。如果存在 v3 塊,請繼續(xù)執(zhí)行步驟 3。否則,回退到使用 v2 方案驗證 APK。
- 對于具有當前平臺范圍內的最小和最大 SDK 版本的 APK 簽名方案 v3 塊中的每個
signer:- 從
signatures中選擇支持的最強signature algorithm ID。強度排序取決于每個實現(xiàn)/平臺版本。 - 使用
public key驗證signaturessigned data中的相應signature。 (現(xiàn)在解析signed data是安全的。) - 驗證簽名數(shù)據(jù)中的最小和最大 SDK 版本是否與為
signer指定的版本匹配。 - 驗證
digests和signatures中簽名算法 ID 的有序列表是否相同。 (這是為了防止簽名剝離/添加。) - 使用與簽名算法使用的摘要算法相同的摘要算法計算 APK 內容的摘要。
- 驗證計算出的摘要與來自
digests的相應digest相同。 - 驗證第一個
certificate的certificates是否與public key相同。 - 如果
signer的旋轉證明屬性存在,則驗證該結構是有效的,并且此signer是列表中的最后一個證書。
- 從
- 如果在當前平臺的范圍內恰好找到一個
signer,并且該signer的步驟 3 成功,則驗證成功。
注意:如果第 3 步或第 4 步發(fā)生故障,則不得使用 v1 或 v2 方案驗證 APK。
4、v4 簽名
Android 11 通過 APK 簽名方案 v4 支持與流式傳輸兼容的簽名方案。v4 簽名基于根據(jù) APK 的所有字節(jié)計算得出的 Merkle 哈希樹。它完全遵循 fs-verity 哈希樹的結構(例如,對鹽進行零填充,以及對最后一個分塊進行零填充。)Android 11 將簽名存儲在單獨的
<apk name>.apk.idsig文件中。v4 簽名需要 v2 或 v3 簽名作為補充。
5、簽名驗證流程

APK 的整個文件哈希根據(jù)存儲在 APK 簽名塊中的 v2+ 簽名進行驗證。哈希涵蓋了除包含 v2+ 簽名的 APK 簽名塊之外的所有內容。在 APK 簽名塊之外對 APK 進行的任何修改都會使 APK 的 v2+ 簽名無效。帶有剝離 v2+ 簽名的 APK 也會被拒絕,因為它們的 v1 簽名指定 APK 是 v2 簽名的,這使得 Android 7.0 和更新版本拒絕使用它們的 v1 簽名驗證 APK。
需要注意的是,對于覆蓋安裝的情況,簽名校驗只支持升級,而不支持降級。也就是說設備上安裝了一個使用 v1 簽名的 APK,可以使用 v2 簽名的 APK 進行覆蓋安裝,反之則不允許。
5、簽名方式對比
| 簽名方式 | 引入版本 | 方案優(yōu)勢 |
|---|---|---|
| v1 | 一開始就有 | 基于 JAR 簽名 |
| v2 | Android 7 | v2 是一個完整的文件簽名方案,它通過檢測 APK 受保護部分的任何更改來提高驗證速度并加強完整性保證 |
| v3 | Android 9 | 支持APK 密鑰輪換,這使應用能夠在 APK 更新中更改其簽名密鑰 |
| v4 | Android 11 | 支持與流式傳輸兼容的簽名方案 |
為了獲得最大的兼容性,請使用所有方案對應用程序進行簽名,首先使用 v1,然后使用 v2,然后使用 v3。 Android 7.0+ 和更新的設備安裝使用 v2+ 方案簽名的應用程序比僅使用 v1 方案簽名的應用程序更快。較舊的 Android 平臺忽略 v2+ 簽名,因此需要應用程序包含 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 轉換。
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
}
}
/**
* 獲取相應類型的字符串(把簽名的byte[]信息轉換成 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 里沒有簽名文件
- 查看簽名方式
sdk 目錄:cd /Users/zyb/Library/Android/sdk/build-tools/26.0.2/
apksigner verify -v <apk>
App 支持的最低API級別太高minSdkVersion>=24(android 7),
只有minSdkVersion<=23(android 6),才支持v1簽名。注:
只有v2簽名的apk,包體中不含簽名文件。只有v1簽名或v1+v2簽名的apk,包體中有簽名文件CERT.SF、Cert.RSA
三、關于簽名的開發(fā)
1、多渠道
- v1:在zip的EOCD數(shù)據(jù)塊加入渠道信息
- v2:walle
2、共享UID功能
// manifest
android:sharedUserId
API 級別 29 中已棄用此常量。
共享用戶 ID 會在軟件包管理器中導致具有不確定性的行為。因此,強烈建議您不要使用它,并且我們在未來的 Android 版本中會將其移除。相反,應用應使用適當?shù)耐ㄐ艡C制(例如服務和 content provider),在共享組件之間實現(xiàn)互操作性。請注意,現(xiàn)有應用無法移除此值,因為不支持不使用共享用戶 ID。這類應用應添加 android:sharedUserMaxSdkVersion="32",以免在新用戶安裝時使用共享用戶 ID。
將與其他應用共享的 Linux 用戶 ID 的名稱。 默認情況下,Android 會為每個應用分配其唯一用戶 ID。 不過,如果針對兩個或多個應用將此屬性設置為相同的值,則這些應用都將共享相同的 ID,前提是這些應用的證書集完全相同。具有相同用戶 ID 的應用可以訪問彼此的數(shù)據(jù),如果需要的話,還可以在同一進程中運行。
