数字证书原理

Posted by     "赵化冰" on Thursday, March 19, 2020

非对称加密

在传统的加密算法中,通信的双方会采用一个共享秘钥来对数据进行加密和解密。消息发送方先采用秘钥对明文进行加密然后再进行传送,待接收方收到消息后,再采用秘钥对密文进行界面,以得到明文。由于加密和解密采用的秘钥是相同的,这种加密算法也称为对称加密。采用对称加密的通信过程如下图所示: 图片来源twilio

与传统的对称加密不同,非对称加密算法采用了两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是成对使用的,如果用公钥对数据进行加密,只有用对应的私钥才能解密;同样,使用私钥对数据进行加密,只有用对应的公钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法,也称为公开密钥加密。采用非对称加密的通信过程如下图所示: 图片来源twilio

使用对称加密算法需要双方事先交换加/解密用的共享秘钥,而交换的过程就存在密码泄露的风险。非对称加密的保密性比较好,因为它消除了通信双方交换秘钥(加密用的私钥)的需要。公钥是不需要进行保密的,可以对外进行公布,但加密后的信息只有私钥的拥有者才能进行解密。

但是非对称加密的效率比对称加密低,因此在通信过程中一般会结合非对称加密和对称加密来实现数据加密传输。先通过非对称加密协商一个用于对称加密的共享秘钥,后续在传递数据时采用该对称加密秘钥匙来对数据加密,以在保证安全性的同时兼顾加密传输的效率。SSL/TLS就采用了类似的加密传输机制。

下面我们用openssl命令来试验创建一个秘钥对,并使用该秘钥对来进行加解密。 首先创建一个私钥。

openssl genpkey -algorithm  RSA  -outform PEM -out private_key.pem

然后创建对应的公钥。

openssl rsa -in private_key.pem -outform PEM -pubout -out public_key.pem
writing RSA key

使用公钥加密文件,从命令输出可以看到明文“Hello world”已经被加密为密文。

#Create a test file...
echo 'Hello world' > plain_text

#Encrypt plain_text with public key...
openssl rsautl -encrypt -inkey public_key.pem -pubin -in plain_text -out encrypted_text

