证书体系

X509标准

X.509 - 这是一种证书标准,主要定义了证书中应该包含哪些内容,证书主要包括颁发者和被办法者的信息,以及被颁发者的公钥,和CA机构对这些信息的认证,

  1. 两种编码方式:
  • PEM - Privacy Enhanced Mail,打开看文本格式,以”—–BEGIN…”开头, “—–END…”结尾,内容是BASE64编码.

      Apache和*NIX服务器偏向于使用这种编码格式.
    
  • DER - Distinguished Encoding Rules,打开看是二进制格式,不可读.

    Java和Windows服务器偏向于使用这种编码格式.

  1. 各种文件拓展名:
  • CRT - CRT应该是certificate的三个字母,其实还是证书的意思,常见于*NIX系统,有可能是PEM编码,也有可能是DER编码,大多数应该是PEM编码
  • CER - 还是certificate,还是证书,常见于Windows系统,同样的,可能是PEM编码,也可能是DER编码,大多数应该是DER编码.
  • KEY - 通常用来存放一个公钥或者私钥,并非X.509证书,编码同样的,可能是PEM,也可能是DER.
  • CSR - Certificate Signing Request,即证书签名请求,这个并不是证书,而是向权威证书颁发机构获得签名证书的申请,其核心内容是一个公钥(当然还附带了一些别的信息),在生成这个申请的时候,同时也会生成一个私钥
  • PFX/P12 - predecessor of PKCS#12,对*nix服务器来说,一般CRT和KEY是分开存放在不同文件中的,但Windows的IIS则将它们存在一个PFX文件中,(因此这个文件包含了证书及私钥)这样会不会不安全?应该不会,PFX通常会有一个”提取密码”

总结起来:crt cer约等于x509证书,key保存公钥或私钥,csr是证书签名请求,pfx包含证书和私钥

PKCS标准

PKCS系列是 Public-Key Cryptography Standards ,是RSA制定的一系列的标准,注意前面的文件后缀,都不算是标准,只有X509和PKCS可以称为标准,
PKCS中经常使用的就是:PKCS1 PKCS8 PKCS12

  • PEM - Privacy Enhanced Mail,打开看文本格式,以”—–BEGIN…”开头, “—–END…”结尾,内容是BASE64编码.

