Android 签名

前序

1、概念梳理

1.1 消息摘要(Message Digest)

消息摘要又称数字摘要(Digital Digest)或数字指纹(Finger Print)。对不同长度的数据通过算法得到一个固定的长度输入,常用的算法有MD5、SHA1、SHA256。

1.2 数字签名

数字签名的作用就是保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。数字签名技术是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。

1.3 数字证书

数字证书是一个经证书授权(Certificate Authentication)中心数字签名的包含公钥拥有者信息以及公钥的文件。数字证书的格式普遍采用的是 X.509 V3 国际标准,一个标准的 X.509 数字证书通常包含以下内容:

  1. 证书的发布机构(Issuer):该证书是由哪个机构(CA 中心)颁发的。
  2. 证书的有效期(Validity):证书的有效期,或者说使用期限。过了该日期,证书就失效了。
  3. 证书所有人的公钥(Public-Key):该证书所有人想要公布出去的公钥。
  4. 证书所有人的名称(Subject):这个证书是发给谁的,或者说证书的所有者,一般是某个人或者某个公司名称、机构的名称、公司网站的网址等。
  5. 证书所使用的签名算法(Signature algorithm):这个数字证书的数字签名所使用的加密算法,这样就可以使用证书发布机构的证书里面的公钥,根据这个算法对指纹进行解密。
  6. 证书发行者对证书的数字签名(Thumbprint):数字证书的hash 值(指纹),用于保证数字证书的完整性,确保证书没有被修改过。

数字证书的原理就是在证书发布时,CA 机构会根据签名算法(Signature algorithm)对整个证书计算其 hash 值(指纹)并和证书放在一起,使用者打开证书时,自己也根据签名算法计算一下证书的 hash 值(指纹),如果和证书中记录的指纹对的上,就说明证书没有被修改过。

数字证书本身也用到了数字签名技术,只不过签名的内容是整个证书(里面包含了证书所有者的公钥以及其他一些内容)。与普通数字签名不同的是,数字证书的签名者不是随随便便一个普通机构,而是 CA 机构。

1.4 JKS格式

JKS 是 java 用来存储密钥的容器。可以同时容纳n个公钥或私钥,后缀一般是 .jks或者.keystore或 .truststore等。

  • 在Java 8之前,这些文件的默认格式为JKS(android .keystore 也是jsk格式的证书)。
  • 从Java 9开始,默认的密钥库格式为PKCS12。
  • Android签名keystore文件也是jks格式,且1.8之后要求转换到p12格式。
  • JKS是二进制格式,同时包含证书和私钥,一般有密码保护,只能存储非对称密钥对(私钥 + x509公钥证书)。
  • 当应用程序需要通过SSL / TLS进行通信时,在大多数情况下将使用java keystore和java truststore。
  • 密钥库和私钥用不同的密码进行保护
  • JKS和PKCS12之间的最大区别是JKS是Java专用的格式,而PKCS12是存储加密的私钥和证书的标准化且与语言无关的方式。
1.5 PKCS#12 / PFX 格式
  • PKCS#12 是公钥加密标准,通用格式(rsa公司标准)。规定了可包含所有私钥、公钥和证书。文件格式是加密过的。
  • PKCS#12 或 PFX 格式是其以二进制格式存储,也称为 PFX 文件,在windows中可以直接导入到密钥区。也可用于导入和导出证书和私钥。
  • PKCS#12 由 PFX 进化而来的,用于交换公共的和私有的对象的标准格式。
  • 文件通常具有扩展名,例如.pkcs12 .pfx .p12。
  • 密钥库和私钥用相同密码进行保护

一、签名方式

1、v1 签名

1.1 签名内容

V1 方案基于签名的 JAR ,解压v1签名的apk包,解压后会有 META-INF 文件夹。其中有 MANIFEST.MF、CERT.SF、CERT.RSA 三个文件,它们是签名过程中生成的文件。

apk解压内容

三个文件作用如下:

  • MANIFEST.MF

该文件中存放的是对apk 中每一个文件使用SHA1或者SHA256消息摘要算法获得摘要,并使用base64进行编码后的内容。Name 对应的是APK中文件路径,SHA-256-Digest 对应的是摘要内容。

MANIFEST.MF
  • 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.SF
  • CERT.RSA

把生成的 CERT.SF 文件用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。Android APK 中的 CERT.RSA 证书是自签名的,并不需要第三方权威机构发布或者认证的证书,用户可以在本地机器自行生成这个自签名证书。Android 目前不对应用证书进行 CA 认证。

CERT.RSA

查看 .RSA 文件内容

