家长签名,领导签名,合同签名,这些签名实际上都是在说明一件事:签名人通过签署自己名字的方式,向公众表示他们已经审核过这个文档,并对文档内容负责。
 在计算机领域,也有这样一套签名机制,称之为数字签名,或电子签章。它的作用也是如此:对一段信息进行签署,对这段信息的完整性、正确性负责。最初了解这个概念是在使用 Https 的时候。Android 的打包过程肯定也遇到了,但是并没有过多留意,直到有一天,需要对 apk 进行系统签名。接下来,就讨论下什么是签名,以及,它是怎么应用到 Android 应用中的。
什么是签名
 签名,与现实中类似,是对我们的信息完整性进行认证,确认我们的信息在传输过程中没有被篡改,即使被篡改了也能被发现。从计算机实现的角度来说,对内容加密应该是一种最快捷便利的方式,而签名正是利用了 非对称加密技术 (只能用公钥解密私钥加密过的内容,反之亦然)实现了这一点。用可靠私钥对需要判断信息完整性的内容进行加密,加密后的结果就是签名的本质。在需要验证的时候,使用公钥对私钥加密过的内容解密。私钥由创建者保存,并保证它的安全,而公钥是公开的。私钥加密过的内容,只能由相应的公钥解密,这样就保证了这段信息的完整性。
 接下来我们来看下经典的 Alice 和 Bolb 通信实例(实例来源于 NetPayClient用户手册):
- Alice 准备了一份合同 M;
- Alice 用摘要算法计算出该合同 M 的消息摘要 MD;
- Alice 用非对称算法和自己的私钥对合同消息摘要 MD 进行加密,该密文 S 就是合同的数字签名;
- Alice 将合同 M 和合同的数字签名 S 合并在一起,通过网络传送到合同的接受者 Bob;
- Bob 收到 Alice 的合同 M 及合同的数字签名 S;
- Bob 用 Alice 的公钥对合同签名 S 进行解密,得到 Alice 计算的合同摘要 MD;
- Bob 采用相同摘要算法对收到的合同重新计算消息摘要 MD’;
- Bob 比较 MD 与 MD’ 是否相等?
- 如结果相等,根据摘要算法的特性表明合同在传输过程中未被篡改。同时由于非对称加密算法的特性可以断定合同确实是 Alice 发送的,因为用 Alice 公钥能解密成功的数据只有 Alice 用她自己私钥对其进行加密才能产生,而她的私钥其它人是无法获取的。
 M 和 S 的合并,在进行通信传输的时候实际上就构成了一个证书。这里,其实也已经透露出了证书的本质,即 *** 信息 + 公钥 + 与公钥所对应的私钥对信息加密过后形成的签名。***
在通信过程中使用签名
 上面的实例中,有一个非常重要的安全问题没有解决,那就是万一有中间人 Doug 截获了信息,然后使用自己的私钥对信息进行签名,并把 Bob 保留的 Alice 的公钥换成了自己的公钥,那么,后来 Alice 发过来的信息无法通过验证,只能由 Doug 才能和 Bob 保持通信。
 为了解决这样重大的安全隐患,需要有一个第三方的可信机构站出来,对我们发过去的公钥做一个认证,这就是 CA 出现的起因。接下来,有几个概念我们需要澄清。
CA(certification authority (CA) ,an entity that issues digital certificates): 数字证书认证机构.也称为电子商务认证中心、电子商务认证授权机构,是负责发放和管理数字证书的权威机构,并作为电子商务交易中受信任的第三方,承担公钥体系中公钥的合法性检验的责任。
 实际上,这就是具有公信力、值得大众信任的第三方。只要是它所签发的证书,大家都认为是安全的。厂商预先在客户端植入这些 CA 的根证书,根证书中包含了它的公钥。后面需要验证其他证书的可靠性时,只需要使用这个公钥解密出所对应的私钥加密过的签名,对信息完整性做一个校验,就可以知道要验证的证书是否可信。所以说,根证书是信任链的起点。
根证书 : 在信任链中作为信任锚的起点角色 在密码学和计算机安全领域,根证书(root certificate)是属于根证书颁发机构(CA)的公钥证书,是在公开密钥基础建设中,信任链的起点.
 根证书,是信任链的起点。我们申请的证书,经过根证书的签名,才是可信任的。这时,根证书的安全性就变得非常重要。所以,在信任链设计中,绝大部分的根证书都不会直接为客户签名,而是先签名一个(或多个)中继证书,再由中继证书为客户签名,这可以加强控管能力及控制一旦签名私钥被泄时的损失。
非对称加密 : 是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密的时候,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。
 非对称加密是技术基础。
 接下来,我们可以来看下证书的定义格式:
1 | Certificate ::= SEQUENCE { |
 以上是一种 ASN 语法描述,表示的是证书中包括了:要被签名的证书(tbs = to be signed),签名算法,签名。其中,
1 | TBSCertificate ::= SEQUENCE { |
 要被签名的证书中,包含了它自己的公钥。而且,tbsCertificate 的信息内容,将作为签名函数的输入,生成 signatureValue 。让我们来看一个实例:
1 | //解压一个 apk 包,在 META-INF 夹下运行下面的命令,当然,首先你要安装 openssl 才行。 |
 目前,证书的格式和验证方法普遍遵循 X.509 国际标准。
Https 中的签名
 通过这种非对称加密的方式完成了双方对于后面需要使用的对称加密的密钥的协商(非对称加密太消耗资源,所以 https 同时使用了对称加密和非对称加密的方式)。这个协商过程是这样的(图片来源:图解HTTP):
 接下来我们看下实例,由 GlobalSign 颁发给维基百科的证书 :
 顺便说一句,实际上我们可以自己给自己颁发证书。但问题在于浏览器等客户端根本不认你的证书,不具有权威性,它被标记你的网站为不安全的。
1 | //1. 生成私钥 |
 以上过程,我们就是用自制的 CA 证书,给自己颁发了一个证书。 CA 证书的意义在于像公证处一样,公证后的信息对所有人具有可靠性;但是如果是自制的,就失去了这样的意义。
 颁发信息是自己在创建时随便填的,邮箱信息为 noreplay@gmail.com. 我们看到,此处的颁发者和使用者是同一个实体。这个是我们自建的 CA 证书,显然并没有通过 CA 认证。
 这里可以看到,这个证书是通过上面那个 ca.cert 证书认证过的,邮箱为 clientNoReplay@gmail.com,信息也是随便填写的,然而,ca.cert 并没有经过认证,所以,这个 server.crt 也是不受信任的。在浏览器中使用,会被标记为不安全:
 回到 Android 中,Android 要求所有 APK 必须先使用证书(包含公钥)进行数字签名,然后才能安装。通过这种手段,确保应用的所有权,因为证书中公钥相对应的私钥在应用的开发者手中。
为 APK 签名
 APK 的签名,和上面通信过程对签名的使用有些不同。打包所需要的证书并不需要 CA 签署,而是一个自签名的证书。对 APK 的签名,是为了验证:我们是 APK 的拥有者,并可以对 APK 进行后续升级管理,这样自签名证书就足够了。
 签名时,有两种工具可以选择,一种是 java 提供的 jarsigner;另外一种,是 Andoroid 专用的 apksigner 。无论使用哪种方式,都要首先生成私钥:
1 | keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias |
 现在我们通过 Java 的 keytool 工具生成了别名为 my-alias 的 PrivateKeyEntry ,并把它存储在 my-release-key.jks 文件中。在 PrivateKeyEntry 包含了一个私钥以及相对应的证书链(我们自己打包时,证书链长度通常为1,我们可以通过证书链获取私钥所对应的公钥)。可以通过以下命令查看:
1 | C:\Users\YueGs\Desktop\test\t>keytool -list -keystore my-release-key.jks |
 下面,我们可以通过 jarsinger 来签名了:
1 | // signed.apk 签名后的apk;unsigned.apk ,用来签名的 apk; |
 在没有打包签名之前,apk 包里是没有 META-INF 文件夹的,而在打包过后,多了这个文件夹,里面包含了验证文件完整性的文件。同样,如果里面已经有了这个文件夹,即已经进行过签名,是无法重复签名的。
 用 apksigner 进行签名,其实也就是一句话的事:
1 | apksigner sign --ks my-release-key.jks --out signed.apk unsigned.apk |
 注意,apksigner 这个命令是在 “sdk\build-tools\xxx” (xxx 为 android 版本号)文件夹下面。签名之后,META-INF 文件夹中会出现文件 MY-ALIAS.RSA。我们比较一下该文件和 my-release-key.jks 中的信息:
 下面,我们对两种工具做个比较。
 jarsiger 是用 JAR 文件设计的,所以它对 APK 文件结构一无所知,它只会把 APK 当做一个 JAR 文件(原因: apk 实际上是个 zip 压缩包,而 zip 压缩包可以被认为是一个 jar 包)。而 apksigner 是专门为 APK 文件设计的。比如,jarsigner 不知道怎样生成在 Android Nougat 中引入的 Scheme v2 类型的签名,但是 apksigner 可以这么做。jarsigner 对于需要在 API 17 或者以下运行时,不能使用 sha-256 的数字签名,但是 apksigner 知道。
 上面已经提到,当前的签名有两种 Scheme,分别是 v1 和 v2 。这里再做一个简单的介绍。在 Android 7.0 中,可以根据 APK 签名方案 v2(v2 方案)或 JAR 签名(v1 方案)验证 APK。更低版本的平台会忽略 v2 签名,仅验证 v1 签名。v1 和 v2 的区别,根据 **官网 **的说法,大概就是 v2 能够提供对整个 apk 签名(v1 对于 metainf 文件夹下的文件是没有验证的),而且 v2 更加验证过程更快(v1 需要解压 apk 逐条验证)。
小结
 这篇文章写的有点久,主要是这一块平时接触的也比较少,所以需要查的资料也比较多。原本想写 Android 中的签名验证机制,后面才发现光是签名部分能说的都太多了。
 签名的底层,是非对称加密,密码学的范畴,非专业人士就不展开了。信任链那部分,我觉得还是挺有意思的。如果 A 是绝对可信的,那么 A 相信并签署的 B 也是可信的,那么 B 相信并签署的 C 也是可信的,以此类推。我们的证书被 Z 相信并签署,因此,也在这条信任链中,也是可信的。
参考
netpayClient.doc
签署您的应用
Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
what-is-the-difference-between-jar-signer-and-apk-signer
https://stackoverflow.com/questions/35706687/why-jarsigner-can-sign-android-apk
APK 验证