查看PEM格式证书的信息:openssl x509 -in certificate.pem -text -noout
Apache和*NIX服务器偏向于使用这种编码格式.

  • DER - Distinguished Encoding Rules,打开看是二进制格式,不可读.

  • PKCS#1:定义RSA公开密钥算法加密和签名机制,主要用于组织PKCS#7中所描述的数字签名和数字信封。

  • PKCS#2:涉及了RSA的消息摘要加密,这已被并入PKCS#1中。

  • PKCS#3:Diffie-Hellman密钥协议标准。PKCS#3描述了一种实现Diffie- Hellman密钥协议的方法。

  • PKCS#4:最初是规定RSA密钥语法的,现已经被包含进PKCS#1中。

  • PKCS#5:基于口令的加密标准。PKCS#5描述了使用由口令生成的密钥来加密8位位组串并产生一个加密的8位位组串的方法。PKCS#5可以用于加密私钥,以便于密钥的安全传输(这在PKCS#8中描述)。

  • PKCS#6:扩展证书语法标准。PKCS#6定义了提供附加实体信息的X.509证书属性扩展的语法(当PKCS#6第一次发布时,X.509还不支持扩展。这些扩展因此被包括在X.509中)。

  • PKCS#7:密码消息语法标准。PKCS#7为使用密码算法的数据规定了通用语法,比如数字签名和数字信封。PKCS#7提供了许多格式选项,包括未加密或签名的格式化消息、已封装(加密)消息、已签名消息和既经过签名又经过加密的消息。

  • PKCS#8:描述私有密钥信息格式,该信息包括公开密钥算法的私有密钥以及可选的属性集等。注意pkcs8不只是能表示RSA,所以比PKCS1更具有通用性

  • PKCS#9:可选属性类型。PKCS#9定义了PKCS#6扩展证书、PKCS#7数字签名消息、PKCS#8私钥信息和PKCS#10证书签名请求中要用到的可选属性类型。已定义的证书属性包括E-mail地址、无格式姓名、内容类型、消息摘要、签名时间、签名副本(counter signature)、质询口令字和扩展证书属性。

  • PKCS#10:证书请求语法标准。PKCS#10定义了证书请求的语法。证书请求包含了一个唯一识别名、公钥和可选的一组属性,它们一起被请求证书的实体签名(证书管理协议中的PKIX证书请求消息就是一个PKCS#10)。

  • PKCS#11:密码令牌接口标准。PKCS#11或“Cryptoki”为拥有密码信息(如加密密钥和证书)和执行密码学函数的单用户设备定义了一个应用程序接口(API)。智能卡就是实现Cryptoki的典型设备。注意:Cryptoki定义了密码函数接口,但并未指明设备具体如何实现这些函数。而且Cryptoki只说明了密码接口,并未定义对设备来说可能有用的其他接口,如访问设备的文件系统接口。

  • PKCS#12:描述个人信息交换语法标准。描述了将用户公钥、私钥、证书和其他相关信息打包的语法。

  • PDCS#13:椭圆曲线密码标准。PKCS#13标准当前正在完善之中。它包括椭圆曲线参数的生成和验证、密钥生成和验证、数字签名和公钥加密,还有密钥协定,以及参数、密钥和方案标识的ASN.1语法。

  • PKCS#14:伪随机数产生标准。PKCS#14标准当前正在完善之中。为什么随机数生成也需要建立自己的标准呢?PKI中用到的许多基本的密码学函数,如密钥生成和Diffie-Hellman共享密钥协商,都需要使用随机数。然而,如果“随机数”不是随机的,而是取自一个可预测的取值集合,那么密码学函数就不再是绝对安全了,因为它的取值被限于一个缩小了的值域中。因此,安全伪随机数的生成对于PKI的安全极为关键。

  • PKCS#15:密码令牌信息语法标准。PKCS#15通过定义令牌上存储的密码对象的通用格式来增进密码令牌的互操作性。在实现PKCS#15的设备上存储的数据对于使用该设备的所有应用程序来说都是一样的,尽管实际上在内部实现时可能所用的格式不同。PKCS#15的实现扮演了翻译家的角色,它在卡的内部格式与应用程序支持的数据格式间进行转换.

总结:PKCS1,8,12都可以在某些情况下当作文件格式,PKCS1描述基础的密钥格式,PKCS8也描述密钥,但格式和1不同,PKCS12等价于PFX文件,包含证书和私钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 生成PKCS#1的公私钥
openssl genrsa -out pkcs1_private.pem 1024
openssl rsa -in pkcs1_private.pem -RSAPublicKey_out -out pkcs1_public.pem

查看私钥
openssl rsa -in rsa_private_key.pem -text -noout
查看公钥
openssl rsa -pubin -in rsa_public_key.pem -text

# 由PKCS#1的私钥,生成PKCS#8的公私钥
openssl pkcs8 -topk8 -inform PEM -in pkcs1_private.pem -outform PEM -nocrypt -out from_pkcs1_private_to_pkcs8_private.pem

openssl rsa -in pkcs1_private.pem -pubout -out from_pkcs1_private_to_pkcs8_public.pem

# 由PKCS#8的私钥,生成PKCS#1的公私钥
openssl rsa -in from_pkcs1_private_to_pkcs8_private.pem -out from_pkcs8_private_to_pkcs1_private.pem

openssl rsa -in from_pkcs1_private_to_pkcs8_private.pem -RSAPublicKey_out -out from_pkcs8_private_to_pkcs1_public.pem

# 由PKCS1公钥生成PKCS#8公钥:
openssl rsa -RSAPublicKey_in -in pkcs1_public.pem -pubout -out from_pkcs1_public_to_pkcs8_public.pem

