openssl密钥证书管理(Key and Certificate Management)

前言

前两日应别人要求提供一份CSR文件过去,方便他们生成相关证书,对于这一块本来也不熟,于是找到openssl官网,想找找相关的教程看看,一番小找,果有收获,是个宝藏,源文档在这(OpenSSL Cookbook烹饪宝典),有意者可前往一观(最好是能仔细看看官方提供的原文,百度众多的博客文章,难为佳策,不如一见本尊为妥)。

本文只是节选概括了其中的部分内容,不为全部,可能会有些理解的偏颇,望周悉。

私钥和证书管理

现在很多网站(或web server)为了安全访问都采用了证书认证的模式(以支持SSL),这个过程主要有3大步:

(1)生成一个私钥(private key);

(2)创建一个CSR(Certificate Signing Request)文件,将它提供给一个CA;

(3)在网站(或web server)安装CA提供的证书文件。

私钥

在运行一个TLS server之前我们首先要做的就是准备好一个私钥文件,在准备私钥文件之前我们得决定私钥的一些属性,比如私钥算法、私钥长度、保护密码等等。

私钥算法

OpenSSL支持RSA/DSA/ECDSA/EdDSA算法(这些算法的一些数学原理大家可以找些条理清晰,没有大毛病的文章看看,比如这里),但是实际过程中,只是使用了其中的部分,用的最多的还是RSA和ECDSA(DSA和EdDSA并不被大范围支持)。

私钥长度

如果在生成私钥时不指定其密钥长度,那么它会使用一个默认值,但这往往不是太安全(长度比较小,破解难度小),我们需要指定一个更大长度的(前提硬件得能能力支持),目前比较推荐的是2048-bit的RSA算法私钥以及256 bits的ECDSA。

保护密码

为私钥设置一个保护密码(使用这份私钥时会提示提供密码,失败则不能使用该私钥文件),并不是必须的,但为了安全程度更高一点还是比较推荐的,但是呢,使用了保护密码,在日常使用上会比较烦,因为每次使用私钥时都被提示需要输入该私钥的密码(一个字符串明文),所以有些场景为了简易部署和使用,可能会不使用该保护密码,毕竟除了方便性,保护密码能提供的保护力度也是有限的,有些地方则会采用硬件保护的方式来确保私钥的安全。

生成私钥

生成RSA私钥,使用genpkey命令,带保护密码的完整命令如下,保护密码也提供了加密算法,这里是AES-128(也可以是AES-256,-aes-256-cbc),避免使用其他算法,比如DES/3DES/SEED等:

复制代码
$openssl genpkey -out private.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -aes-128-cbc

按提示创建一个包含密码并再确认重输一次。

如果不要保护密码,那么可以参考下面的命令:

复制代码
$openssl genpkey -out private.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048

后面还会提到另外一种生成方式(与csr文件同时)。

查看私钥

当通过genpkey命令生成私钥时,这份私钥会被保存为PKCS #8格式(文本),cat private.key一下看起来会像下面这样:

复制代码
root@ubuntu:/tmp# openssl genpkey -out private.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048
...............................................+++
........................................+++
root@ubuntu:/tmp# cat private.key 
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCiFKjjCTCIv4al
FX6tE0sKwd8o/cGET9lilw77M3SSHYf++Eczu9h5y4JUVtKmXc81uLgFWVQ/Jvkq
TTW/d+P8FdXUBRfQEW0CLKaa3ONaOnVtrQjLp4eYGQbhXifotYGnnDUFyUdyy0Eq
obdMJrTixA5HGAGOodHPgwzw5dVkGPmMrcGJjcYU1QsBikYpQ2hIYu5qlUS2BgeM
Yhkxks07iVKNYAOp3YD3owaGpXUzvjTi0h22o5QExE8LV99nI5ZkwvJknVy2+k20
kzFqwSD6ohfVJ5GEBmUSJlOE2+dvgGRblEgr5ByTt5fcMGK2+4x7Np4k65XZiY9O
WBixzXdjAgMBAAECggEABNrPkDVXQdFSzCEPsUlxpvnVoFmxcTAfF8fkh/urkzDV
QPrYzyDiRAundLVBDAClUzucL3TUhCu7rNuxL3H83zEcPYSFiRLsi/MgbmUNXkd/
+vAC4m67LgAZ56g1U9AT+RxBrlGwFS0nX4qGff5uzpgGwXRjn40TRr7rJVANCzl0
EkwHJOgvrm9US4R5BZw1RVe69W5UB9fvYvyo0rzHmK6e/mkKpzixmBQZ6Mk26jV2
IiwYkMt4WyCSPFhKDEBsOVVYRob6vtkBFDA5OoukJ99jpG0qu4DAXgRiTS+7qqYB
ktdI3lZHmIehoPDCmqJJj6UitocpVyH/b2PI0q9oUQKBgQDS6z7rX6ga3d0mGUPk
4vdRTVTRGgy+74X0vRbVxJY5zOTcx/hOdnzWezNsYSN1eCx2Z/Pku9Mg9kneuo6U
EHIwtQHSeKS2S7EB/Ic6KLqW+HgTK42wi3r+husRNB+M08RvN1q0tV0IH5Xf6cxl
5pGmFV8Cwx7tySla/qdyDUpmGwKBgQDEuSe0Q85g7c4yhdf7BTD6vV6rkuA65Wmc
y+WyWuk7NolgYRz5HIKY6Pqfo3hjLqyx4LC+JTGWUgqgtag/qVAtgrAXdizo87sG
cdhmAiPrwDwUE0KcrX6BsyuRZsVfH35ftWSFCVpbbEb+8nhkLhBpV0aKmkjTlVpC
/1PE969oWQKBgCDy+wtOEDbSPf/7pm2VP0mGKR/ZT/Br/qMy/uQ6J7Wz2nTbEGFB
hVMEQA5IRn/NXDTLHIC5DsxxbSzMPAXJY+1T+YrlqeWIgYhr6LwfLLb07d0gyquv
vnEQppwzZlJbqq4tGZBtXLEuLf2iynmIwWwOnp8R2D0RVdKPQZ1BkkPNAoGACG9P
7oDBxpIrtyrZ+d9dHEMmXgDsMFpJzvlHHgCb43gRJ9rCHURFrTzAEjw2GzqTGQVj
TqPVnnZiEkX3stMVpv3lgvwELPDWYg5bkvQgiikx6kJxJrqho/oEsjVP3SZCiuQC
LVobT7QyzxeiZdk7xuiBE8xNtxS+TAUamGVuVfECgYBsO01qj45/rbfr1V4njdol
+qIsJiuuSkZ7OvWkb3qMgqTmjzFJjgJwcIpsnC5tc2ObVP2oKdD3rxTygFyN4lK6
uQOqPd1auSJG3uMt892IHO/zSfoHjL6D5L9YVdDWrtRRmB0NjS9LOrH5sRbToFG1
HRvSTWAtZdCMivKieiv6fQ==
-----END PRIVATE KEY-----