cat encrypted_text
?▒▒nC▒▒▒▒K▒߿$▒E▒{'▒k▒▒▒
▒▒▒▒>▒▒▒~▒▒▒▒p▒&Y▒▒▒▒'▒▒Z▒Z▒O$▒b▒ϒ▒Z▒mh▒*"▒^,▒▒6▒ê2▒▒▒?H▒&▒▒▒g▒5▒▒g▒a
▒yNs▒r▒75cyq▒P?p▒M[iZ@▒▒b▒*o▒▒?(▒▒M▒▒x▒^▒▒ٱ󊽂Q▒▒@▒jޤ▒K▒▒▒&=)▒▒▒▒y▒▒▒
 H▒▒▒B▒0Z▒0▒▒▒~t|<~▒b/Y▒O▒^⫯&a

使用私钥进行解密,密文被还原为明文“Hello world”。

#Decrypt encrypted_text with private  key...
openssl rsautl -decrypt -inkey private_key.pem -in encrypted_text -out decrypted_text

cat  decrypted_text
Hello world

哈希函数

哈希函数是一个具有以下输入和输出的数学函数 H(X)=Y:

  • H: 哈希函数,其输入参数为X,输出为Y。
  • X: 哈希方法的输入,可以是任意长度的任意数据。
  • Y:哈希方法的输出,是一段固定长度的二进制数据,长度可以是256,384,516…。

用于加密的哈希函数有下面的特征:

  • 无法找到产生相同输出的输入。从数学理论上来说,我们是可以找到具备相同输出的输入的,因为输入可以是任何数据,而输出则是固定长度的。但是对于一个好的哈希函数来说,我们就是使用地球上所有的计算机一起进行计算,也无法在可以接受的时间内找出该输入。
  • 无法通过输出反推出输入。对于输入值的范围很大的情况来说,这是没有问题的,如果输入值是一个有限的集合,则很容易通过遍历尝试每一个输入值来推断出一个输出对应的输入。在这种情况下,我们可以为输入X加上一个随机值R来隐藏输入值,即 H(R|X)=Y。由于攻击者不知道R的值,因此无法再通过遍历尝试每个输入的方法推断出Y对应的输入。这种做法被称为“加盐”,例如我们在存储密码时就会通过“加盐”的方法来避免彩虹表攻击。

从上面哈希函数的的特点可以看出,一段数据的哈希值就是该数据的一个固定长度的,独一无二的“特征”,我们可以把哈希值看作该数据的“数字指纹”。

数字签名

我们在“非对称加密”一节中演示了采用公钥进行加密,私钥进行解密的过程。实际上我们也可以采用私钥进行加密,采用公钥进行解密。秘钥对的拥有者采用私钥对一段数据进行加密,然后公布密文,原文和公钥,任何人都可以使用公钥解密密文,然后核对原文和密文是否一致。由于私钥是不公开的,只有拥有者才能采用私钥对数据加密,所以这种方式可以用于证明发布这条消息的用户拥有消息中的公钥对应的私钥,这就是数字签名的基本原理。在数字证书,区块链,比特币中就采用了这种方法来验证用户身份。采用数学公式来表示数字签名的原理:

  • 消息发送者生成秘钥对: (sk,pk):= generates(keysize)
  • 消息发送者生成数字签名: signature := encrypt(sk, message)
  • 消息接收者验证发送者身份: isValid := isEqual(decrypt(pk, message),message)

该方法直接对传递的原始数据进行加密,当数据很大时,对数据进行签名和传输的效率很低。此时我们可以通过哈希函数提取数据的特征值,然后只对数据的哈希值进行签名。由于哈希函数的特点,通过哈希值同样可以验证签名的私钥身份,同时还避免了对整段数据进行加密和传输的开销。采用哈希函数后的数字签名原理如下:

  • 消息发送者生成秘钥对: (sk,pk):= generates(keysize)
  • 消息发送者生成数字签名: signature := encrypt(sk, hash(message))
  • 消息接受者验证发送者身份: isValid := isEqual(decrypt(pk, signature),hash(message))

下图展示了数字签名和验证的流程: 图片来源revasolutions

除了验证公钥拥有者的身份之外,数字签名还可以证明传输的数据没有被人恶意篡改,因为如果数据被篡改了,数据的哈希值就和解密后的签名对不上。

数字证书

在前面我们讲到可以通过数字签名来证明一个人或者组织拥有一个公钥对应的私钥,因此我们可以把一个公钥看作一个数字身份标识,如果一个人可以发出使用该标识(公钥)对应的私钥签名的数据,则说明该用户是该数字身份标识的拥有者。

在现实生活中也有类似的身份证明,身份证明有匿名的,也有实名的。匿名的身份证明只是用于证明你拥有某一项身份或者权益,并不需要声明你到底是谁,例如各种运动俱乐部的会员卡。实名的身份证明则需要你的姓名,住址,出生年月等信息,例如我们的身份证。

当我们只需要匿名的身份标识时,直接采用公钥就可以了,采用公钥对应的私钥对任意一段数据进行数字签名,并发布原生数据,公钥和签名数据,就可以证明公钥拥有者的身份了。比特币是一个匿名的交易系统,只需要证明你是比特币的拥有者,并不需要公布真实身份,因此在比特币中就直接采用了公钥作为比特币的钱包地址。

在大部分的情况下,我们还需要证明公钥拥有者的真实身份,例如姓名,地址,电子邮件等,因此会将这些信息随着公钥一起发布,这就是数字证书。在前面介绍的数字签名中,传输的文本是任意一段数据,在数字证书中的数据则是证书拥有者的真实身份信息。我们可以把数字证书理解为包含“公钥+姓名+地址+电子邮件等个人/组织信息”的一段数据,通过数字签名来证明证书拥有者的身份和确保证书中的信息在传输过程中不被恶意篡改。

RFC5280定义了x.509公钥证书的标准格式,如下图所示: 图片来源researchgate

从图中可以看到,除了证书拥有者的公钥,证书拥有者的一些基本信息和数字签名之外,还包含了证书签发者的信息,我们后续会对此进行进一步说明。

我们可以采用openssl来创建一个数字证书及其对应的私钥:

openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
Generating a RSA private key
.................+++++
.......+++++
writing new private key to 'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Sichuan
Locality Name (eg, city) []:Chengdu
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Make a Lot of Money Ltd        
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:zhaohuabing
Email Address []:zhaohuabing@gmail.com

在证书创建过程中,会要求输入姓名、住址、电子邮件等身份信息。

查看刚才生成的证书文件中的内容。

openssl x509 -in certificate.pem -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            22:92:57:9c:c4:2f:8a:fc:77:13:7a:67:71:78:2d:b3:f9:d1:6c:50
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = CN, ST = Sichuan, L = Chengdu, O = Make a Lot of Money Ltd, CN = zhaohuabing, emailAddress = zhaohuabing@gmail.com
        Validity
            Not Before: Mar 19 11:38:30 2020 GMT
            Not After : Mar 19 11:38:30 2021 GMT
        Subject: C = CN, ST = Sichuan, L = Chengdu, O = Make a Lot of Money Ltd, CN = zhaohuabing, emailAddress = zhaohuabing@gmail.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:a2:34:3d:bc:79:52:e0:aa:04:96:d3:89:12:d5:
                    ec:53:f2:60:b2:81:f6:77:99:0c:5f:ba:8d:b3:aa:
                    88:59:05:ae:4b:53:2a:03:f2:10:12:a3:71:eb:23:
                    9d:3c:63:d3:ec:b6:12:2a:f7:bc:8d:4b:a9:84:f5:
                    67:17:7a:6b:50:aa:dd:9b:5c:8c:b1:5e:a8:33:f4:
                    d0:69:ba:78:11:84:ee:44:80:46:2b:c3:a4:39:53:
                    f2:de:d6:1a:2b:3a:e2:87:0b:06:30:8f:1f:89:aa:
                    59:2a:f8:43:04:3a:b5:59:60:60:f0:2e:64:10:38:
                    84:4f:9a:07:67:f0:8a:d9:8e:04:20:9d:22:32:63:
                    79:2e:fb:e6:da:90:14:23:da:8f:3e:31:b4:96:91:
                    c6:3a:08:3b:a2:3c:c0:94:55:38:d3:73:1a:b1:e7:
                    ec:c8:44:3e:42:a0:8a:99:c5:8a:ac:e9:5a:4a:06:
                    f4:53:34:c7:88:fa:b9:81:40:19:bb:97:f2:26:74:
                    05:20:bd:2d:31:93:da:2c:ba:b3:87:96:0b:dc:de:
                    3f:86:05:b8:56:e8:ab:b1:75:8d:33:41:b3:cc:16:
                    dc:a7:fb:ae:05:55:a1:96:11:9d:1e:16:c6:55:d6:
                    4e:cf:ab:c3:b3:29:bd:43:84:2f:a0:29:ee:b1:3d:
                    02:45
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                C9:45:FA:08:E2:95:6B:C4:CC:D5:15:DC:2C:19:05:C9:40:E6:DD:F2
            X509v3 Authority Key Identifier:
                keyid:C9:45:FA:08:E2:95:6B:C4:CC:D5:15:DC:2C:19:05:C9:40:E6:DD:F2

            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         6b:bd:33:f7:4b:35:73:1f:bf:ad:24:f0:ce:9c:43:f0:51:96:
         19:08:bb:40:1b:a8:67:c6:e4:15:db:7a:09:78:74:56:06:7b:
         17:23:15:14:fc:d5:09:39:fd:dd:63:14:a3:6d:ca:e6:59:cf:
         83:1c:a9:f0:c4:8a:73:43:fc:12:9a:26:31:82:2a:99:74:0b:
         d2:57:ca:b9:85:fd:60:7e:c1:84:9a:51:9e:73:ac:84:43:f1:
         44:b9:ec:59:6b:42:2b:48:8c:b2:ff:32:0f:58:c6:61:7d:57:
         82:31:43:58:e8:76:2a:f4:eb:7f:96:50:b7:6b:69:15:39:fa:
         cc:63:b7:12:6d:ed:42:6b:62:cc:9c:f4:4d:e3:46:8e:a2:e8:
         ff:9c:2a:e9:c9:1f:b6:fa:a1:b6:4f:4b:92:8d:b2:1e:2e:64:
         67:1f:37:3c:6b:69:29:6f:1a:28:3a:f0:a9:35:ad:61:a8:5c:
         71:78:00:7f:48:48:ee:ef:dc:9a:17:ca:31:d0:da:ce:d4:f2:
         9e:de:7c:91:bb:aa:e7:ea:9d:4f:96:88:00:a9:25:09:0c:e0:
         fb:aa:4b:12:a2:d7:da:e9:3a:e6:1d:89:bf:05:96:76:aa:29:
         c4:c2:e2:0e:7d:39:23:b6:e9:56:17:89:d5:88:44:2e:ae:37:
         a6:50:b9:b4
-----BEGIN CERTIFICATE-----
MIIEATCCAumgAwIBAgIUIpJXnMQvivx3E3pncXgts/nRbFAwDQYJKoZIhvcNAQEL
BQAwgY8xCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdTaWNodWFuMRAwDgYDVQQHDAdD
aGVuZ2R1MSAwHgYDVQQKDBdNYWtlIGEgTG90IG9mIE1vbmV5IEx0ZDEUMBIGA1UE
AwwLemhhb2h1YWJpbmcxJDAiBgkqhkiG9w0BCQEWFXpoYW9odWFiaW5nQGdtYWls
LmNvbTAeFw0yMDAzMTkxMTM4MzBaFw0yMTAzMTkxMTM4MzBaMIGPMQswCQYDVQQG
EwJDTjEQMA4GA1UECAwHU2ljaHVhbjEQMA4GA1UEBwwHQ2hlbmdkdTEgMB4GA1UE
CgwXTWFrZSBhIExvdCBvZiBNb25leSBMdGQxFDASBgNVBAMMC3poYW9odWFiaW5n
MSQwIgYJKoZIhvcNAQkBFhV6aGFvaHVhYmluZ0BnbWFpbC5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCiND28eVLgqgSW04kS1exT8mCygfZ3mQxf
uo2zqohZBa5LUyoD8hASo3HrI508Y9PsthIq97yNS6mE9WcXemtQqt2bXIyxXqgz
9NBpungRhO5EgEYrw6Q5U/Le1horOuKHCwYwjx+Jqlkq+EMEOrVZYGDwLmQQOIRP
mgdn8IrZjgQgnSIyY3ku++bakBQj2o8+MbSWkcY6CDuiPMCUVTjTcxqx5+zIRD5C
oIqZxYqs6VpKBvRTNMeI+rmBQBm7l/ImdAUgvS0xk9osurOHlgvc3j+GBbhW6Kux
dY0zQbPMFtyn+64FVaGWEZ0eFsZV1k7Pq8OzKb1DhC+gKe6xPQJFAgMBAAGjUzBR
MB0GA1UdDgQWBBTJRfoI4pVrxMzVFdwsGQXJQObd8jAfBgNVHSMEGDAWgBTJRfoI
4pVrxMzVFdwsGQXJQObd8jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4IBAQBrvTP3SzVzH7+tJPDOnEPwUZYZCLtAG6hnxuQV23oJeHRWBnsXIxUU/NUJ
Of3dYxSjbcrmWc+DHKnwxIpzQ/wSmiYxgiqZdAvSV8q5hf1gfsGEmlGec6yEQ/FE
uexZa0IrSIyy/zIPWMZhfVeCMUNY6HYq9Ot/llC3a2kVOfrMY7cSbe1Ca2LMnPRN
40aOouj/nCrpyR+2+qG2T0uSjbIeLmRnHzc8a2kpbxooOvCpNa1hqFxxeAB/SEju
79yaF8ox0NrO1PKe3nyRu6rn6p1PlogAqSUJDOD7qksSotfa6TrmHYm/BZZ2qinE
wuIOfTkjtulWF4nViEQurjemULm0
-----END CERTIFICATE-----

命令行输出的前半部分是解码后的正式内容,"—–BEGIN CERTIFICATE—–“之后的部分则是采用PEM(Privacy Enhanced Mail)格式进行Base64编码的原始证书文件内容。其中值得重点关注的有以下字段:

  • Issuer: 证书颁发机构。
  • Subject: 证书拥有者。
  • Subject Public Key Info: 证书拥有者的公钥。
  • Signature Algorithm: 签名算法及数字签名。

自签名证书

生成数字证书时会使用证书颁发机构(Issuer)的私钥对证书内容(证书拥有者的公钥和身份信息)进行数字签名。从上面实验的输出可以看到,Issuer和Subject中的信息是相同的。都是“C = CN, ST = Sichuan, L = Chengdu, O = Make a Lot of Money Ltd, CN = zhaohuabing”,这说明对该证书进行签名使用的私钥是证书中公钥对应的私钥。这种采用证书中的公钥对应的私钥进行签名的证书被称为自签名证书,意思是签名者和证书拥有者为同一人。

下图展示了一个自签名证书的内部结构:

我们可以用openssl verify命令来验证证书,-CAfile参数指明验证证书数字签名使用的公钥。由于这是一个自签名证书,因此需要采用自身证书中的公钥来验证该证书。如下所示:

openssl verify -CAfile certificate.pem certificate.pem
certificate.pem: OK

自签名证书可以证明该证书是由证书中公钥(以及对应的私钥)拥有者发布的,并且可以保证证书中包含的信息例如姓名、组织机构、地址等未被第三方篡改过,但是并不能解决证书拥有者和证书使用者之间的信任问题。我们可以把自签名证书理解为一个人给自己印了一张名片,名片中写上了自己的姓名,地址,电子邮件等信息,然后把这张名片分发给别人。如果别人从其他渠道中得知你是可以信任的,则会欣然接受该张名片,并且认可名片中的信息;但如果别人之前并不认识你,那么就会对名片中的信息持怀疑态度:我怎么知道你名片中的姓名等内容是真实的呢?因此自签名证书的使用场合是有限的,只限于一些组织机构的内部系统中(我们可以认为在一个系统内部中,自签名证书所声明的身份是可信的),并不能在互联网上使用。

证书机构

前面我们讲到自签发证书存在内容的可信度问题,因此在数字证书体系中引入了证书机构来解决该问题。我们可以把证书机构看作真实世界中的权威机构,例如政府部门。我们使用的身份证具有可信度,这是因为有政府部门担保其中信息的正确性。在数字证书体系中也有类似的权威机构,我们称为证书机构。

引入证书机构后,我们使用证书机构而不是证书拥有者的私钥来对数字证书进行签名,如下图所示: 我们认为证书机构是可信的,那么使用证书机构的私钥对证书进行签名相当于证书机构用自己的信誉来保证了证书中用户身份信息和公钥的真实性。只要使用该证书的用户用证书机构的公钥验证了该证书中的签名,就可以信任该证书中的公钥和用户身份。然后就可以使用该公钥来和证书拥有者进行安全通信了,例如访问证书拥有者的网站。

看到这里你可能会发现一个问题:在验证证书时我们需要用到证书机构的公钥,那我们如何拿到证书机构的公钥和身份信息,并保证证书机构自身身份的真实性呢?权威证书机构会为自己颁发一个自签名证书,这称为证书机构的根证书,然后操作系统和浏览器会将这些根证书内置到发布的版本中,当验证用户证书时,操作系统和浏览器会认为内置的这些证书机构的证书是可信的,这样就解决了证书机构的信任问题。有时,我们可能想使用一些未被操作系统和浏览器缺省内置的证书机构,则可以把这些证书机构的根证书手动导入到操作系统或者浏览器中。

假设Alice要发送一个电子商务合同给Bob,为了向Bob表明合同是Alice发送的,Alice使用自己的证书对该合同进行数字签名后再发送,证书的签发、使用和验证过程是这样的:

  1. Alice在本地生成Private Key和CSR(Certificate Signing Request)。CSR中包含了Alice的公钥和姓名,机构、地址等身份信息。
  2. Alice使用该CSR向证书机构发起数字证书申请。
  3. 证书机构验证Alice的身份后,使用CSR中的信息生成数字证书,并使用自己的CA根证书对应的私钥对该证书签名。
  4. Alice使用自己的Private Key对合同进行签名,然后将签名后的合同和自己的证书一起并发送给Bob。
  5. Bob使用操作系统中自带的证书机构根证书中的公钥来验证Alice证书中的签名,以确认Alice的身份和公钥。
  6. Alice的证书验证成功后,Bob使用Alice证书中的公钥来验证合同中数字签名。
  7. 合同数字签名通过验证,可以证明该合同为Alice本人发送,并且中间未被第三方篡改过。

下面我们通过openssl命令行来试验这一流程。

模拟一个证书机构,为该证书机构创建私钥和自签名的根证书。

openssl req -newkey rsa:2048 -nodes -keyout rootCA.key -x509 -days 365 -out rootCA.crt
Generating a RSA private key
.................................................................................................+++++
............+++++
writing new private key to 'rootCA.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
string is too long, it needs to be no more than 2 bytes long
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Sichuan
Locality Name (eg, city) []:Chengdu
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Root CA
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:My Root CA
Email Address []:admin@myrootca.com

创建Alice的私钥和CSR(证书签名请求),该CSR中包含了Alice的私钥对应的公钥,以及Alice的姓名,机构等身份信息。

openssl req -new -nodes -keyout Alice.key -out Alice.csr
Generating a RSA private key
.....................+++++
................................................+++++
writing new private key to 'Alice.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Sichuan
Locality Name (eg, city) []:Chengdu
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Alice Trade Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Alice
Email Address []:alice@alicetrade.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

使用证书机构的私钥、根证书和Alice的CSR为Alice创建数字证书。

openssl x509 -req -in Alice.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out Alice.crt
Signature ok
subject=C = CN, ST = Sichuan, L = Chengdu, O = Alice Trade Ltd, CN = Alice, emailAddress = alice@alicetrade.com
Getting CA Private Key

Alice使用私钥对商业合同签名,然后把签名后的合同和Alice的数字证书一起发给Bob。

echo "A very important business contract to Bob" > alice-contract
openssl dgst -sha256 -sign Alice.key -out alice-contract-sign.sha256 alice-contract

Bob收到该合同后,需要先验证Alice的数字证书是否合法。

openssl verify Alice.crt
C = CN, ST = Sichuan, L = Chengdu, O = Alice Trade Ltd, CN = Alice, emailAddress = alice@alicetrade.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error Alice.crt: verification failed

在这里看到验证失败了,原因是签发Alice证书的的根证书是由我们生成的一个自签名证书,不是内置在操作系统内的权威机构的根证书。

我们可以在验证Alice证书的命令行中指定证书机构的根证书,可以看到在指定根证书后,验证成功了。

openssl verify -CAfile rootCA.crt  Alice.crt
Alice.crt: OK

在正常情况下,该根证书应该是内置在操作系统中的,我们通过命令把该证书导入到系统中,以模拟内置证书的情况。备注:每种操作系统的证书导入方法有所区别,本例中是Arch Linux中的导入方法。

sudo cp rootCA.crt /etc/ca-certificates/trust-source/anchors/
sudo trust extract-compat

然后再对Alice的证书进行验证,即使不指定根证书,也可以验证成功。这是因为验证时会缺省使用操作系统中内置的根证书。

openssl verify Alice.crt
Alice.crt: OK

在验证了Alice的证书后,使用Alice的证书中的公钥对合同的电子签名进行验证,以证明该合同的确是由Alice发出的。

#从Alice的证书中导出Alice的公钥
openssl x509 -pubkey -noout -in Alice.crt  > Alice-pub.key
#使用公钥对合同签名进行验证
openssl dgst -sha256 -verify Alice-pub.key -signature alice-contract-sign.sha256 alice-contract
Verified OK

证书链

在Alice和Bob的证书实验中,Alice的证书是采用我们创建的证书机构的根证书签发的。但实际上真正的证书机构不会使用根证书来直接签发用户证书。原因是根证书非常重要,如果根证书的秘钥泄露了,会影响该根证书签发的所有用户证书,导致非常严重的安全风险。为了避免根证书的安全风险,证书机构会采用根证书签发中间证书(intermediate certificate),然后使用中间证书来签发用户证书,这样如果一个中间证书出现了安全问题,可以废弃掉该中间证书而不影响根证书,影响的用户证书范围也小一些。

因此证书有三种,根证书,中间证书和用户证书,其中中间证书可以有多个层级,这样形成一个链式的证书层级,称为证书链。多层证书链的生成和验证过程和前面试验中的两层(根证书直接生成用户证书)的情况是类似的,只是中间过程会多几个层级。以三层证书链的生成和验证过程举例说明。

  • 证书生成和使用过程
    1. 证书机构生成自签名根证书
    2. 证书机构采用根证书对应的私钥签名中间证书
    3. 证书机构采用中间证书对应的私钥签名用户证书
    4. 用户采用用户证书对应的私钥签名用户数据
  • 证书使用时的验证过程
    1. 采用中间证书中的公钥验证用户证书的签名
    2. 采用根证书中的公钥验证中间证书的签名
    3. 采用用户证书中的公钥来验证数据中的签名

证书链中证书的关系如下图所示,每一个下级证书中都有其上级证书的DN(Distinguished Name),在进行验证时,每一级都会通过该DN来找到上级证书,并使用上级证书的public Key来验证本机证书的签名,如果中间有多个层级,则会重复该验证过程一直到自签名根证书,由于自签名根证书已经内置在操作系统中,属于系统信任的根证书,到达根证书时就验证完毕,形成了一条从上到下的链状信任关系。 图片来源f5

下面我们用openssl来试验一个三层的证书链。

首先生成根证书和对应的私钥。

openssl req -newkey rsa:2048 -nodes -keyout rootCA.key -x509 -days 365 -out rootCA.crt
Generating a RSA private key
.......+++++
..................................+++++
writing new private key to 'rootCA.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Sichuan
Locality Name (eg, city) []:Chengdu
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Test Root CA
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Test Root CA
Email Address []:rootca@zhaohuabing.com

生成中间证书的私钥和CSR。

openssl req -new -nodes -keyout intermediate.key -out intermediate.csr
Generating a RSA private key
...........+++++
................+++++
writing new private key to 'intermediate.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Sichuan
Locality Name (eg, city) []:Chengdu
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Test Intermediate CA
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Test Intermediate CA
Email Address []:intermediateCA@zhaohuabing.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

和用户证书不同的是,中间证书需要在证书的basicConstraints中设置CA:true标签,以标明该证书属于证书机构的证书,可以用于签发和验证用户证书。而openssl x509命令不能设置basicConstraints命令,因此我们需要采用openssl ca命令,该命令实现了一个简单的证书机构。

openssl ca 命令需要采用一个配置文件来配置生成证书的相关参数。我们创建一个intermediateCA.conf文件,其内容如下:

[ ca ]
default_ca = intermediate_ca
[ intermediate_ca ]
dir = .
private_key = $dir/rootCA.key
certificate = $dir/rootCA.crt
new_certs_dir = $dir/
serial = $dir/crt.srl
database = $dir/db/index
default_md = sha256
policy = policy_any
email_in_dn = no
[ policy_any ]
domainComponent = optional
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional
[ ca_ext ]
keyUsage                = critical,keyCertSign,cRLSign
# 注意这里设置了CA:true,表明使用该配置生成的证书是CA证书,可以用于签发用户证书
basicConstraints        = critical,CA:true
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always

由于openssl ca命令实现了一个简单的证书机构,会使用一个文本数据库来记录生成的证书,我们需要生成该数据库索引文件。

mkdir db
touch db/index

使用intermediateCA.conf生成中间证书。

openssl ca -config intermediateCA.conf -days 365 -create_serial -in intermediate.csr -out intermediate.crt -extensions ca_ext -notext
Using configuration from intermediateCA.conf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'CN'
stateOrProvinceName   :ASN.1 12:'Sichuan'
localityName          :ASN.1 12:'Chengdu'
organizationName      :ASN.1 12:'Test Intermediate CA'
commonName            :ASN.1 12:'Test Intermediate CA'
Certificate is to be certified until Mar 21 02:04:36 2021 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

生成Alice的私钥和CSR。

openssl req -new -nodes -keyout Alice.key -out Alice.csr
Generating a RSA private key
........+++++
......................+++++
writing new private key to 'Alice.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Sichuan
Locality Name (eg, city) []:Chengdu
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Make a Lot of Money Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Alice
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

生成Alice的用户证书。

openssl x509 -req -in Alice.csr -CA intermediate.crt -CAkey intermediate.key -CAcreateserial -out Alice.crt
Signature ok
subject=C = CN, ST = Sichuan, L = Chengdu, O = Make a Lot of Money Ltd, CN = Alice
Getting CA Private Key

对Alice的用户证书进行验证,验证时需要同时指明根证书和中间证书。

openssl verify -CAfile rootCA.crt -untrusted intermediate.crt Alice.crt
Alice.crt: OK

我们可以把根证书和中间证书的内容一起放到一个证书链文件中,然后使用该证书链文件来验证用户证书。

cat rootCA.crt intermediate.crt > chain.crt
openssl verify -CAfile chain.crt Alice.crt
Alice.crt: OK

在真实场景下,根证书自身就是可信的。我们将根证书导入到操作系统中来模拟该情况。

sudo cp rootCA.crt /etc/ca-certificates/trust-source/anchors/
sudo trust extract-compat

然后在openssl命令行中只指明中间证书,就可以验证Alice的用户证书。

openssl verify -CAfile intermediate.crt Alice.crt
Alice.crt: OK

交叉认证

在签发证书时,多个CA可以为同一个公钥签发证书,因此一个证书签名对应的公钥可以存在于多个上级证书中。采用这种方式,一个证书可以存在于多条链中,并且每条链都是合法的,都可以通过验证。

让我们来看看下面图中的例子: 在上图中,我们采用了CA1来为User1签发证书,CA1有一个自签名的根证书,同时采用CA2的根证书为CA1签发了一个中间证书,这样User1就处在了两条合法的证书链上(分别用实线和虚线表示)。

  • User 1 Certificate -> CA1 Self-signed Certificate
  • User 1 Certificate -> CA1 Certificate Issued by CA2 -> CA2 Self-signed Certificate

这两条链都是合法的,都可以对User1的证书进行验证。同理,也可以用CA1为CA2签发一个中间证书,使CA2颁发的用户证书也处于两条合法的证书链上。这种方式被称为交叉认证,通过交叉认证,可以为两个CA颁布的证书在两个CA之间建立互信关系。

通过这种方式还可以实现CA的根证书更新,在进行根证书更新时,CA生成一对新的秘钥对和根证书,然后用新的私钥为老的公钥签名生成一个中间证书,并用老的私钥为新的公钥签名生成一个中间证书。这样,无论是新的根证书还是老的根证书颁发的证书在更新期间都可以正常使用,以实现CA新老根证书的平滑过渡。

本文试验所采用的所有命令已经编写成可以执行的脚本,可以从该github地址下载后自行执行试验。

参考文档