# 由PKCS8公钥生成PKCS#1公钥:
openssl rsa -pubin -in from_pkcs1_private_to_pkcs8_public.pem -RSAPublicKey_out -out from_pkcs8_public_to_pkcs1_public.pem

产生证书请求 注意PKCS1 8都可以
openssl req -new -key private_key.pem -out rsaCerReq.csr

产生证书 注意PKCS1 8都可以
openssl x509 -req -days 3650 -in rsaCerReq.csr -signkey private_key.pem -out rsaCert.crt

从证书获得公钥:
openssl x509 -in rsaCert.crt -noout -pubkey > public_key.pem

生成PKCS12
openssl pkcs12 -export -inkey serverprikey.pem -in server.pem -password pass:"123456" -out server_nocret.pfx


从PKCS12获得证书和私钥
openssl pkcs12 -in server_nocret.pfx -nocerts -nodes -out alicekey.pem

openssl pkcs12 -in server_nocret.pfx -nokeys -out cert.pem

查看pkcs12内容 -nodes:因为私钥在在输出前会输出加密结果,所以需要nodes来保证不用打密码和不加密
openssl pkcs12 -in server_nocret.pfx -nocerts -nodes -out alicekey.pem

SSL常见证书

根据不同的服务器以及服务器的版本,我们需要用到不同的证书格式,就市面上主流的服务器来说,大概有以下格式:

  • .DER .CER,文件是二进制格式,只保存证书,不保存私钥。
  • .PEM,一般是文本格式,可保存证书,可保存私钥。
  • .CRT,可以是二进制格式,可以是文本格式,与 .DER 格式相同,不保存私钥。
  • .PFX .P12,二进制格式,同时包含证书和私钥,一般有密码保护。
  • .JKS,二进制格式,同时包含证书和私钥,一般有密码保护。

DER

该格式是二进制文件内容,Java 和 Windows 服务器偏向于使用这种编码格式。

OpenSSL 查看:

1
openssl x509 -in certificate.der -inform der -text -noout

转换为 PEM:

1
openssl x509 -in cert.crt -inform der -outform pem -out cert.pem

PEM

Privacy Enhanced Mail,一般为文本格式,以 -----BEGIN... 开头,以 -----END... 结尾。中间的内容是 BASE64 编码。这种格式可以保存证书和私钥,有时我们也把PEM 格式的私钥的后缀改为 .key 以区别证书与私钥。具体你可以看文件的内容。

这种格式常用于 Apache 和 Nginx 服务器。

OpenSSL 查看:

1
openssl x509 -in certificate.pem -text -noout

转换为 DER:

1
openssl x509 -in cert.crt -outform der -out cert.der

CRT

Certificate 的简称,有可能是 PEM 编码格式,也有可能是 DER 编码格式。如何查看请参考前两种格式。

PFX

Predecessor of PKCS#12,这种格式是二进制格式,且证书和私钥存在一个 PFX 文件中。一般用于 Windows 上的 IIS 服务器。改格式的文件一般会有一个密码用于保证私钥的安全。

OpenSSL 查看:

1
openssl pkcs12 -in key.pfx

转换为 PEM:

1
openssl pkcs12 -in key.pfx -out key.pem -nodes

JKS

Java Key Storage,很容易知道这是 JAVA 的专属格式,利用 JAVA 的一个叫 keytool的工具可以进行格式转换。一般用于 Tomcat 服务器。

在线格式转换

TOMCAT双向认证

  1. 使用keytool生成证书,成文keystore文件,ailas(别名)每个keystore都关联这一个独一无二的alias,这个alias通常不区分大小写

在keystore里,包含两种数据:

  • 密钥实体(Key entity)——密钥(secret key)又或者是私钥和配对公钥(采用非对称加密)
  • 可信任的证书实体(trusted certificate entries)——只包含公钥