虽然私钥看起来是由一堆随机数据组成的,但是实际上可不是这样,它是有自己的数据结构组成,我们可以通过pkey命令看看:

复制代码
root@ubuntu:/tmp# openssl pkey -in private.key -text -noout
Private-Key: (2048 bit)
modulus:
    00:a2:14:a8:e3:09:30:88:bf:86:a5:15:7e:ad:13:
    4b:0a:c1:df:28:fd:c1:84:4f:d9:62:97:0e:fb:33:
    74:92:1d:87:fe:f8:47:33:bb:d8:79:cb:82:54:56:
    d2:a6:5d:cf:35:b8:b8:05:59:54:3f:26:f9:2a:4d:
    35:bf:77:e3:fc:15:d5:d4:05:17:d0:11:6d:02:2c:
    a6:9a:dc:e3:5a:3a:75:6d:ad:08:cb:a7:87:98:19:
    06:e1:5e:27:e8:b5:81:a7:9c:35:05:c9:47:72:cb:
    41:2a:a1:b7:4c:26:b4:e2:c4:0e:47:18:01:8e:a1:
    d1:cf:83:0c:f0:e5:d5:64:18:f9:8c:ad:c1:89:8d:
    c6:14:d5:0b:01:8a:46:29:43:68:48:62:ee:6a:95:
    44:b6:06:07:8c:62:19:31:92:cd:3b:89:52:8d:60:
    03:a9:dd:80:f7:a3:06:86:a5:75:33:be:34:e2:d2:
    1d:b6:a3:94:04:c4:4f:0b:57:df:67:23:96:64:c2:
    f2:64:9d:5c:b6:fa:4d:b4:93:31:6a:c1:20:fa:a2:
    17:d5:27:91:84:06:65:12:26:53:84:db:e7:6f:80:
    64:5b:94:48:2b:e4:1c:93:b7:97:dc:30:62:b6:fb:
    8c:7b:36:9e:24:eb:95:d9:89:8f:4e:58:18:b1:cd:
    77:63
publicExponent: 65537 (0x10001)
privateExponent:
    04:da:cf:90:35:57:41:d1:52:cc:21:0f:b1:49:71:
    a6:f9:d5:a0:59:b1:71:30:1f:17:c7:e4:87:fb:ab:
    93:30:d5:40:fa:d8:cf:20:e2:44:0b:a7:74:b5:41:
    0c:00:a5:53:3b:9c:2f:74:d4:84:2b:bb:ac:db:b1:
    2f:71:fc:df:31:1c:3d:84:85:89:12:ec:8b:f3:20:
    6e:65:0d:5e:47:7f:fa:f0:02:e2:6e:bb:2e:00:19:
    e7:a8:35:53:d0:13:f9:1c:41:ae:51:b0:15:2d:27:
    5f:8a:86:7d:fe:6e:ce:98:06:c1:74:63:9f:8d:13:
    46:be:eb:25:50:0d:0b:39:74:12:4c:07:24:e8:2f:
    ae:6f:54:4b:84:79:05:9c:35:45:57:ba:f5:6e:54:
    07:d7:ef:62:fc:a8:d2:bc:c7:98:ae:9e:fe:69:0a:
    a7:38:b1:98:14:19:e8:c9:36:ea:35:76:22:2c:18:
    90:cb:78:5b:20:92:3c:58:4a:0c:40:6c:39:55:58:
    46:86:fa:be:d9:01:14:30:39:3a:8b:a4:27:df:63:
    a4:6d:2a:bb:80:c0:5e:04:62:4d:2f:bb:aa:a6:01:
    92:d7:48:de:56:47:98:87:a1:a0:f0:c2:9a:a2:49:
    8f:a5:22:b6:87:29:57:21:ff:6f:63:c8:d2:af:68:
    51