pkcs7 -inform DER -in xx/META-INF/CERT.RSA -text -noout -print_certs
1.2 签名流程
  1. 计算每一个原始文件的 SHA-1 摘要,写入到MANIFEST.MF中;

  2. 计算整个MANIFEST.MF文件的 SHA-1 摘要,写入到CERT.SF中;

  3. 计算MANIFEST.MF中,每一块的SHA-1 摘要,写入到CERT.SF中;

  4. 计算整个CERT.SF文件的摘要,使用开发者私钥计算出摘要的签名;

  5. 将签名和开发者证书(X.509)写入CERT.RSA


    签名流程
1.3 签名校验

签名验证是发生在 APK 的安装过程中,一共分为三步:

  1. 检查 APK 中包含的所有文件,对应的摘要值与 MANIFEST.MF 文件中记录的值一致。
  2. 使用证书文件(RSA 文件)检验签名文件(SF 文件)没有被修改过。
  3. 使用签名文件(SF 文件)检验 MF 文件没有被修改过。
1.4 总结
  1. V1 需要对 apk 中的每个文件都计算摘要并验证,如果文件很多,校验时间会很长。
  2. V1 签名只会校验 Zip 文件中的部分文件,META-INFO 文件夹就不会参与校验。

2、v2 签名

APK Signature Scheme v2 是一个完整的文件签名方案,它通过检测 APK 受保护部分的任何更改来提高验证速度并加强完整性保证。Android 7 引入、Android 11 强制。

使用 APK 签名方案 v2 进行签名,会将 APK 签名块插入到 APK 文件中,紧挨在 ZIP 中央目录部分之前。在 APK 签名块内,v2 签名和签名者身份信息存储在APK 签名方案 v2 块中。

image.png

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 的格式如下(所有数字字段都是 little-endian):

  • size of block(以字节为单位)(不包括此字段)(uint64)
  • uint64-length-prefixed ID-value 对的序列:
    -- ID (uint32)
    -- value (可变长度:对的长度 - 4 个字节)
    -- size of block(以字节为单位):——与第一个字段(uint64)相同
    -- magic: “APK Sig Block 42”(16 字节)

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 会使用提供的列表中支持的签名进行验证。具有未知签名算法的签名将被忽略。当遇到多个支持的签名时,由每个实现选择使用哪个签名。这使得将来能够以向后兼容的方式引入更强大的签名方法。建议的方法是验证最强的签名。

格式

APK 签名方案 v2 块存储在 ID 0x7109871a下的 APK 签名块中。

APK Signature Scheme v2 Block的格式如下(所有数值均为little-endian,所有长度前缀字段使用uint32表示长度):

  • 长度前缀signer的长度前缀序列:
    • 以长度为前缀的有signed data
      • 长度前缀digests的长度前缀序列:
        • signature algorithm ID (uint32)
        • (长度前缀) digest
      • X.509 certificates的长度前缀序列:
        • 以长度为前缀的 X.509 certificate (ASN.1 DER 形式)
      • 以长度为前缀的additional attributes的长度前缀序列:
        • ID (uint32)
        • value (可变长度:附加属性的长度 - 4 个字节)
    • 长度前缀signatures的长度前缀序列:
      • signature algorithm ID (uint32)
      • 签名signed data上的长度前缀signature
    • 长度前缀public key (SubjectPublicKeyInfo,ASN.1 DER 形式)

签名算法 ID
0x0101—带有 SHA2-256 摘要、SHA2-256 MGF1、32 字节盐的 RSASSA-PSS,预告片:0xbc
0x0102—带有 SHA2-512 摘要、SHA2-512 MGF1、64 字节盐的 RSASSA-PSS,预告片:0xbc
0x0103—带有 SHA2-256 摘要的 RSASSA-PKCS1-v1_5。这适用于需要确定性签名的构建系统。
0x0104—带有 SHA2-512 摘要的 RSASSA-PKCS1-v1_5。这适用于需要确定性签名的构建系统。
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
  • 动态搜索广告:1024、2048、3072

完整性保护的内容
为了保护 APK 内容,一个 APK 包含四个部分:

  1. ZIP 条目的内容(从偏移量 0 到 APK 签名块的开始)
  2. APK 签名块
  3. ZIP 中央目录
  4. 中央目录的 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字节)
        • digest (长度前缀)
      • X.509 certificates的长度前缀序列:
        • 以长度为前缀的 X.509 certificate (ASN.1 DER 形式)
      • minSDK (uint32) - 如果平台版本低于此数字,则应忽略此签名者。
      • maxSDK (uint32) - 如果平台版本高于此数字,则应忽略此签名者。
      • 以长度为前缀的additional attributes的长度前缀序列:
        • ID (uint32)
        • value (可变长度:附加属性的长度 - 4 个字节)
        • ID - **0x3ba06f8c**
        • value -旋转证明结构
    • minSDK (uint32) - 签名数据部分中 minSDK 值的副本 - 如果当前平台不在范围内,则用于跳过此签名的验证。必须匹配带符号的数据值。
    • maxSDK (uint32) - 签名数据部分中 maxSDK 值的副本 - 如果当前平台不在范围内,则用于跳过此签名的验证。必须匹配带符号的数据值。
    • 长度前缀signatures的长度前缀序列:
      • signature algorithm ID (uint32)
      • 签名signed data上的长度前缀signature
    • 长度前缀public key (SubjectPublicKeyInfo,ASN.1 DER 形式)