1
keytool -genkey -v -alias tomcat -keyalg RSA -keystore tomcat.keystore -validity 36500
  1. 为客户端生成证书
1
keytool -genkey -v -alias mykey -keyalg RSA -storetype PKCS12 -keystore mykey.p12 
  1. 让服务器信任客户端证书

由于是双向SSL认证,服务器必须要信任客户端证书,因此,必须把客户端证书添加为服务器的信任认证。由于不能直接将PKCS12格式的证书库导入,必须先把客户端证书导出为一个单独的CER文件,使用如下命令:

1
keytool -export -alias mykey -keystore mykey.p12 -storetype PKCS12 -storepass 123456 -rfc -file mykey.cer   //PKCS#12转cer

将该cer文件导入到服务器的证书库,添加为一个信任证书。

1
keytool -import -v -file mykey.cer -keystore tomcat.keystore

通过list命令查看服务器的证书库,可以看到两个证书,一个是服务器证书,一个是受信任的客户端证书:

1
keytool -list -keystore tomcat.keystore
  1. 让客户端信任服务器证书

由于是双向SSL认证,客户端也要验证服务器证书,因此,必须把服务器证书添加到浏览的“受信任的根证书颁发机构”。由于不能直接将keystore格式的证书库导入,必须先把服务器证书导出为一个单独的CER文件,使用如下命令。

1
keytool -keystore tomcat.keystore -export -alias tomcat -file  tomcat.cer -validity 36500

5.配置Tomcat服务器

打开Tomcat根目录下的/conf/server.xml,找到Connector port=”8443″配置段,修改为如下:

1
2
3
4
5
6
7
8
9
<Connector port=”8443″ protocol=”org.apache.coyote.http11.Http11NioProtocol”

SSLEnabled=”true” maxThreads=”150″ scheme=”https”

secure=”true” clientAuth=”true” sslProtocol=”TLS”

keystoreFile=”D:\\home\\tomcat.keystore” keystorePass=”123456″

truststoreFile=”D:\\home\\tomcat.keystore” truststorePass=”123456″ />

属性说明:

1
2
3
4
5
6
7
8
9
clientAuth:设置是否双向验证,默认为false,设置为true代表双向验证

keystoreFile:服务器证书文件路径

keystorePass:服务器证书密码

truststoreFile:用来验证客户端证书的根证书,此例中就是服务器证书

truststorePass:根证书密码

应用程序HTTP自动跳转到HTTPS(未测试)

在应用程序中web.xml中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<security-constraint>

<web-resource-collection >

<web-resource-name >SSL</web-resource-name>

