生成数字证书

使用 OpenSSL 和 JDK 中的 keytool 工具生成数字证书

Posted by mingfer on April 20, 2019

不管是在配置 HTTPS 还是其他需要使用 SSL/TLS 协议的场景中,都需要我们提供证书文件。

本文主要介绍如何使用 OpenSSL 和 JDK 中的 keytool 生成证书文件,以及 JKS 格式文件和 CRT/PKCS#12 格式的文件之间的转换。

使用 OpenSSL 生成非对称密钥

证书实际上是由非对称算法进行安全保障的,在介绍证书的生成之前。我们先了解一下如何通过 OpenSSL 生成非对称密钥。OpenSSL 支持生成 RSA 和 ECC 两种算法的非对称密钥,但是生成这两种密钥的命令并不一样。

生成 RSA 非对称密钥

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
# 生成一把 2048 bit 长度的 RSA 私钥
$ openssl genrsa -out rsa.key 2048

# 查看私钥内容
$ openssl rsa -in rsa.key -noout -text

# 从私钥中导出公钥
$ openssl rsa -in rsa.key -pubout -out public.key

# 查看公钥内容
$ openssl rsa -pubin -in public.key -noout -text

# 使用 aes256 加密私钥
$ openssl rsa -in rsa.key -aes256 -passout pass:123456 -out cipher.key

# 解密成明文私钥
$ openssl rsa -in cipher.key -aes256 -passin pass:123456 -out plaintext.key

# 生成 aes256 算法加密加密的私钥
$ openssl genrsa -aes256 -passout pass:111111 -out rsa.key 2048

# 从加密的私钥中导出公钥
$ openssl rsa -in cipher.key -passin pass:123456 -pubout -out public.key

# 从 PER 格式转换为 DER 格式
$ openssl rsa -in rsa.key -outform der -out rsa-der.der

# 将 PKCS#1 格式的密码转换为密文的 PKCS#8
$ openssl pkcs8 -topk8 -in rsa.key -passout pass:123456 -out pkcs8-cipher.key

# 将 PKCS#1 格式的密码转换为明文的 PKCS#8
$ openssl pkcs8 -topk8 -in rsa.key -nocrypt -out pkcs8-plaintext.key

注意:DER 格式和 PEM 格式其实只是存储内容编码上的区别,DER 是直接以字节的方式存储到文件(查看文件内容是乱码的),PEM 是将 Base64 编码后的内容存放到文件(查看文件内容是可见的 Base64 字符串)。

生成 ECC 非对称密钥

1
2
3
4
5
6
7
8
# 查看 OpenSSL 支持那些 ECC 曲线
$ openssl ecparam -list_curves

# 生成一把 ECC 非对称密钥
$ openssl ecparam -out ecc.key -name prime256v1 -genkey

# 查看私钥内容
$ openssl ecparam -in ecc.key -noout -text

使用 OpenSSL 生成 RSA 证书

生成自签发根证书

生成一个自签名的 2048 bit 的 RSA 算法(-newkey rsa:2048)自签发根证书 root.crt,有效期为一年 -days 365,并存储私钥到文件 root.key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ openssl req -newkey rsa:2048 -nodes -keyout root.key -x509 -days 365 -out root.crt

Generating a RSA private key
............................................................................+++++
....+++++
writing new private key to 'root.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]:SH
Locality Name (eg, city) []:SH
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:your.domain.com
Email Address []:[email protected] 

如上所示,在生成证书的过程中,会要求用户输入证书的 DN 信息,如 Country Name 等,这里按照自己的实际 DN 填写即可。使用 OpenSSL 生成证书的时候,对应的 DN 域如果没有填写会默认使用 [] 中的值,如不填写 Country Name 会使用默认值 AU

当然,OpenSSL 中也提供了直接指定 DN 信息的参数 -subj

1
$ openssl req -newkey rsa:2048 -nodes -keyout root.key -x509 -days 365 -out root.crt -subj "/C=CN/ST=SH/L=SH/O=vihoo/OU=Internet Widgits Pty Ltd/CN=your.domain.com/[email protected]"

此外,OpenSSL 还可以通过一把指定的私钥生成自签发证书:

1
$ openssl req -new -x509 -days 365 -key rsa_private.key -out cert.crt
  • -new 是指生成新的证书
  • -x509 是指输出 X.509 格式的证书
  • -key 用于指定私钥文件,如果私钥文件有密码需要使用 -passin pass:123456 指定私钥文件的保护密码

使用根证书签发证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 生成一对 RSA 密钥
$ openssl genrsa -out leaf.key 2048

# 生成用于签发的证书请求文件,和生成自签发证书一样,这里也需要填写 DN 信息
$ openssl req -new -key leaf.key -out leaf.csr

# 用指定的 DN 信息生成证书请求文件
$ openssl req -new -key leaf.key -out leaf.csr -subj "/C=CN/ST=SH/L=SH/O=vihoo/OU=Internet Widgits Pty Ltd/CN=leaf.domain.com/[email protected]"

# 查看请求文件中的信息
$ openssl req -in leaf.csr -noout -text

# 使用根证书 root.crt 签发证书请求文件 leaf.csr 得到叶子证书 leaf.crt
# 如果需要 root.key 是加密的,那么使用 -passin pass:123456 指定密码
$ openssl x509 -req -days 3650 -in leaf.csr -CA root.crt -CAkey root.key -CAcreateserial -out leaf.crt

# 查看证书内容
$ openssl x509 -in leaf.crt -noout -text

转换证书格式

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
# PEM 格式的证书转换的 DER 格式
$ openssl x509 -in leaf.crt -inform PEM -outform DER -out leaf.cer

# DER 格式的证书转换为 PEM 格式
$ openssl x509 -in leaf.cer -inform DER -outform PEM -out leaf.crt