prime1:
    00:d2:eb:3e:eb:5f:a8:1a:dd:dd:26:19:43:e4:e2:
    f7:51:4d:54:d1:1a:0c:be:ef:85:f4:bd:16:d5:c4:
    96:39:cc:e4:dc:c7:f8:4e:76:7c:d6:7b:33:6c:61:
    23:75:78:2c:76:67:f3:e4:bb:d3:20:f6:49:de:ba:
    8e:94:10:72:30:b5:01:d2:78:a4:b6:4b:b1:01:fc:
    87:3a:28:ba:96:f8:78:13:2b:8d:b0:8b:7a:fe:86:
    eb:11:34:1f:8c:d3:c4:6f:37:5a:b4:b5:5d:08:1f:
    95:df:e9:cc:65:e6:91:a6:15:5f:02:c3:1e:ed:c9:
    29:5a:fe:a7:72:0d:4a:66:1b
prime2:
    00:c4:b9:27:b4:43:ce:60:ed:ce:32:85:d7:fb:05:
    30:fa:bd:5e:ab:92:e0:3a:e5:69:9c:cb:e5:b2:5a:
    e9:3b:36:89:60:61:1c:f9:1c:82:98:e8:fa:9f:a3:
    78:63:2e:ac:b1:e0:b0:be:25:31:96:52:0a:a0:b5:
    a8:3f:a9:50:2d:82:b0:17:76:2c:e8:f3:bb:06:71:
    d8:66:02:23:eb:c0:3c:14:13:42:9c:ad:7e:81:b3:
    2b:91:66:c5:5f:1f:7e:5f:b5:64:85:09:5a:5b:6c:
    46:fe:f2:78:64:2e:10:69:57:46:8a:9a:48:d3:95:
    5a:42:ff:53:c4:f7:af:68:59
exponent1:
    20:f2:fb:0b:4e:10:36:d2:3d:ff:fb:a6:6d:95:3f:
    49:86:29:1f:d9:4f:f0:6b:fe:a3:32:fe:e4:3a:27:
    b5:b3:da:74:db:10:61:41:85:53:04:40:0e:48:46:
    7f:cd:5c:34:cb:1c:80:b9:0e:cc:71:6d:2c:cc:3c:
    05:c9:63:ed:53:f9:8a:e5:a9:e5:88:81:88:6b:e8:
    bc:1f:2c:b6:f4:ed:dd:20:ca:ab:af:be:71:10:a6:
    9c:33:66:52:5b:aa:ae:2d:19:90:6d:5c:b1:2e:2d:
    fd:a2:ca:79:88:c1:6c:0e:9e:9f:11:d8:3d:11:55:
    d2:8f:41:9d:41:92:43:cd
exponent2:
    08:6f:4f:ee:80:c1:c6:92:2b:b7:2a:d9:f9:df:5d:
    1c:43:26:5e:00:ec:30:5a:49:ce:f9:47:1e:00:9b:
    e3:78:11:27:da:c2:1d:44:45:ad:3c:c0:12:3c:36:
    1b:3a:93:19:05:63:4e:a3:d5:9e:76:62:12:45:f7:
    b2:d3:15:a6:fd:e5:82:fc:04:2c:f0:d6:62:0e:5b:
    92:f4:20:8a:29:31:ea:42:71:26:ba:a1:a3:fa:04:
    b2:35:4f:dd:26:42:8a:e4:02:2d:5a:1b:4f:b4:32:
    cf:17:a2:65:d9:3b:c6:e8:81:13:cc:4d:b7:14:be:
    4c:05:1a:98:65:6e:55:f1
coefficient:
    6c:3b:4d:6a:8f:8e:7f:ad:b7:eb:d5:5e:27:8d:da:
    25:fa:a2:2c:26:2b:ae:4a:46:7b:3a:f5:a4:6f:7a:
    8c:82:a4:e6:8f:31:49:8e:02:70:70:8a:6c:9c:2e:
    6d:73:63:9b:54:fd:a8:29:d0:f7:af:14:f2:80:5c:
    8d:e2:52:ba:b9:03:aa:3d:dd:5a:b9:22:46:de:e3:
    2d:f3:dd:88:1c:ef:f3:49:fa:07:8c:be:83:e4:bf:
    58:55:d0:d6:ae:d4:51:98:1d:0d:8d:2f:4b:3a:b1:
    f9:b1:16:d3:a0:51:b5:1d:1b:d2:4d:60:2d:65:d0:
    8c:8a:f2:a2:7a:2b:fa:7d

如果你想提取该私钥文件的公共部分(公钥),我们可以这样干(rsa):

复制代码
$openssl pkey -in private.key -pubout -out private-public.key