3.3 旋转证明和自信任旧证书结构

轮换证明结构允许应用轮换其签名证书,而不会被与其通信的其他应用程序阻止。为此,应用签名包含两条新数据:

  • 向第三方断言应用程序的签名证书可以在其前身受信任的任何地方被信任
  • 应用程序本身仍然信任的应用程序的旧签名证书

签名数据部分中的旋转证明属性由一个单链表组成,每个节点都包含一个签名证书,用于对应用程序的先前版本进行签名。此属性旨在包含概念性的旋转证明和自信任旧证书数据结构。该列表按版本排序,其中最早的签名证书对应于根节点。旋转证明数据结构是通过让每个节点中的证书签署列表中的下一个证书来构建的,从而为每个新密钥注入证据,证明它应该与旧密钥一样受信任。

self-trusted-old-certs 数据结构是通过向每个节点添加标志来构建的,这些标志指示其在集合中的成员资格和属性。例如,可能存在一个标志,指示给定节点上的签名证书是可信的,以获得 Android 签名权限。此标志允许由旧证书签名的其他应用程序仍被授予由使用新签名证书签名的应用程序定义的签名权限。因为整个循环证明属性驻留在 v3 signer字段的已签名数据部分中,所以它受到用于签署包含 apk 的密钥的保护。

这种格式排除了多个签名密钥和不同祖先签名证书聚合为一个(多个起始节点到一个公共接收器)。

格式

旋转证明存储在 ID 0x3ba06f8c下的 APK 签名方案 v3 块中。它的格式是:

  • 长度前缀levels的长度前缀序列:
    • 以长度为前缀的signed data (由先前的证书 - 如果存在)
      • 以长度为前缀的 X.509 certificate (ASN.1 DER 形式)
      • signature algorithm ID (uint32) - 证书在上一级使用的算法
    • flags (uint32) - 指示此证书是否应位于 self-trusted-old-certs 结构中以及用于哪些操作的标志。
    • signature algorithm ID (uint32) - 必须与下一级签名数据部分中的 ID 匹配。
    • 上述signed data的长度前缀signature
3.4 多个证书

Android 当前将使用多个证书签名的 APK 视为具有与组成证书分开的唯一签名身份。因此,签名数据部分中的旋转证明属性形成了一个有向无环图,可以更好地将其视为一个单链表,给定版本的每一组签名者代表一个节点。这为旋转证明结构(下面的多签名者版本)增加了额外的复杂性。特别是,排序成为一个问题。更重要的是,不再能够独立签署 APK,因为旋转证明结构必须让旧的签名证书签署新的证书集,而不是一个接一个地签署它们。例如,由密钥 A 签名的 APK 希望由两个新密钥 B 和 C 签名,B 签名者不能只包含 A 或 B 的签名,因为这与 B 和 C 的签名身份不同。这将意味着签名者必须在构建这样的结构之前进行协调。

3.5 多签名者旋转证明属性
  • 长度前缀sets的长度前缀序列:
    • signed data (由前一组 - 如果存在)
      • 以长度为前缀的certificates序列
        • 以长度为前缀的 X.509 certificate (ASN.1 DER 形式)
      • signature algorithm IDs序列 (uint32) - 一个用于前一组中的每个证书,顺序相同。
    • flags (uint32) - 指示这组证书是否应该在 self-trusted-old-certs 结构中以及针对哪些操作的标志。
    • 长度前缀signatures的长度前缀序列:
      • signature algorithm ID (uint32) - 必须与签名数据部分中的匹配
      • 上述signed data的长度前缀signature
3.6 旋转证明结构中的多个祖先

v3 方案也不处理两个不同的密钥轮换到同一个应用程序的同一个签名密钥。这与收购的情况不同,收购公司希望移动被收购的应用程序以使用其签名密钥来共享权限。此次收购被视为受支持的用例,因为新应用程序将通过其包名称来区分,并且可能包含其自己的旋转证明结构。不支持的情况,即同一个应用程序有两条不同的路径来获得相同的证书,打破了密钥轮换设计中的许多假设。

3.7 确认

在 Android 9 及更高版本中,可以根据 APK 签名方案 v3、v2 方案或 v1 方案验证 APK。旧平台忽略 v3 签名并尝试验证 v2 签名,然后是 v1。

