TOC

使用 openssl 创建自签名证书

OpenSSL LOGO

证书与公钥基础设施

证书,就是一个用来加密的东西。

  1. 数据整体加密,使内容难以被第三方窥探。
  2. 关键部位提取出来加密,生成一个签名,附加在数据上,用于确保原数据未被篡改,
    发送方也无法抵赖。

证书的作用如此之大,如果制作方不中立,很难令人信服。
所以有一套完善的机制来保证证书的有效性,可靠性,这就是我们的公钥基础设施(Public Key Infrastructure, PKI)。
其中最为关键的是证书颁发机构(Certification Authority, CA),负责颁发证书、认证证书、管理已颁发证书。
CA 自己创建一个证书(公钥 + 私钥),信任这个 CA 的人使用它的证书来签名和验证自己的证书,一级一级的传递下来。
如果需要一个大家都信任的证书,就需要向一个大家都信任的 CA 申请,提交身份信息 + 密钥。
CA 确认之后,使用私钥为之签名,然后这个身份信息 + 颁发者信息(序列号) + 公钥 + 算法 + 签名 + 有效期等,合在一起就是一个证书了。
如果其他人需要确认这个证书的真假,就拿 CA 的公钥(公开的)来校验一下,通过之后就认为其有效。

PKI 的这一整套机制,全部遵循国际电信联盟制定与颁发的 X.509 标准。
几乎 IT 届所有通讯技术中使用的加密都离不开这一套(还有一套机制,就是 PGP 体系的分布式信任模型 Web of Trust)。

  • PEM 编码格式,就是常见的 -----BEGIN XXX----- + base64 + -----END XXX-----
  • DER 编码格式,二进制
  • PFX 参考下面说的 PKCS#12, 公钥 / 私钥 / 证书,好像是用于安全转移
    通常用它来打包一个私钥及有关的 X.509 证书,或者打包信任链的全部项目。
    其文件常用 .p12 后缀或 .pfx 后缀
  • JKS Java Key Store, 私钥 + 证书,常用 Java Keytool 做合成或提取
  • CER / CRT 常见后缀,表示证书
  • PKCS The Public-Key Cryptography Standards,RSA 公司设计的公钥加密标准,目前共有 15 部分,其中:
    • PKCS#1 RSA Cryptography Standard v2.2, RFC8017
    • PKCS#3 Diffie–Hellman Key Agreement Standard v1.4
    • PKCS#5 Password-based Encryption Standard v2.1, RFC8018, PBKDF2
    • PKCS#8 Private-Key Information Syntax Standard v1.2, RFC5958
    • PKCS#10 Certification Request Standard v1.7, RFC2986
    • PKCS#12 Personal Information Exchange Syntax Standard v1.1 RFC7292
      似乎是根据微软的 PFX 格式发展过来的,现在可以把 PFX 看做 PKCS#12 的同义词

一般的习惯:

  1. 公钥和私钥,用 .key 后缀
  2. 证书一般采用 .cer 或 .crt 做后缀
  3. 不管是公钥、私钥,还是证书,都有按照保存格式,使用 .pem 或 .der 后缀的
  4. 还有合并存储的可能,最简单的就是 cat ca.key ca.crt > ca.pem
    至少 Python 的 SSL 库加载证书时,支持私钥和证书合并存储在同一个文件

什么是自签名

模拟 PKI 机制,先创建一个证书,将其作为根证书。然后使用这个根证书给其他新创建的证书做签名,形成一个证书链。
这种自己给自己背书的行为,就叫自签名,得到的签名证书就叫自签名证书。

一般,我们在 Socket 编程中,使用 TLS 的场景,似乎都不对证书做校验,只要求两边都有证书就行了。
这种情况下,自签名似乎是个脱裤子放屁的事情,直接生成 CA 证书使用就行了。

但是也有很多场景,就是应用层的互联网协议,一般需要完整的 PKI 支持,比如浏览器(HTTPS),会逐层校验证书合法性。
这个日后我会再写一篇 HTTPS 通信方面的文章,对这套 PKI 机制的全流程应用做一个简单梳理。

Step 1: Create 2048 Bit RSA Key

生成一个 2048 位 RSA 密钥(看完发现,其实这一步可以省略)

# 私钥 server.key
openssl genrsa -out server.key 2048
# 公钥 server_pub.key
openssl rsa -in server.key -pubout -out server_pub.key
# 查看详情
openssl rsa -noout -text -in server.key
openssl rsa -noout -text -in server_pub.key -pubin

# PKCS#1 => PKCS#8
openssl pkcs8 -topk8 -in server.key -out server_pk8.key -passout pass:create_rsa_key  # 默认需要加密
openssl pkcs8 -topk8 -in server.key -out server_pk8.key -nocrypt  # 不要加密

PS:公钥可以公开,私钥不可泄露

加密(可选)

# man genrsa or man openssl-genrsa
# -aes128, -aes192, -aes256, -aria128, -aria192, -aria256, -camellia128, -camellia192, -camellia256, -des, -des3, -idea
openssl genrsa -aes256 -passout pass:create_rsa_key -out server.key 2048
openssl rsa -in server.key -pubout -out server_pub.key -passin pass:create_rsa_key

# 私钥加密
openssl rsa -in server.key -out server_passwd.key -passin pass:create_rsa_key -aes256
# 加密私钥去掉密码
openssl rsa -in server_passwd.key -out server.key -passin pass:create_rsa_key

Step 2: 创建签名请求 Create Certificate Sign Request

server.csr(请求)
如果不传入一个密钥文件,则会自动生成一个,默认文件名为 privkey.pem