root@ubuntu:/tmp# openssl pkey -in private.key -pubout -out private-public.key
root@ubuntu:/tmp# cat private-public.key 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAohSo4wkwiL+GpRV+rRNL
CsHfKP3BhE/ZYpcO+zN0kh2H/vhHM7vYecuCVFbSpl3PNbi4BVlUPyb5Kk01v3fj
/BXV1AUX0BFtAiymmtzjWjp1ba0Iy6eHmBkG4V4n6LWBp5w1BclHcstBKqG3TCa0
4sQORxgBjqHRz4MM8OXVZBj5jK3BiY3GFNULAYpGKUNoSGLuapVEtgYHjGIZMZLN
O4lSjWADqd2A96MGhqV1M7404tIdtqOUBMRPC1ffZyOWZMLyZJ1ctvpNtJMxasEg
+qIX1SeRhAZlEiZThNvnb4BkW5RIK+Qck7eX3DBitvuMezaeJOuV2YmPTlgYsc13
YwIDAQAB
-----END PUBLIC KEY-----

如果像上面这样-----BEGIN PUBLIC KEY-----开始以-----END PUBLIC KEY-----收尾,说明提取导出公钥成功了(如果没有-pubout参数,那么导出的文件内容其实跟原始的私钥文件是一样的,可以通过cat方式看看BEGIN XXX KEY到底是私钥还是公钥)。

另外一种ECDSA算法的私钥生成流程也是差不多的,只不过需要选择曲线参数,并以此确定私钥的长度(不能随意选择),我们试试(曲线为P-256,也可以叫secp256r1):

复制代码
$openssl genpkey -out private2.key -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -aes-128-cbc

OpenSSL支持很多曲线类型(通过openssl ecparam -list_curves查看支持的种类),但是一般都比较建议使用P-256(别名secp256r1或prime256v1)和P-384(secp384r1)为web server生成密钥文件,前者更为推荐。

目前新加的x25519/x448/ed25519/ed448也被支持,只不过需要加个-algorithm参数进行选择,比如下面的命令会生成ed25519曲线的私钥(有点老的openssl可能不支持这些曲线)。

复制代码
$openssl genpkey -algorithm ed25519

证书签名请求文件CSR

CSR生成

有两种方法,各有千秋,其中差别,自个体会。

命令行方法

私钥准备好了,我们就可以生成一份CSR文件并提供给CA中心得到目标证书了(它包含请求证书的实体的公钥和有关该实体的一些信息。这些数据都将是证书的一部分。CSR总是使用与其携带的公钥相对应的私钥进行签名)。

生成CSR文件(利用私钥文件)的过程需要我们根据其提示输入一些具体信息(以区别其他的CSR文件),这些信息包括国家代码、省份(或州)、公司名称、组织名称、组织单元名称、邮箱地址等等,但是有一点需要注意,这些信息并不是都需要填写的,有些可以为空(为空的我们不能直接按回车键,而是也要输入一个·,否则openssl会自动填充一个默认值,如果你不想使用默认值的话,那就要输入·),我们来试试。

复制代码
root@ubuntu:/tmp# openssl req -new -key private.key -out private.csr
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]:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:GD
Organizational Unit Name (eg, section) []:DR
Common Name (e.g. server FQDN or YOUR name) []:mike
Email Address []:.

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:.
An optional company name []:.
root@ubuntu:/tmp# 

上面我准备的私钥文件不带保护密码,所以没有提示输入密码相关的内容。这里面有个challenge password字段,它是为了吊销证书过程使用的一种保护机制,但一般都不建议使用它,不用管,管了也容易与保护密码(后面还有共享密钥)等概念相混淆,也起不到好的保护作用。

配置文件方法

除了上面这种比较常见的命令行方式外,我们还可以提前将一些填充信息写到配置文件(可以参考freeRADIUS工程中certs文件夹下提供的配置文件示例),然后在生成CSR时读取配置文件就行,这样也方便后期的更新。

官方提供的一个配置文件内容如下(fd.cnf):

然后通过下面的命令生成CSR:

复制代码
$openssl req -new -config fd.cnf -key fd.key -out fd.csr
检查CSR

在我们将CSR文件提交给CA中心生成证书之前,我们最好先检查下CSR的信息是否正确,以免多次提交,浪费时间浪费资源。效果如下:

复制代码
root@ubuntu:/tmp# openssl req -text -in private.csr -noout
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=CN, ST=Shanghai, L=Shanghai, O=GD, OU=DR, CN=mike
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:a2:14:a8:e3:09:30:88:bf:86:a5:15:7e:ad:13:
                    4b:0a:c1:df:28:fd:c1:84:4f:d9:62:97:0e:fb:33:
                    74:92:1d:87:fe:f8:47:33:bb:d8:79:cb:82:54:56:
                    d2:a6:5d:cf:35:b8:b8:05:59:54:3f:26:f9:2a:4d:
                    35:bf:77:e3:fc:15:d5:d4:05:17:d0:11:6d:02:2c:
                    a6:9a:dc:e3:5a:3a:75:6d:ad:08:cb:a7:87:98:19:
                    06:e1:5e:27:e8:b5:81:a7:9c:35:05:c9:47:72:cb:
                    41:2a:a1:b7:4c:26:b4:e2:c4:0e:47:18:01:8e:a1:
                    d1:cf:83:0c:f0:e5:d5:64:18:f9:8c:ad:c1:89:8d:
                    c6:14:d5:0b:01:8a:46:29:43:68:48:62:ee:6a:95:
                    44:b6:06:07:8c:62:19:31:92:cd:3b:89:52:8d:60:
                    03:a9:dd:80:f7:a3:06:86:a5:75:33:be:34:e2:d2:
                    1d:b6:a3:94:04:c4:4f:0b:57:df:67:23:96:64:c2:
                    f2:64:9d:5c:b6:fa:4d:b4:93:31:6a:c1:20:fa:a2:
                    17:d5:27:91:84:06:65:12:26:53:84:db:e7:6f:80:
                    64:5b:94:48:2b:e4:1c:93:b7:97:dc:30:62:b6:fb:
                    8c:7b:36:9e:24:eb:95:d9:89:8f:4e:58:18:b1:cd:
                    77:63
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha256WithRSAEncryption
         5c:0b:c1:fd:da:b1:7c:28:93:85:63:a0:13:db:5b:d3:e2:d1:
         d0:95:d5:b1:61:cf:db:f4:d6:d0:b9:5b:4d:05:0f:fb:41:c3:
         9a:78:e4:29:49:ac:0e:3d:30:26:df:0a:a9:73:9a:60:d7:24:
         f6:87:0b:a2:cf:53:1a:ad:a3:66:40:3c:91:b4:a8:f5:98:65:
         cf:91:30:90:94:11:00:79:f8:e5:9b:70:25:1b:b4:05:20:30:
         da:05:1a:f1:71:e3:07:ed:bf:d8:cb:d4:ed:9d:45:89:b8:6b:
         6d:8a:e2:65:49:2d:c5:54:68:37:9f:e6:5d:89:7d:c7:bb:cc:
         6a:1e:0b:ec:84:9a:25:b9:be:3b:bd:46:80:3b:0c:3f:d7:f9:
         d3:ee:22:99:3c:76:3e:08:a0:dd:ce:40:49:21:bc:bb:67:9f:
         4b:2b:79:54:e6:19:f3:22:de:c9:cd:3c:f4:77:37:de:71:c0:
         c4:74:06:d3:16:b1:3e:71:89:ee:ef:57:2a:c0:23:24:65:80:
         eb:f4:2a:1c:ce:3b:92:44:c1:a2:68:26:b8:06:5d:5a:98:81:
         9a:94:1d:87:20:72:c0:6b:16:1d:4e:82:14:b7:4f:0d:fd:0d:
         07:d4:16:d9:38:b7:9c:3b:90:0d:79:c9:fc:71:7f:ba:a5:44:
         98:25:a5:d0
CSR重生

如果需要更新证书(可能过期或其他原因),按照常规流程,我们得再一次生成私钥,再生成CSR,重新输入各种信息,再提交CSR,但如果之前填写的信息都一样不用变动呢,那么再按流程走稍显繁琐了,此种情况,我们可以通过利用之前的私钥及证书文件重新导出一个全新的CSR文件:

复制代码
openssl x509 -x509toreq -in client.crt -out client.csr -signkey client.key

私钥最好是每次都重新生成一次(毕竟也不麻烦),既增加了安全系数也获得了心安,何乐不为。

一键生成私钥和CSR

上文所介绍的都是按部就班的流程,先生成私钥再根据此私钥生成CSR,那么我们有没有一步到位的命令呢,还真有,下面的命令记好:

复制代码
$openssl req -newkey rsa:2048 -keyout client.key -out client.csr -nodes

注意最后一个参数-nodes,意思是不用保护密码(给私钥),如果需要则将-nodes参数去掉即可。

自签名证书

自签名,顾名思义,自己给自己的CSR文件进行签名。当我们自己在配置TLS server(自用)或想快速测试一些认证功能时,一般都可以使用自签名证书来布置。

我们来用之前生成的private.key和private.csr文件生成我们自签名证书client.crt。

复制代码
root@ubuntu:/tmp# openssl x509 -req -days 365 -in private.csr -signkey private.key -out client.crt
Signature ok
subject=/C=CN/ST=Shanghai/L=Shanghai/O=GD/OU=DR/CN=mike
Getting Private key
root@ubuntu:/tmp# 

root@ubuntu:/tmp# cat client.crt 
-----BEGIN CERTIFICATE-----
MIIDNDCCAhwCCQDrUJJRjOrRSzANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJD
TjERMA8GA1UECAwIU2hhbmdoYWkxETAPBgNVBAcMCFNoYW5naGFpMQswCQYDVQQK
DAJHRDELMAkGA1UECwwCRFIxDTALBgNVBAMMBG1pa2UwHhcNMjQwNDExMDMxNzIw
WhcNMjUwNDExMDMxNzIwWjBcMQswCQYDVQQGEwJDTjERMA8GA1UECAwIU2hhbmdo
YWkxETAPBgNVBAcMCFNoYW5naGFpMQswCQYDVQQKDAJHRDELMAkGA1UECwwCRFIx
DTALBgNVBAMMBG1pa2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi
FKjjCTCIv4alFX6tE0sKwd8o/cGET9lilw77M3SSHYf++Eczu9h5y4JUVtKmXc81
uLgFWVQ/JvkqTTW/d+P8FdXUBRfQEW0CLKaa3ONaOnVtrQjLp4eYGQbhXifotYGn
nDUFyUdyy0EqobdMJrTixA5HGAGOodHPgwzw5dVkGPmMrcGJjcYU1QsBikYpQ2hI
Yu5qlUS2BgeMYhkxks07iVKNYAOp3YD3owaGpXUzvjTi0h22o5QExE8LV99nI5Zk
wvJknVy2+k20kzFqwSD6ohfVJ5GEBmUSJlOE2+dvgGRblEgr5ByTt5fcMGK2+4x7
Np4k65XZiY9OWBixzXdjAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFNpS8j4jNHM
IMVVd4DRT3NUPNTVXGYVn0KfX4F0laO7PDyGRRpJQwFnd01X1ca6xdNJcjCvItZd
EnaQxy+RwhM9cvMVHdiLitZa/UQJ8/wtEnIsbZLIp4VEIjVPk1FArESOXlV9LoUE
qefqJhCQw4IMMv8OD8rK7nNeLnpLhano5xXlXZZgdY61//rD29de1rdna99BWMXs
RChdNJTu8SGoEgYYfOfwQO4hu2Fd/WWwDel1ME8JXNY1esF+Ccg4x+Hy6zcZhQ9t
fpHA0EFr6oltttEli/ZIzsZDhJvjB4Rhpj/b6Ih71rlHqRvddS0vCfLMRKiI2I2i
M5Co7S3VMXg=
-----END CERTIFICATE-----