<url-pattern>/*</url-pattern>

</web-resource-collection>

<user-data-constraint>

<transport-guarantee>CONFIDENTIAL</transport-guarantee>

</user-data-constraint>

</security-constraint>

安卓签名

生成keystore文件

1
keytool -genkey -alias android.keystore -keyalg RSA -validity 36500 -keystore android.keystore

签名:

1
2
3
4
jarsigner -verbose -keystore xx.keystore  -storepass XXXXXX -signedjar android_sign.apk -digestalg SHA1 -sigalg MD5withRSA android.apk 别名

android_sign.apk为签名后生成的apk文件
android.apk为源apk文件

V1和V2签名

签名工具

    Android 应用的签名工具有两种:jarsigner 和 signAPK。它们的签名算法没什么区别,主要是签名使用的文件不同。
  • jarsigner:jdk 自带的签名工具,可以对 jar 进行签名。使用 keystore 文件进行签名。生成的签名文件默认使用 keystore 的别名命名。
  • signAPK:Android sdk 提供的专门用于 Android 应用的签名工具。使用 pk8、x509.pem 文件进行签名。其中 pk8 是私钥文件,x509.pem 是含有公钥的文件。生成的签名文件统一使用“CERT”命名。

v1签名

  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 编码

  1. CERT.RSA

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

    CERT.RSA文件:

签名过程
签名校验过程

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

  1. 检查 APK 中包含的所有文件,对应的摘要值与 MANIFEST.MF 文件中记录的值一致。

  2. 使用证书文件(RSA 文件)检验签名文件(SF 文件)没有被修改过。

  3. 使用签名文件(SF 文件)检验 MF 文件没有被修改过

V2签名

v1签名的劣势

从 Android 7.0 开始,Android 支持了一套全新的 V2 签名机制,通过前面的分析,可以发现 v1 签名有两个地方可以改进:

签名校验速度慢
校验过程中需要对apk中所有文件进行摘要计算,在 APK 资源很多、性能较差的机器上签名校验会花费较长时间,导致安装速度慢。

完整性保障不够
META-INF 目录用来存放签名,自然此目录本身是不计入签名校验过程的,可以随意在这个目录中添加文件,比如一些快速批量打包方案就选择在这个目录中添加渠道文件。

为了解决这两个问题,在 Android 7.0 Nougat 中引入了全新的 APK Signature Scheme v2。

v2与v1不同

由于在 v1 仅针对单个 ZIP 条目进行验证,因此,在 APK 签署后可进行许多修改 — 可以移动甚至重新压缩文件。事实上,编译过程中要用到的 ZIPalign 工具就是这么做的,它用于根据正确的字节限制调整 ZIP 条目,以改进运行时性能。而且我们也可以利用这个东西,在打包之后修改 META-INF 目录下面的内容,或者修改 ZIP 的注释来实现多渠道的打包,在 v1 签名中都可以校验通过。

v2 签名将验证归档中的所有字节,而不是单个 ZIP 条目,因此,在签署后无法再运行 ZIPalign(必须在签名之前执行)。正因如此,现在,在编译过程中,Google 将压缩、调整和签署合并成一步完成。

v2 签名模式

简单来说,v2 签名模式在原先 APK 块中增加了一个新的块(签名块),新的块存储了签名,摘要,签名算法,证书链,额外属性等信息,这个块有特定的格式。

v2签名过程

首先,说一下 APK 摘要计算规则,对于每个摘要算法,计算结果如下:

将 APK 中文件 ZIP 条目的内容、ZIP 中央目录、ZIP 中央目录结尾按照 1MB 大小分割成一些小块。
计算每个小块的数据摘要,数据内容是 0xa5 + 块字节长度 + 块内容。
计算整体的数据摘要,数据内容是 0x5a + 数据块的数量 + 每个数据块的摘要内容
总之,就是把 APK 按照 1M 大小分割,分别计算这些分段的摘要,最后把这些分段的摘要在进行计算得到最终的摘要也就是 APK 的摘要。然后将 APK 的摘要 + 数字证书 + 其他属性生成签名数据写入到 APK Signing Block 区块。

签名校验过程

其中 v2 签名机制是在 Android 7.0 以及以上版本才支持。因此对于 Android 7.0 以及以上版本,在安装过程中,如果发现有 v2 签名块,则必须走 v2 签名机制,不能绕过。否则降级走 v1 签名机制。
v1 和 v2 签名机制是可以同时存在的,其中对于 v1 和 v2 版本同时存在的时候,v1 版本的 META_INF 的 .SF 文件属性当中有一个 X-Android-APK-Signed 属性:

1
X-Android-APK-Signed: 2

因此如果想绕过 v2 走 v1 校验是不行的。

找到APK 签名分块并验证以下内容:
a. APK 签名分块的两个大小字段包含相同的值。
b. ZIP 中央目录结尾紧跟在ZIP 中央目录记录后面。
c. ZIP 中央目录结尾之后没有任何数据。

找到APK 签名分块中的第一个APK 签名方案 v2 分块。如果 v2 分块存在,则继续执行第 3 步。否则,回退至使用 v1 方案验证 APK。

APK 签名方案 v2 分块中的每个 signer 执行以下操作:
a. 从 signatures 中选择安全系数最高的受支持 signature algorithm ID。安全系数排序取决于各个实现/平台版本。
b. 使用 public key 并对照signed data 验证 signatures 中对应的 signature。(现在可以安全地解析 signed data了。)
c. 验证 digests 和 signatures 中的签名算法 ID 列表(有序列表)是否相同。(这是为了防止删除/添加签名。)
d. 使用签名算法所用的同一种摘要算法计算 APK 内容的摘要。
e. 验证计算出的摘要是否与 digests 中对应的 digest 相同。
f. 验证 certificates 中第一个 certificate 的 SubjectPublicKeyInfo 是否与 public key 相同。

如果找到了至少一个 signer,并且对于每个找到的 signer,第 3 步都取得了成功,APK 验证将会成功。

兼容机制

因为V2签名机制是在Android 7.0中引入的,为了使APK可在Android 7.0以下版本中安装,应先用JAR签名对APK进行签名,再用V2方案进行签名。要注意顺序一定是先JAR签名再V2签名,因为JAR签名需要修改zip数据区中央目录的内容,先使用V2签名再JAR签名会破坏V2签名的完整性。

实际上我们在编译APK时并不需要关心这个过程,在Android Plugin for Gradle 2.2中,gradle默认会同时使用JAR签名和V2方案对APK进行签名,如果想要关闭JAR签名或V2签名,可以在build.gradle中进行配置:

1
2
3
4
5
6
7
8
9
10
11
android {
...
defaultConfig { ... }
signingConfigs {
release {
...
// v1SigningEnabled false
v2SigningEnabled false
}
}
}

在 Android 7.0 中,会优先以 v2方案验证 APK,在Android 7.0以下版本中,系统会忽略 v2 签名,仅验证 v1 签名。Android 7.0+的校验过程如下

防回滚机制

因为在经过V2签名的APK中同时带有JAR签名,攻击者可能将APK的V2签名删除,使得Android系统只校验JAR签名。为防范此类攻击,V2方案规定:

V2签名的APK如果还带JAR签名,其 META-INF/.SF 文件的首部中必须包含 X-Android-APK-Signed 属性。该属性的值是一组以英文逗号分隔的 APK 签名方案 ID(v2 方案的 ID 为 2)。在验证 v1 签名时,对于此组中验证程序首选的 APK 签名方案(例如,v2 方案),如果 APK 没有相应的签名,APK 验证程序必须要拒绝这些 APK。此项保护依赖于内容 META-INF/.SF 文件受 v1 签名保护这一事实。

常见问题

申请第三方SDK(如微信支付)时填入的SAH1值是什么?

签名证书的指纹,在申请第三方SDK时,需填入APK包名和证书指纹,SDK开发者后台会根据这两个值生成一个key。第三方SDK在初始化时,会从系统中获取当前APK的包名、签名证书指纹以及key,然后将此指纹上传到其服务器,然后校验包名、签名证书指纹是否与此key绑定,校验通过后才进行授权。

目前众多的快速批量打包方案又是如何绕过签名检验的?

在V2方案出现之前,快速批量打包方案有3类:

  1. 反编译APK后修改渠道值,再重新打包

    这种方案实际上是重新签名,因有反编译、重新打包、签名的过程,速度相对后两种方案较慢;

  2. 将渠道信息以文件形式写入META-INF目录中

    因为META-INF目录是用来存放签名的,其本身无法加入签名校验中,在META-INF目录中添加文件不会破坏原有签名。此方案需同时修改zip数据区中央目录中央目录结尾记录

  3. 将渠道信息写到zip中央目录结尾记录的comment字段中

    通过前面分析zip文件结构,可以发现中央目录结尾记录最后注释字段,这部分内容在JAR签名方案中同样不在签名校验范围中,故添加注释也不会破坏原有签名。此方案只需修改中央目录结尾记录

参考

1
2
3
https://www.jianshu.com/p/308515c94dc6
https://blog.csdn.net/freekiteyu/article/details/84849651
https://www.jianshu.com/p/d05e702e0507 -v1源码分析