openssl req -new -keuy server.key -out server.csr

免输入:

openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=who@markjour.com" -passout pass:create_csr

# 免加密
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=who@markjour.com" -nodes

# 使用新的密钥
openssl req -new -out server.csr -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=who@markjour.com" --nodes -keyout server_generated.key

查看详情:

openssl req -noout -text -in server.csr

Step 3: 创建自签名证书 Create Self-Signed Certificate

我们自己做 CA,所以需要一个 CA 证书:ca.crt(证书)和 ca.key(密钥)

# 证书请求
#       openssl req
# 使用新的密钥(RSA2048),默认格式:PKCS8
#       -newkey rsa:2048 -keyout ca.key -passout pass:xxx
# 证书相关
#       -x509 -days 365 -subj "xxx"
openssl req \
    -newkey rsa:2048 -keyout ca.key -passout pass:create_crt \
    -out ca.crt -x509 -days 365 -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=signer@markjour.com"

# 如果不想加密,可以使用 -nodes(no des)
openssl req \
    -newkey rsa:2048 -keyout ca.key -nodes \
    -out ca.crt -x509 -days 365 -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=signer@markjour.com"

# 使用已有私钥
openssl req \
    -new -key server.key -nodes \
    -out ca.crt -x509 -days 365 -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=signer@markjour.com"

# -new 参数不要似乎也行
openssl req \
    -keyout ca.key -nodes \
    -out ca.crt -x509 -days 365 -subj "/C=CN/ST=Hubei/L=Wuhan/O=markjour.com/OU=R&D/CN=Hu Ang/emailAddress=signer@markjour.com"

Step 4: 自签名 Self Sign CSR

自己做 CA,给自己的请求签名:

# -signkey val    Self sign cert with arg
# 很奇怪,通过密钥怎么得到的 CA 信息
openssl x509 -req -days 365 -in server.csr \
    -signkey ca.key \
    -out server.crt -passin pass:create_crt

# 还看到一处这样调用,效果似乎没有差别:
# -CA infile      Set the CA certificate, must be PEM format
# -CAkey val      The CA key, must be PEM format; if not in CAfile
# -CAcreateserial Create serial number file if it does not exist
openssl x509 -req -days 365 -in server.csr \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out server.crt -passin pass:create_crt

一个证书(X.509 Certificate)这就好了:server.crt

查看详情:

openssl x509 -in server.crt -noout -text

其他

# 格式转换,默认 PEM,所以所有 PEM 可以不写
openssl rsa -inform PEM -outform DER -in server.key -out server.der
openssl x509 -inform PEM -outform DER -in cert.pem -out cert.der
openssl x509 -inform DER -outform PEM -in cert.cer -out cert.pem
# PEM => PFX
openssl pkcs12 -export -inkey privateKey.key -certfile CACert.crt -in cert.pem -out cert.pfx
# PFX to PEM (证书和私钥在一起?)
openssl pkcs12 -in cert.pfx -out cert.cer -nodes

# 证书 + 私钥,合并成 PKCS#12 证书
openssl pkcs12 -inkey ca.key -in ca.crt -export -out ca.pfx --passout pass:pfx3000

openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx
openssl pkcs12 -export -in server.crt -inkey server.key -passin pass:111111 \
    -password pass:111111 -out server.pfx
# 证书链:加上上级证书
# -name 指定 server.crt 别名
# -name 指定 ca.crt 别名
openssl pkcs12 -export -in server.crt -inkey server.key -passin pass:111111 \
    -chain -CAfile ca.crt \
    -password pass:111111 -out server-all.pfx
# 从 PKCS#12 导出
openssl pkcs12 -in server.pfx -password pass:111111 -passout pass:111111 -out out/server.pem
openssl pkcs12 -in server.pfx -password pass:111111 -passout pass:111111 -nocerts -out out/server.key
openssl pkcs12 -in server.pfx -password pass:111111 -nokeys -out out/server.key
openssl pkcs12 -in server-all.pfx -password pass:111111 -nokeys -cacerts -out out/ca.crt
openssl pkcs12 -in server-all.pfx -password pass:111111 -nokeys -clcerts -out out/server.crt

# 查看 DER 证书
openssl x509 -in server.der -inform der -noout -text

PuTTY 的 ppk 格式

PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: imported-openssh-key
Public-Lines: 6
<-- base64 lines -->
Private-Lines: 14
<-- base64 lines -->
Private-MAC: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 生成 PPK 文件
# 支持的密钥类型(-t):ed25519, ecdsa, rsa, dsa, rsa1
# RSA1 好像是为 SSHv1 准备的
puttygen -t rsa -b 2048 -C 'just for ppk testing' -o markjour.ppk -q

# 支持的输出类型(-O):
# private             output PuTTY private key format
# private-openssh     export OpenSSH private key
# private-openssh-new export OpenSSH private key (force new format)
# private-sshcom      export ssh.com private key
# public              RFC 4716 / ssh.com public key
# public-openssh      OpenSSH public key
# fingerprint         output the key fingerprint
puttygen markjour.ppk -O private-openssh -o markjour-id_rsa # 提取私钥
puttygen markjour.ppk -O public-openssh -o markjour-id_rsa.pub # 提取公钥
puttygen markjour.ppk -O public-openssh # 输出公钥
puttygen markjour.ppk -O fingerprint # 输出指纹

疑惑:为什么 ppk 的私钥部分只有 PEM 私钥的一半?
利用公钥部分做校验,压缩了体积么?

附件

参考资料与拓展阅读