我们还可以在未准备好CSR文件的情况,去生成自签名证书(记住是自签名哈),命令如下:

复制代码
$openssl req -new -x509 -days 365 -key private.key -out client1.crt

root@ubuntu:/tmp# openssl req -new -x509 -days 365 -key private.key -out client1.crt
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]:

可以看到,还是需要输入一些信息的(这些信息很重要,不可能真正省略嘛),还有另外一个办法一次性将需要的信息填充出来(-subj,借鉴了配置文件的方法),如下:

复制代码
$openssl req -new -x509 -days 365 -key private.key -out client1.crt -subj "/C=CN/L=Shanghai/O=GD/"

创建对多个域名有效的证书

默认情况,一个证书中的common name只对应一个域名,那么如果有个不一样的域名的网站,那就需要另外一个证书(即使这俩网站是同一个网站,比如www.feistyduck.com和feistyduck.com,还有www.blumitek.com和www.blumitech.com这样的情况),理论上这样没错,但实际使用中的确有一些不便,有没有什么办法能让一张证书对应多个主机名呢?

不但有,还有两种方法。

第一种方法是使用名为Subject Alternative Name(SAN)的X.509扩展名列出所有需要的主机名(域名)。第二种是使用通配符。在更方便的时候,也可以使用这两种方法的组合。在实践中,对于大多数网站,我们可以指定一个裸域名和一个通配符来覆盖所有子域。

演练一下。

我们创建一份文本文件fd.ext,里面写上所有可能的域名,再结合下通配符,内容如下:

复制代码
subjectAltName = DNS:*.feistyduck.com, DNS:feistyduck.com

我们再来使用x509命令去生成一张证书,但是我们要添加-extfile参数来指定读取ext文件:

复制代码
$openssl x509 -req -days 365 -in private.csr -signkey private.key -out client.crt -extfile fd.ext

我们在证书生成后检查下内容信息有什么变化。

查看证书

证书可不像私钥文件(文本)那样可以直接用cat方式浏览,但我们可以使用x509命令进行查看。

复制代码
$ openssl x509 -text -in fd.crt -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            76:bc:fb:f6:06:0e:61:eb:99:5e:83:ea:ef:92:0b:32:4f:fd:3b:51
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = GB, L = London, O = Feisty Duck Ltd, CN = www.feistyduck.com
        Validity
            Not Before: Aug 15 09:31:54 2020 GMT
            Not After : Aug 15 09:31:54 2021 GMT
        Subject: C = GB, L = London, O = Feisty Duck Ltd, CN = www.feistyduck.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:8a:d5:de:69:30:c7:77:b0:a0:54:f7:b3:34:9a:
                    96:1c:23:81:e3:9c:0c:81:a6:8a:a5:14:76:f4:4c:
                    b3:10:cb:ee:50:d1:ea:70:e9:7f:8f:75:67:f9:12:
                    83:b0:11:e7:6c:64:de:bc:af:bd:3f:43:da:b8:41:
                    96:75:34:63:85
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                DNS:*.feistyduck.com, DNS:feistyduck.com
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:20:4d:36:34:cd:e9:3e:df:18:52:e7:74:c4:a1:97:
         91:6a:e7:c1:6d:12:01:63:d1:fd:90:28:32:70:24:5c:be:35:
         02:21:00:bd:02:64:c9:8b:27:8f:79:c7:a4:41:7c:31:2f:98:
         29:3e:db:8c:f3:f1:d7:bb:fa:fe:95:48:be:16:e1:ab:1b

有看到下面的内容吧。

自签名证书仅仅包含了证书数据的最基本部分,一般来说证书主体里面是要添加签名信息的。而来自CA中心颁发的证书则包含了更多的信息(通过X509扩展机制)。

检查公共证书

下面是一个公共证书的部分内容(此一节内容全翻译自原文):

复制代码
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            03:5e:50:53:75:08:1a:f2:7d:27:64:4f:d5:6f:1a:02:07:89
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
        Validity
            Not Before: Aug  2 23:10:45 2020 GMT
            Not After : Oct 31 23:10:45 2020 GMT
        Subject: CN = www.feistyduck.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:c8:14:4f:33:9a:db:bb:e7:e3:78:93:46:5d:56:
                    a7:bc:58:86:43:dc:ea:c1:01:52:4b:0f:20:b7:38:
                    [...]
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            [...]