# 将证书和私钥以 PKCS#12 格式合并到一起
$ openssl pkcs12 -export -in leaf.crt -inkey leaf.key -password pass:123456 -out leaf.p12

# 将整个证书链和私钥以 PKCS#12 格式合并到一起
$ openssl pkcs12 -export -in leaf.crt -inkey leaf.key -chain -CAfile root.crt -password pass:123456 -out leaf-all.p12

# 将 PKCS#12 转换为 PEM 格式
$ openssl pkcs12 -in leaf.p12 -password pass:123456 -passout pass:123456 -out leaf.pem

# 导出 PKCS#12 中的私钥
$ openssl pkcs12 -in leaf.p12 -password pass:123456 -passout pass:123456 -nocerts -out leaf-cipher.key

# 导出 PKCS#12 中的所有证书
$ openssl pkcs12 -in leaf.p12 -password pass:123456 -nokeys -out leaf-cert-all.crt

# 导出 PKCS#12 中的客户端证书
$ openssl pkcs12 -in leaf.p12 -password pass:123456 -nokeys -clcerts -out leaf.crt

# 导出 PKCS#12 中的 CA 证书
$ openssl pkcs12 -in leaf.p12 -password pass:123456 -nokeys -cacerts -out root.crt

使用 OpenSSL 生成 ECC 证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 生成一对 ECC 密钥
$ openssl ecparam -out ecc-root.key -name prime256v1 -genkey

# 生成根证书的请求文件
$ openssl req -key ecc-root.key -new -out ecc-root.req

# 直接在命令中指定 DN 生成请求文件
$ openssl req -key ecc-root.key -new -out ecc-root.req -subj "/C=CN/ST=SH/L=SH/O=vihoo/OU=Internet Widgits Pty Ltd/CN=your.domain.com/[email protected]"

# 签发一个 ECC 自签发证书作为根证书
$ openssl x509 -req -in ecc-root.req -signkey ecc-root.key -out ecc-root.crt

# 生成客户端证书的 ECC 密钥对
$ openssl ecparam -out ecc-leaf.key -name prime256v1 -genkey

# 生成客户端证书的请求文件
$ openssl req -key ecc-leaf.key -new -out ecc-leaf.req

# 使用根证书签发客户端证书
$ openssl x509 -req -days 3650 -in ecc-leaf.req -CA ecc-root.crt -CAkey ecc-root.key -CAcreateserial -out ecc-leaf.crt

ECC 证书和 RSA 证书不同的地方只是在密钥对上,所以签发证书和打包 PKCS#12 格式的证书和 RSA 证书是一样的。

KeyTool 生成密钥和证书

keytool 是个密钥和证书管理工具。它提供了管理公私钥对和相关证书的功能,以及用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性校验的服务。在 JDK 1.4 以后的 JDK 版本中都包含了这一工具,它的位置在 $JAVA_HOME/bin/keytool 。keytool 目前不支持 ECC 算法,而且因为出口限制的原因可能一些比较长的密钥长度或高级算法也不支持。

本质上 keytool 管理的是一个密钥仓库,这个仓库文件里面可以存各种密钥和证书。

生成证书文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 通过交互的方式生成
$ keytool -genkey -alias root -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore root.jks -storepass 123456

# 一次性指定 DN 生成证书
$ keytool -genkey -alias root -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore root.jks -storepass 123456 -dname "C=CN, ST=SH, L=SH, O=vihoo, OU=Internet Widgits Pty Ltd, CN=leaf.domain.com/[email protected]"

# 从上面的 KeyStore 生成 CSR 文件
$ keytool -certreq -keyalg RSA -alias root -keystore root.jks -storetype JKS -storepass 123456 -file leaf-jks.csr

# 签发 CSR 文件,使用 -rfc 可以生成 PEM 格式的证书,不指定生成 DER 格式的证书
$ keytool -gencert -alias root -keystore ca.jks -storepass 123456 -infile leaf-jks.csr -outfile leaf-jks.cer

# 直接查看证书内容
$ keytool -list -v -keystore root.jks -storepass 123456
$ keytool -list -rfc -keystore root.jks -storepass 123456
  • -alias 是指生成的密钥的别名
  • -keypass 保护密钥的密码
  • -keyalg 密钥的算法
  • -validity 证书的有效时间
  • -keystore 存储证书和密钥对的文件名和路径
  • -storepass 保护存储文件的密码
  • -dname 用于指定生成证书的 DN 值
  • -v 以可理解的内容打印证书
  • -rfc 以编码的格式打印证书

KeyStore 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 从 KeyStore 导出 DER 证书
$ keytool -exportcert -alias root -file test.cer -keystore root.jks -storepass 123456

# 从 KeyStore 导出 PEM 证书
$ keytool -exportcert -rfc -alias root -file test.crt -keystore root.jks -storepass 123456

# 从 KeyStore 删除证书
$ keytool -delete -alias root -keystore root.jks -storepass 123456

# 导入证书到 KeyStore
$ keytool -exportcert -alias root -keystore root.jks -file root.crt -storepass 123456

# 查看证书内容
$ keytool -printcert -file test.crt

当我们需要发送 CA 证书给客户的时候,可以通过证书导出命令导出 CA 证书。

P12 和 JKS 转换

PKCS#12 转 JKS:

1
$ keytool -importkeystore -srckeystore leaf.p12 -srcstorepass 123456 -srcstoretype PKCS12 -deststoretype JKS -destkeystore leaf.jks -deststorepass 123456

JKS 转 PKCS#12:

1
$ keytool -importkeystore -srckeystore leaf.jks -srcstorepass 123456 -srcstoretype JKS -deststoretype PKCS12 -destkeystore leaf.p12 -deststorepass 123456