3.8 APK 签名方案 v3 验证
  1. 找到 APK 签名块并验证:
    1. APK 签名块的两个大小字段包含相同的值。
    2. ZIP Central Directory 后紧跟 ZIP End of Central Directory 记录。
    3. 中央目录的 ZIP 结尾后面没有更多数据。
  2. 在 APK 签名块内找到第一个 APK 签名方案 v3 块。如果存在 v3 块,请继续执行步骤 3。否则,回退到使用 v2 方案验证 APK。
  3. 对于具有当前平台范围内的最小和最大 SDK 版本的 APK 签名方案 v3 块中的每个signer
    1. signatures中选择支持的最强signature algorithm ID 。强度排序取决于每个实现/平台版本。
    2. 使用public key验证signatures signed data中的相应signature 。 (现在解析signed data是安全的。)
    3. 验证签名数据中的最小和最大 SDK 版本是否与为signer指定的版本匹配。
    4. 验证digestssignatures中签名算法 ID 的有序列表是否相同。 (这是为了防止签名剥离/添加。)
    5. 使用与签名算法使用的摘要算法相同的摘要算法计算 APK 内容的摘要。
    6. 验证计算出的摘要与来自digests的相应digest相同。
    7. 验证第一个certificatecertificates是否与public key相同。
    8. 如果signer的旋转证明属性存在,则验证该结构是有效的,并且此signer是列表中的最后一个证书。
  4. 如果在当前平台的范围内恰好找到一个signer ,并且该signer的步骤 3 成功,则验证成功。

注意:如果第 3 步或第 4 步发生故障,则不得使用 v1 或 v2 方案验证 APK。

4、v4 签名

Android 11 通过 APK 签名方案 v4 支持与流式传输兼容的签名方案。v4 签名基于根据 APK 的所有字节计算得出的 Merkle 哈希树。它完全遵循 fs-verity 哈希树的结构(例如,对盐进行零填充,以及对最后一个分块进行零填充。)Android 11 将签名存储在单独的 .apk.idsig 文件中。v4 签名需要 v2 或 v3 签名作为补充。

5、签名验证流程

签名验证流程

APK 的整个文件哈希根据存储在 APK 签名块中的 v2+ 签名进行验证。哈希涵盖了除包含 v2+ 签名的 APK 签名块之外的所有内容。在 APK 签名块之外对 APK 进行的任何修改都会使 APK 的 v2+ 签名无效。带有剥离 v2+ 签名的 APK 也会被拒绝,因为它们的 v1 签名指定 APK 是 v2 签名的,这使得 Android 7.0 和更新版本拒绝使用它们的 v1 签名验证 APK。

需要注意的是,对于覆盖安装的情况,签名校验只支持升级,而不支持降级。也就是说设备上安装了一个使用 v1 签名的 APK,可以使用 v2 签名的 APK 进行覆盖安装,反之则不允许。

5、签名方式对比
签名方式 引入版本 方案优势
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 

二、获取签名指纹

1、签名文件

keytool -list -v -keystore 
指纹

注意这里命令行输出有一行提示,提示 .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 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? {
        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 {
        val packageName = context.packageName
        val signatures = getSignatures(context, packageName)
        val list = mutableListOf()
        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 

App 支持的最低API级别太高minSdkVersion>=24(android 7),
只有minSdkVersion<=23(android 6),才支持v1签名。

注:
只有v2签名的apk,包体中不含签名文件。只有v1签名或v1+v2签名的apk,包体中有签名文件CERT.SF、Cert.RSA

无法同时使用签名方案v1和v2问题

三、关于签名的开发

1、多渠道

  • v1:在zip的EOCD数据块加入渠道信息
  • v2:walle

2、共享UID功能

// manifest
android:sharedUserId

API 级别 29 中已弃用此常量。
共享用户 ID 会在软件包管理器中导致具有不确定性的行为。因此,强烈建议您不要使用它,并且我们在未来的 Android 版本中会将其移除。相反,应用应使用适当的通信机制(例如服务和 content provider),在共享组件之间实现互操作性。请注意,现有应用无法移除此值,因为不支持不使用共享用户 ID。这类应用应添加 android:sharedUserMaxSdkVersion="32",以免在新用户安装时使用共享用户 ID。

将与其他应用共享的 Linux 用户 ID 的名称。 默认情况下,Android 会为每个应用分配其唯一用户 ID。 不过,如果针对两个或多个应用将此属性设置为相同的值,则这些应用都将共享相同的 ID,前提是这些应用的证书集完全相同。具有相同用户 ID 的应用可以访问彼此的数据,如果需要的话,还可以在同一进程中运行。

四、引用

  • 压缩包Zip格式详析
  • Android签名 jks 转 pkcs12
  • Android v1、v2、v3签名详解

你可能感兴趣的:(Android 签名)