可以看到其与自签名证书的内容看起来差不多,但由CA中心颁发的公共证书与自签名证书还是有几点区别,一个是拥有不同的父级(如颁发者信息所示Issuer),还有一个就是X.509扩展部分。

非CA颁发的证书,字段X509v3 Basic Constraints可能有可能无,如果有,它应该是这样:

可以看到,CA:FALSE。

密钥使用(KU)和扩展密钥使用(EKU)扩展限制了证书的用途。如果存在这些扩展,则只允许列出的用途。如果扩展不存在,则没有使用限制。在上图看到的是典型的web服务器证书,例如,它不允许代码签名。

CRL分发点扩展列出了可以在其中找到CA的证书吊销列表(CRL)信息的地址。在需要吊销证书的情况下,此信息非常重要。CRL是CA签署的已吊销证书列表,每隔一定时间(例如七天)发布。

证书策略扩展用于指示颁发证书所依据的策略。例如,我们可以在这里找到用于确定所有者身份的验证类型的指示。可以找到扩展验证(EV)指标(如上图所示)。这些指示符采用唯一对象标识符(OID)的形式,其中一些是通用的,另一些是颁发CA特有的。OID 2.23.140.1.2.1表示域验证证书。此外,此扩展通常包含一个或多个证书实践声明(CPS)点,这些点通常是网页或PDF文档。

授权信息访问(AIA)扩展通常包含两条重要信息。首先,它列出了CA的在线证书状态协议(OCSP)响应程序的地址,该响应程序可用于实时检查证书吊销。扩展还可能包含一个链接,指向可以找到颁发者证书(链中的下一个证书)的位置。如今,服务器证书很少由受信任的根证书直接签名,这意味着用户必须在其配置中包含一个或多个中间证书。错误很容易犯,会使证书失效。一些客户端将使用此扩展中提供的信息来修复不完整的证书链,但许多客户端不会。

主题密钥标识符和权威密钥标识符扩展分别建立唯一的主题和权威密钥标识。证书的颁发机构密钥标识符扩展中指定的值必须与颁发证书的使用者密钥标识符扩展指定的值匹配。这些信息在证书路径构建过程中非常有用,在这个过程中,客户端试图找到从叶(服务器)证书到受信任根的所有可能路径。证书颁发机构通常会将一个私钥与多个证书一起使用,该字段允许软件可靠地识别哪个证书可以与哪个密钥匹配。在现实世界中,服务器提供的许多证书链都是无效的,但这一事实经常被忽视,因为浏览器能够找到替代的信任路径。

Subject Alternative Name扩展名用于列出证书有效的所有主机名(域名)。这个扩展过去是可选的;如果不存在,客户端将返回使用公共名称(CN)中提供的信息,该名称是Subject字段的一部分。如果存在扩展,则在验证过程中会忽略CN字段的内容。

最后,最新添加的是证书透明性(CT)扩展,用于将日志记录证明携带到各种公共CT日志中。根据证书的生存期,您可以看到两到五个签名证书时间戳(SCT)。对于承认证书有效所必需的SCT的数量和类型,没有一套统一的要求。从技术上讲,这取决于每个客户指定他们期望的内容。在实践中,Chrome是第一个需要CT的浏览器,其他客户端可能也会效仿它。

密钥和证书的格式转换

在刚开始接触证书认证的时候,百度了很多内容,尤其是被各种乱七八糟的格式搞自闭,动不动就报证书格式问题,根据百度到的转换命令强制转换 之后依然是各种问题,人生至郁时刻。

证书格式

Binary(DER)

包含使用DER ASN.1编码的原始形式的X.509证书。

ASCII (PEM)

包含使用base64编码的DER证书,以-----BEGIN CERTIFICATE-----开头以-----END CERTIFICATE-----收尾,在一个文件只包含一份证书的这种情况下比较常见(尽管一些程序允许一个文件可以包含多份证书)。举个例子,较旧的Apache web服务器版本要求服务器证书单独存在于一个文件中,所有中间证书一起存在于另一个文件。

PKCS #7

RFC 2315中定义的一种为传输签名或加密数据而设计的复杂格式。它通常与.p7b和.p7c扩展一起使用,并且可以根据需要包括整个证书链。Java的keytool实用程序支持这种格式。

PKCS #12 (PFX)

见下文解释,主要是Windows平台在用,比如在window server上生成导出密钥和证书时,可能会将二者共同导出到一个文件中(可选加密密码),这个文件就是.pfx后缀的。

密钥格式

旧版OpenSSL

包含一个原始形式的私钥,使用DER ASN.1编码。从历史上看,OpenSSL使用基于PKCS#1的格式。现在,如果使用正确的命令(即genpkey),OpenSSL默认为PKCS#8。

ASCII (PEM)

包含一个base64编码的DER密钥,有时还包含额外的元数据(例如,用于密码保护的算法)。页眉和页脚中(-----xxx-----这个东西)的文本可能不同,具体取决于所使用的基本密钥格式。

PKCS #8

私钥存储的新默认格式(刚开始我们生成的私钥就是这种格式)。PKCS#8是在RFC 5208中定义的。如果出于任何原因需要从PKCS#8转换为传统格式,请使用pkcs8命令。

PKCS #12 (PFX)

