本文是作者研究参考了多方资料,融汇整理并加入自己的理解和实践结论后所作
准备知识
在介绍签名机制前,需要首先了解一下消息摘要、签名文件、数字证书的知识。
消息摘要 - Message Digest
消息摘要(Message Digest),又称数字摘要(Digital Digest)或数字指纹(Finger Print)。简
单来说,消息摘要就是在消息数据上,执行一个单向的Hash函数,生成一个
固定长度
的Hash值,这个Hash值即是消息摘要。关于这个Hash函数,我们来看看维基百科上的定义:
https://en.wikipedia.org/wiki/Cryptographic_hash_function
A
cryptographic hash function
is a special class of
hash function
that has certain properties which make it suitable for use in
cryptography
. It is a mathematical
algorithm
that
maps
data of arbitrary size to a
bit string
of a fixed size (a
hash function
) which is designed to also be a
one-way function
, that is, a function which is
infeasible
to invert. The only way to recreate the input data from an ideal cryptographic hash function's output is to attempt a
brute-force search
of possible inputs to see if they produce a match, or use a
rainbow table
of matched hashes.
Bruce Schneier
has called one-way hash functions "the workhorses of modern cryptography".
[1]
The input data is often called the
message
, and the output (the
hash value
or
hash
) is often called the
message digest
or simply the
digest
.
The ideal cryptographic hash function has five main properties:
- it is deterministic so the same message always results in the same hash
- it is quick to compute the hash value for any given message
- it is infeasible to generate a message from its hash value except by trying all possible messages
- a small change to a message should change the hash value so extensively that the new hash value appears uncorrelated with the old hash value
- it is infeasible to find two different messages with the same hash value
Cryptographic hash functions have many
information-security
applications, notably in
digital signatures
,
message authentication codes
(MACs), and other forms of
authentication
. They can also be used as ordinary
hash functions
, to index data in
hash tables
, for
fingerprinting
, to detect duplicate data or uniquely identify files, and as
checksums
to detect accidental data corruption. Indeed, in information-security contexts, cryptographic hash values are sometimes called (
digital
)
fingerprints
,
checksums
, or just
hash values
, even though all these terms stand for more general functions with rather different properties and purposes.
A cryptographic hash function (specifically
SHA-1
) at work. A small change in the input (in the word "over") drastically changes the output (digest). This is the so-called
avalanche effect
.
上面提到的的加密Hash函数就是消息摘要算法。它有以下特征:
①无论输入的消息有多长,计算出来的消息摘要的
长度总是固定的
。例如应用MD5算法摘要的消息有128个
比特位
,用SHA-1算法摘要的消息最终有160比特位的输出,SHA-1的变体可以产生192比特位和256比特位的消息摘要。一般认为,摘要的最终输出越长,该摘要算法就越安全。
②消息摘要看起来是“随机的”。这些比特看上去是胡乱的杂凑在一起的。可以用大量的输入来检验其输出是否相同,一般,不同的输入会有不同的输出,而且输出的摘要消息可以通过
随机性
检验。但是,一个摘要并不是真正随机的,因为用相同的算法对相同的消息求两次摘要,其结果必然相同;而若是真正随机的,则无论如何都是无法重现的。因此消息摘要是“
伪随机的
”。
③消息摘要函数是
单向
函数,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息。当然,可以采用强力攻击的方法,即尝试每一个可能的信息,计算其摘要,看看是否与已有的摘要相同,如果这样做,最终肯定会恢复出摘要的消息。但实际上,要得到的信息可能是
无穷
个消息之一,所以这种强力攻击几乎是无效的。
④好的摘要算法,
没有人能从中找到“碰撞”
,虽然“碰撞”是肯定存在的(由于长明文生成短摘要的Hash必然会产生碰撞)。即对于给定的一个摘要,不可能找到一条信息使其摘要正好是给定的。或者说,无法找到两条消息,使它们的摘要相同。
正是由于以上特点,消息摘要算法被广泛应用在“数字签名”领域,作为对明文的摘要算法。
著名的消息摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的变体
。
数字签名 - Signature
数字签名
方案是一种以电子形式存储消息签名的方法。一个完整的数字签名方案应该由两部分组成:签名算法和验证算法。
在讲数字签名之前,我们先简单介绍几个相关知识点:“公钥密码体制”、“对称加密算法”、“非对称加密算法”。
公钥密码体制(public-key cryptography)
公钥密码体制分为三个部分,
公钥
、
私钥
、加密解密算法,它的加密解密过程如下:
- 加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。加密过程需要用到公钥。
- 解密:通过解密算法和私钥对密文进行解密,得到明文。解密过程需要用到解密算法和私钥。注意,由公钥加密的内容,只能由私钥进行解密,也就是说,由公钥加密的内容,如果不知道私钥,是无法解密的。
公钥密码体制的公钥和算法都是公开的(这是为什么叫公钥密码体制的原因),私钥是保密的。大家都以使用公钥进行加密,但是只有私钥的持有者才能解密。在实际的使用中,有需要的人会生成一对公钥和私钥,把公钥发布出去给别人使用,自己保留私钥。目前使用最广泛的公钥密码体制是RSA密码体制。
对称加密算法(symmetric key algorithms)
在
对称加密算法
中,加密和解密都是使用的同一个
密钥
。因此对称加密算法要保证安全性的话,
密钥
要做好保密,只能让使用的人知道,不能对外公开。
非对称加密算法(asymmetric key algorithms)
在
非对称加密算法
中,加密使用的
密钥
和解密使用的
密钥
是不相同的。前面所说的
公钥密码体制
就是一种非对称加密算法,他的
公钥
和是
私钥
是不能相同的,也就是说加密使用的
密钥
和解密使用的
密钥
不同,因此它是一个
非对称加密算法
。
RSA简介
RSA密码体制是一种公钥密码体制,
公钥
公开,
私钥
保密,它的加密解密算法是公开的。 由
公钥
加密的内容可以并且只能由
私钥
进行解密,而由
私钥
加密的内容可以并且只能由公钥进行
解密
。也就是说,
RSA的这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密
。
加密
:公钥加密,私钥解密的过程,称为“加密”。因为公钥是公开的,任何公钥持有者都可以将想要发送给私钥持有者的信息进行加密后发送,而这个信息只有私钥持有者才能解密。
签名
:私钥加密,公钥解密的过程,称为“签名”。它和加密有什么区别呢?因为公钥是公开的,所以任何持有公钥的人都能解密私钥加密过的密文,所以这个过程并不能保证消息的安全性,但是它却能保证消息来源的准确性和不可否认性,也就是说,如果使用公钥能正常解密某一个密文,那么就能证明这段密文一定是由私钥持有者发布的,而不是其他第三方发布的,并且私钥持有者不能否认他曾经发布过该消息。故此将该过程称为“签名”。
数字签名
事实上,
任何一个公钥密码体制都可以单独地作为一种数字签名方案使用。如RSA作为数字签名方案使用时,可以定义如下:
这种签名实际上就是用信源的
私钥
加密消息,加密后的消息即成了签体;而用对应的
公钥
进行验证,若公钥解密后的消息与原来的消息相同,则消息是完整的,否则消息不完整。它正好和公钥密码用于消息保密是相反的过程。因为只有信源才拥有自己地私钥,别人无法重新加密源消息,所以即使有人截获且更改了源消息,也无法重新生成签体,因为只有用信源的私钥才能形成正确地签体。同样信宿只要验证用信源的公钥解密的消息是否与明文消息相同,就可以知道消息是否被更改过,而且可以认证消息是否是确实来自意定的信源,还可以使信源不能否认曾经发送的消息。所以这样可以完成
数字签名
的功能。
但这种方案过于单纯,它仅可以保证消息的完整性,而无法确保消息的保密性。而且这种方案要对所有的消息进行加密操作,这在消息的长度比较大时,效率是非常低的,主要原因在于
公钥
体制的加解密过程的低效性。所以这种方案一般不可取。
几乎所有的
数字签名
方案都要和快速高效的摘要算法(Hash函数)一起使用,
当
公钥算法
与摘要算法结合起来使用时,便构成了一种有效地数字签名方案
。
这个过程是:
(1)用摘要算法对消息进行摘要。
(2)再把摘要值用信源的
私钥
加密。
通过以上两步得到的消息就是所谓的原始信息的数字签名,发送者需要将原始信息和数字签名一同发送给接收者。而接收者在接收到原始信息和数字签名后,通过以下3步验证消息的真伪:
(1)先把接收到的原始消息用同样的摘要算法摘要,形成“准签体”。
(2)对附加上的那段数字签名,使用预先得到的公钥解密。
(3)比较前两步所得到的两段消息是否一致。如果一致,则表明消息确实是期望的发送者发的,且内容没有被篡改过;相反,如果不一致,则表明传送的过程中一定出了问题,消息不可信。
这种方法使公钥加密只对消息摘要进行操作,因为一种摘要算法的摘要消息长度是固定的,而且都比较“短”(相对于消息而言),正好符合公钥加密的要求。这样效率得到了提高,而其安全性也并未因为使用摘要算法而减弱。
综上所述,
数字签名是非对称加密技术 + 消息摘要技术的结合
。
数字证书 - Certificate
通过数字签名技术,确实可以解决可靠通信的问题。一旦验签通过,接收者就能确信该消息是期望的发送者发送的,而发送者也不能否认曾经发送过该消息。大家有没有注意到,前面讲的数字签名方法,有一个前提,就是消息的接收者必须事先得到正确的公钥。如果一开始公钥就被别人篡改了,那坏人就会被你当成好人,而真正的消息发送者给你发的消息会被你视作无效的。而且,很多时候根本就不具备事先沟通公钥的信息通道。那么如何保证公钥的安全可信呢?这就要靠数字证书来解决了。
数字证书是一个经证书授权(Certificate Authentication)中心数字签名的包含公钥拥有者信息以及公钥的文件。
数字证书的格式普遍采用的是
X.509
V3国际标准,一个标准的X.509数字证书通常包含以下内容:
证书的发布机构(Issuer)
- 该证书是由哪个机构(CA中心)颁发的。
证书的有效期(Validity)
- 证书的有效期,或者说使用期限。过了该日期,证书就失效了。
证书所有人的公钥(Public-Key)
- 该证书所有人想要公布出去的公钥。
证书所有人的名称(Subject)
- 这个证书是发给谁的,或者说证书的所有者,一般是某个人或者某个公司名称、机构的名称、公司网站的网址等。
证书所使用的签名算法(Signature algorithm)
- 这个数字证书的数字签名所使用的加密算法,这样就可以使用证书发布机构的证书里面的公钥,根据这个算法对指纹进行解密。
证书发行者对证书的数字签名(Thumbprint)
- 也就是该数字证书的指纹,用于保证数字证书的完整性,确保证书没有被修改过。其原理就是在发布证书时,CA机构会根据签名算法(Signature algorithm)对整个证书计算其hash值(指纹)并和证书放在一起,使用者打开证书时,自己也根据签名算法计算一下证书的hash值(指纹),如果和证书中记录的指纹对的上,就说明证书没有被修改过。
可以看出,数字证书本身也用到了数字签名技术,只不过签名的内容是整个证书(里面包含了证书所有者的公钥以及其他一些内容)。与普通数字签名不同的是,数字证书的签名者不是随随便便一个普通机构,而是CA机构。这就好像你的大学毕业证书上签名的一般都是德高望重的校长一样。一般来说,这些CA机构的根证书已经在设备出厂前预先安装到了你的设备上了。所以,数字证书可以保证证书里的公钥确实是这个证书所有者的,或者证书可以用来确认对方的身份。可见,
数字证书主要是用来解决公钥的安全发放问题。
综上所述,总结一下,数字签名和签名验证的大体流程如下图所示:
Android签名机制
签名工具
Android应用的签名工具有两种:jarsigner和signapk。它们的签名算法没什么区别,主要是签名使用的文件不同。
jarsigner:jdk自带的签名工具,可以对jar进行签名。使用keystore文件进行签名。生成的签名文件默认使用keystore的别名命名。
signapk:Android sdk提供的专门用于Android应用的签名工具。使用pk8、x509.pem文件进行签名。其中pk8是私钥文件,x509.pem是含有公钥的文件。生成的签名文件统一使用“CERT”命名。
既然这两个工具都是给apk签名的,那么keystore文件和pk8,x509.pem他们之间是不是有什么联系呢?答案是肯定的,他们之间是可以转化的,这里就不再分析如何进行转化,网上的例子很多。
还有一个需要注意的知识点,如果我们查看一个keystore文件的内容,会发现里面包含有一个MD5和SHA1摘要,这个就是keystore文件中私钥的数据摘要,这个信息也是我们在申请很多开发平台账号时需要填入的信息。
签名过程
首先我们任意选取一个签名后的APK(SMSSDKSample-release.apk)解压:
在META-INF文件夹下有三个文件:MANIFEST.MF、CERT.SF、CERT.RSA。它们就是签名过程中生成的文件,姑且叫他们“签名三兄弟”吧,把它们搞清楚了,你就精通签名了。
1.MANIFEST.MF
该文件中保存的内容其实就是逐一遍历apk中的所有条目,如果是目录就跳过,如果是一个文件,就用SHA1(或者SHA256)消息摘要算法提取出该文件的摘要然后进行BASE64编码后,作为“SHA1-Digest”属性的值写入到MANIFEST.MF文件中的一个块中。该块有一个“Name”属性,其值就是该文件在apk包中的路径。
2.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编码
对于SHA1-Digest值的验证可以手动进行,将MANIFEST.MF中任意一个块的内容复制并保存在一个新的文档中,注意文末需要加两个换行(这是由signapk的源码决定的)
保存文件,然后对该文件计算其SHA1值后使用Base64编码:
得到的值就是CERT.SF
中相应条目的SHA1-Digest的值:
3.CERT.RSA
这里会把之前生成的 CERT.SF文件,用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。这里要注意的是,
Android APK中的CERT.RSA证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的,用户可以在本地机器自行生成这个自签名证书。Android 目前不对应用证书进行 CA 认证。
Tips:所谓自签名证书是指自己给自己颁发的证书,即公钥证书中Issuer(发布者)和Subject(所有者)是相同的。当然,APK也可以采用由CA颁发私钥证书进行签名。采用非自签名时,最终APK的公钥证书中就会包含证书链,并且会存在多余一个证书,证书间通过Issuer与Subject进行关联,Issuer负责对Subject进行认证。当安装APK时,系统只会用位于证书链中最底层的证书对APK进行校验,但并不会验证证书链的有效性。在Https通信中使用自签名证书时浏览器的显示效果:
这里我们看到的都是二进制文件,因为RSA文件加密了,所以我们需要用openssl命令才能查看其内容:
openssl pkcs7 -inform DER -in /Users/jackie/Downloads/apk签名机制/SMSSDKSample-release_new/original/META-INF/DEMOKEY_.RSA -text -noout -print_certs
关于这些信息,可以看下面这张图:
综上所述,一个完整的签名过程如下所示:
签名验证过程
签名验证是发生在apk的安装过程中,一共分为三步:
(1)检查apk中包含的所有文件,对应的摘要值与MANIFEST.MF文件中记录的值一致。
(2)使用证书文件(RSA文件)检验签名文件(SF文件)没有被修改过。
(3)使用签名文件(SF文件)检验MF文件没有被修改过。
综上所述,一个完整的签名验证过程如下所示:
为什么使用这样的签名流程呢?
我们假设一下,首先,如果你改变了apk包中的任何文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是验证失败,程序就不能成功安装。
其次,如果你对更改过的文件相应的算出新的摘要值,然后更改MANIFEST.MF文件里面对应的属性值,那么必定与CERT.SF文件中算出的摘要值不一样,照样验证失败。
最后,如果你还不死心,继续计算MANIFEST.MF的摘要值,相应的更改CERT.SF里面的值,那么数字签名值必定与CERT.RSA文件中记录的不一样,还是失败。
那么能不能继续伪造数字签名呢?不可能,因为没有数字证书对应的私钥。
APK Signature Scheme v2
在 Android 7.0 Nougat 中引入了全新的 APK Signature Scheme v2。
APK 签名方案 v2 是一种
全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保证。
原因
为什么谷歌要做这个事情呢?第一点毋庸置疑,肯定是处于安全性的考虑,之前的校验方式开发者可以在打包之后对apk做很多处理,第二为了性能考虑,安装校验的时候不需要再解压缩校验,从而提升安装速度。
v2带来了什么变化
由于在 v1 仅针对单个ZIP条目进行验证,因此,在 APK 签署后可进行许多修改 - 可以移动甚至重新压缩文件。事实上,编译过程中要用到的 zipalign 工具就是这么做的,它用于根据正确的字节限制调整 ZIP 条目,以改进运行时性能。而且我们也可以利用这个东西,在打包之后修改META-INF目录下面的内容,或者修改Zip的注释来实现多渠道的打包,在v1签名中都可以校验通过。
v2 签名将验证归档中的所有字节,而不是单个 ZIP 条目,因此,在签署后无法再运行 zipalign(必须在签名之前执行)。正因如此,现在,在编译过程中,Google将压缩、调整和签署合并成一步完成。
新的签名方案就是一种扩展的Zip格式
使用 APK 签名方案 v2 进行签名时,会在 APK 文件中插入一个 APK 签名分块,该分块位于“ZIP 中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2 签名和签名者身份信息会存储在 APK 签名方案 v2 分块中。
受完整性保护的内容
为了保护APK内容,整个APK(zip文件格式)被分为以下4个区块:
- ZIP 条目的内容(从偏移量 0 处开始一直到“APK 签名分块”的起始位置)
- APK 签名分块
- ZIP 中央目录
- ZIP 中央目录结尾
APK 签名方案 v2 负责保护第 1、3、4 部分的完整性,以及第 2 部分包含的“APK 签名方案 v2 分块”中的 signed data 分块的完整性。
第 1、3 和 4 部分的完整性通过其内容的一个或多个摘要来保护,这些摘要存储在 signed data 分块中,而这些分块则通过一个或多个签名来保护。
新旧签名方案的兼容
新的签名格式向后兼容,因此,使用这种新格式签名的 APK 可在更低版本的 Android 设备上进行安装(会直接忽略添加到 APK 的额外数据),但前提是这些 APK 还带有 v1 签名。
验证程序会对照存储在“APK 签名分块”中的 v2 签名对 APK 的全文件哈希进行验证。该哈希涵盖除“APK 签名分块”(其中包含 v2 签名)之外的所有内容。在“APK 签名分块”以外对 APK 进行的任何修改都会使 APK 的 v2 签名作废。v2 签名被删除的 APK 也会被拒绝,因为 v1 签名指明相应 APK 带有 v2 签名,所以 Android Nougat 及更高版本会拒绝使用 v1 签名验证 APK。这就是所谓的“防回滚保护”。
Tips:攻击者可能会试图在支持对带 v2 签名的 APK 进行验证的 Android 平台上将带 v2 签名的 APK 作为带 v1 签名的 APK 进行验证。为了防范此类攻击,带 v2 签名的 APK 如果还带 v1 签名,其 META-INF/*.SF 文件的主要部分中必须包含 X-Android-APK-Signed 属性。该属性的值是一组以英文逗号分隔的 APK 签名方案 ID(v2 方案的 ID 为 2)。在验证 v1 签名时,对于此组中验证程序首选的 APK 签名方案(例如,v2 方案),如果 APK 没有相应的签名,APK 验证程序必须要拒绝这些 APK。此项保护依赖于内容 META-INF/*.SF 文件受 v1 签名保护这一事实。
新签名方案v2对现存的渠道包生成工具的影响
之前的渠道包生成方案是通过在META-INF目录下添加空文件,用空文件的名称来作为渠道的唯一标识。但在新的应用签名方案下META-INF已经被列入了保护区了,向META-INF添加空文件的方案会对区块1、3、4都会有影响。
另外一种比较流行的渠道包快速生成方案(修改Zip的注释)也因为上述原因,无法在新的应用签名方案下进行正常工作。
如果新签名方案v2后续改成强制要求,那现有的生成渠道包的方式就会无法工作,难道要退回到解放前吗?
从上面的描述可以看到,区块1、3、4都是被保护的,任何针对这3个区块的修改都会被新签名方案v2检测到,但区块2(APK Signing Block)是不受签名校验规则保护的。美团的新一代打渠道包工具Walle就是往区块2写入一个自定义的ID-value,在App运行阶段,可以通过ZIP的EOCD(End of central directory)、Central directory等结构中的信息(涉及ZIP格式的相关知识,这里不做展开描述)找到我们自己添加的ID-value,从而实现获取渠道信息的功能,来应对新签名方案v2的。
知识点梳理
1.公钥密码体制:
(1)公钥密码体制的公钥和算法都是公开的,私钥是保密的。一方加密的数据只能由另一方解密。
(2)公钥加密、私钥解密,称为“加密”;私钥加密、公钥解密,称为“签名”。公钥密码体制是目前唯一同时具备了加密与签名功能的密码体制。
2.消息摘要、数字签名、数字证书的含义:
(1)消息摘要又称数字指纹,就是对一个消息做SHA/MD5算法,这个值是唯一的;
(2)一个高效的数字签名技术 = 消息摘要技术 + 非对称加密技术(RSA算法)
(3)数字证书中包含了证书持有者的信息、持有者的公钥、证书签发机构(CA)的信息、CA机构对证书本身的签名信息以及其他一些信息,主要用于解决公钥的安全发放问题
(4)在Android签名之后,其中SF就是签名文件,RSA就是证书文件,我们可以用openssl来查看RSA文件中的证书信息和公钥信息
(5)Android APK中的CERT.RSA证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的
3.Android中的签名有两种方式:jarsigner和signapk。这两种方式的区别是:
(1)jarsigner签名时,需要的是keystore文件,而signapk签名的时候是pk8,x509.pem文件
(2)jarsigner签名之后的SF和RSA文件名默认是keystore的别名,而signapk签名之后文件名是固定的:CERT
(3)keystore文件和pk8,x509.pem文件之间可以互相转化
4.新签名方案v2:
(1)v2是一种全文件签名方案,对整个zip文件(包括zip元数据)进行签名
(2)v2方案下,zipalign需要在签名之前执行
(3)v2的签名工具-apksigner,位于sdk的build-tools目录下,但由于v2是Android7.0之后才推出的,所以只有版本>25的sdk中才能找到apksigner.jar
(4)为了兼容7.0以下设备,需要同时使用v1和v2签名。此时,在7.0及以上设备中只会验证v2签名,如果试图删除v2签名保留v1签名,系统同样会验证不通过,即“防回滚保护”;而在7.0以下设备中,则只会验证v1签名