一种复杂的格式,可以存储和保护服务器密钥以及整个证书链。它通常与.p12和.pfx扩展名一起使用。此格式通常用于Microsoft产品,但也用于客户端证书。如今,PFX名称被用作PKCS#12的同义词,尽管PFX在很久以前引用了一种不同的格式(PKCS#11的早期版本)。你不太可能在任何地方遇到旧版本。

格式转换

PEM-DER

这两种格式的证书我们使用x509命令工具进行转换。

PEM->DER

复制代码
$ openssl x509 -inform PEM -in fd.pem -outform DER -out fd.der

DER->PEM

复制代码
$ openssl x509 -inform DER -in fd.der -outform PEM -out fd.pem

对于这两种格式的私钥互转的语法也是一样的,只是针对不同加密算法的密钥我们要使用不同的命令(替换x509),如rsa(RSA密钥)和dsa(DSA密钥)。如果是PKCS #8格式的密钥呢可以使用pkey命令。

PKCS #12 (PFX)

将PEM格式的密钥和证书转换为PKCS#12只需要一个命令。以下示例将密钥(fd.key)、证书(fd.crt)和中间证书(fd-chain.crt)转换为等效的单个PKCS#12文件:

复制代码
$ openssl pkcs12 -export -name "My Certificate" -out fd.p12 -inkey fd.key -in fd.crt -certfile fd-chain.crt

反向转换则比较麻烦了。可以使用一个命令,首先得到一个包含全部内容的文件:

复制代码
$ openssl pkcs12 -in fd.p12 -out fd.pem -nodes

然后我们需要手动打开fd.pem文件(比如可以通过gedit或者notepad++),将其拆分为单独的密钥、证书和中间证书文件,在每部分内容之前我们会看到一些额外的信息,比如像下面这样的:

复制代码
Bag Attributes
    localKeyID: E3 11 E4 F1 2C ED 11 66 41 1B B8 83 35 D2 DD 07 FC DE 28 76
subject=/1.3.6.1.4.1.311.60.2.1.3=GB/2.5.4.15=Private Organization/serialNumber=06694169/C=GB/ST=London/L=London/O=Feisty Duck Ltd/CN=www.feistyduck.com
issuer=/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://certificates.starfieldtech.com/repository/CN=Starfield Secure Certification Authority
-----BEGIN CERTIFICATE-----
MIIF5zCCBM+gAwIBAgIHBG9JXlv9vTANBgkqhkiG9w0BAQUFADCB3DELMAkGA1UE
BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAj
[...]

这个额外的元数据对于快速识别证书非常方便。显然,我们应该确保主证书文件包含叶服务器证书,而不是其他内容。此外,还应确保以正确的顺序提供中间证书,并在已签名证书之后提供颁发证书。如果看到一个自签名的根证书,则应删除它或将其存储在其他地方,它不应该被包含进入证书链。(是不是很有挑战力度++)

除了手动操作外,我们也可以利用openssl提供的命令来分离,下面的三行命令即分别分离出私钥文件fd.key、证书文件fd.crt、中间链证书fd-chain.crt:

复制代码
$ openssl pkcs12 -in fd.p12 -nocerts -out fd.key -nodes
$ openssl pkcs12 -in fd.p12 -nokeys -clcerts -out fd.crt
$ openssl pkcs12 -in fd.p12 -nokeys -cacerts -out fd-chain.crt

虽然通过命令可以比较方便的进行分离提取,但是我们还是要仔细检查每一份文件,如果有多余的元数据在里面(比如自签名证书),我们要当机立断删除它们。

PKCS #7

PEM->PKCS #7

复制代码
$ openssl crl2pkcs7 -nocrl -out fd.p7b -certfile fd.crt -certfile fd-chain.crt

PKCS #7->PEM

复制代码
$openssl pkcs7 -in fd.p7b -print_certs -out fd.pem

与PKCS #12 (PFX)的转换一样,我们还是需要手动检查fd.pem以剔除不需要的信息。

关于密钥和证书方面的内容,本篇大致陈述这么些。

相关推荐
XMYX-02 小时前
解决 Apache/WAF SSL 证书链不完整导致的 PKIX path building failed 问题
网络协议·apache·ssl
少陽君1 天前
什么是CA根证书
服务器·https·ssl
梦想blog1 天前
漏洞修复 Nginx TLSSSL 弱密码套件
运维·nginx·ssl·漏洞修复·tlsv1.2
yong15858553431 天前
利用 openssl api 实现 TLS 双向认证
linux·ssl
深耕AI2 天前
Win64OpenSSL-3_5_2.exe【安装步骤】
openssl
XYiFfang3 天前
【Python+requests】解决Python requests中的ProxyError:SSL版本错误问题详解
python·debug·ssl·常见错误·代理配置
看那山瞧那水3 天前
DELPHI 利用OpenSSL实现加解密,证书(X.509)等功能
delphi·openssl
Linux运维技术栈5 天前
Linux系统部署:Certbot 实现 Nginx 自动续期&部署 Let‘s Encrypt 免费 SSL 证书
linux·运维·nginx·ssl·certbot
m0_474606786 天前
Nginx + Certbot配置 HTTPS / SSL 证书(简化版已测试)
nginx·https·ssl
神色自若8 天前
AbpVnext 阿里云ssl证书多个生产环境自动更新
服务器·阿里云·ssl