PKI数字签名之ECC加密证书实战

应用场景:公司需求将所有与中间件的通信都采用PKI数字签名的方式进行加密通信,其中包括RabbitMQ、ES、与车端Netty通信、Nginx、WebSocket等加密通信,本篇文章介绍数字签名证书生成方式之ECC加密算法生成证书原理与操作,经过不断的探索,摸索出一套完整可用的操作文档,记录知识,传递价值。

概述

PKI指的是Public Key Infrastructure,‌即公钥基础设施。‌这是一种利用公钥加密技术为电子商务的开展提供一套安全基础平台的技术和规范。‌PKI的主要目的是管理密钥和证书,‌通过采用公钥加密和数字签名服务,‌建立一个安全的网络环境。‌它能够为所有网络应用提供加密和数字签名等密码服务及所必需的密钥和证书管理体系。‌简单来说,‌PKI就是利用公钥理论和技术建立的提供安全服务的基础设施,‌用户可利用PKI平台提供的服务进行安全的电子交易、‌通信和互联网上的各种活动。

关注公众号【可为编程】回复【加群】进入技术群一起成长学习!!!

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

ECC加密算法具有高安全性、低消耗、运算速度快的特点,在数字签名领域有良好的应用前景,那么我们今天就来探讨一下ECC加密算法在数字签名中的应用。ECC算法,目前主要用于签名/验签和生成DH会话秘钥。下面的表格展示了在相同的安全登记下,ECC和RSA秘钥长度的对比:

RSA size(in bits)

ECC size(in bits)

1024

160

2048

224

3072

256

7680

384

管理证书:certmgr.msc

列出可用ECC曲线:openssl ecparam -list_curves

(注意这里需要在Linux系统中安装openssl开源组件)

我们使用 prime256v1 生成ECC秘钥对。

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

1、使用ECC key 生成CA 证书

我们需要使用CA证书来签名服务器和客户端的证书,使用ECC私钥生成CA 根证书,为此我们创建几个目录,用来存储CA证书,秘钥,索引数据。

注意:以上目录创建在/root/ECC/tls目录下。

代码语言:javascript
复制
$mkdir /root/ECC/tls
$cd /root/ECC/tls
$mkdir certs private
$echo 00 > /root/ECC/serial

在/root/ECC/目录下,我们需要一个openssl 配置文件,在/etc/pki/CA目录下有此配置文件,可以拷贝过来。或者直接复制下面内容创建openssl.cnf文件。

2、openssl.cnf文件配置

首先配置配置openssl.cnf文件:

代码语言:javascript
复制
[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]

Directory and file locations.

dir = /root/ECC#这里改成实际目录
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
RANDFILE = $dir/private/.rand

The root key and root certificate.

private_key = $dir/private/ca.key.pem
certificate = $dir/certs/ca.cert.pem

For certificate revocation lists.

crlnumber = $dir/crlnumber
crl = $dir/crl/ca.crl.pem
crl_extensions = crl_ext
default_crl_days = 30

SHA-1 is deprecated, so use SHA-2 instead.

default_md = sha256

name_opt = ca_default
cert_opt = ca_default
default_days = 375
preserve = no
policy = policy_strict

[ policy_strict ]

The root CA should only sign intermediate certificates that match.

See the POLICY FORMAT section of man ca.

countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ policy_loose ]

Allow the intermediate CA to sign a more diverse range of certificates.

See the POLICY FORMAT section of the ca man page.

countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ req ]

Options for the req tool (man req).

default_bits = 2048
distinguished_name = req_distinguished_name
string_mask = utf8only

SHA-1 is deprecated, so use SHA-2 instead.

default_md = sha256

Extension to add when the -x509 option is used.

x509_extensions = v3_ca

req_extensions = v3_req

[ req_distinguished_name ]

See <https://en.wikipedia.org/wiki/Certificate_signing_request>.

countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address

Optionally, specify some defaults.

countryName_default = CN
stateOrProvinceName_default = China
localityName_default = BJ
0.organizationName_default = JD
#organizationalUnitName_default = pt.itage
#emailAddress_default =

[ v3_req ]

Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = xxx.com //一定要注意这里,可以改成自己的域名
DNS.2 = pt.xxx.com //一定要注意这里,可以改成自己的域名
DNS.3 = localhost //一定要注意这里,可以改成自己的域名
IP.1 = 192.168.190.131//一定注意这里,改成自己的IP地址
关注公众号【可为编程】回复【加群】进入技术群一起成长学习!!!
关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

[ v3_ca ]

Extensions for a typical CA (man x509v3_config).

subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]

Extensions for a typical intermediate CA (man x509v3_config).

subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]

Extensions for client certificates (man x509v3_config).

basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]

Extensions for server certificates (man x509v3_config).

basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ crl_ext ]

Extension for CRLs (man x509v3_config).

authorityKeyIdentifier=keyid:always

[ ocsp ]

Extension for OCSP signing certificates (man ocsp).

basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

上面配置文件要注意几个地方:

代码语言:javascript
复制
[ CA_default ]
# Directory and file locations.
dir               = /root/ECC#这里改成实际目录

可以加入一些公司信息和城市信息:

代码语言:javascript
复制

# Optionally, specify some defaults.
countryName_default             = CN
stateOrProvinceName_default     = China
localityName_default            = BJ
0.organizationName_default      = JD
#organizationalUnitName_default = pt.itage
#emailAddress_default           =

这里配置IP和域名,一定要配置好,不然会找不到对应的IP或域名,导致证书验签不通过,在nginx配置后浏览器会提示非不安全的请求访问。

代码语言:javascript
复制
[ alt_names ]
DNS.1 = xxx.com //一定要注意这里,可以改成自己的域名
DNS.2 = pt.xxx.com //一定要注意这里,可以改成自己的域名
DNS.3 = localhost //一定要注意这里,可以改成自己的域名
IP.1 = 192.168.190.131//一定注意这里,改成自己的IP地址

生成ECC私钥

代码语言:javascript
复制
$openssl ecparam -out private/ec-cakey.pem -name prime256v1 -genkey

生成秘钥时,openssl默认仅存储权限的名字:

代码语言:javascript
复制
 $openssl ecparam -in private/ec-cakey.pem -text -noout

生成CA证书

使用上一步生成的CA私钥,生成CA证书

代码语言:javascript
复制
openssl req -new -x509 -days 36500 -config openssl.cnf -extensions v3_ca -key private/ec-cakey.pem -out certs/ec-cacert.pem

接下来,我们可以验证下CA证书的内容和使用的签名算法

代码语言:javascript
复制
openssl x509 -noout -text -in certs/ec-cacert.pem | grep -i algorithm

可以看到,我们使用的是ECDSA签名算法来生成我们的CA证书,而不是使用的RSA。

关注公众号【可为编程】回复【加群】进入技术群一起成长学习!!!

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

使用私钥验证CA证书

如果想使用私钥(包含ECDSA key)验证证书:

代码语言:javascript
复制
$openssl x509 -noout -pubkey -in certs/ec-cacert.pem

类似地,我们可以从私钥导出公钥:

代码语言:javascript
复制
$openssl pkey -pubout -in private/ec-cakey.pem
代码语言:javascript
复制
$openssl pkey -pubout -in private/ec-cakey.pem -ou certs/ec-capub.pem

可以看到,生成的公钥是相同的。

3、生成服务端证书

现在我们可以使用ECC私钥生成服务端证书,为了存储服务端证书,我们创建目录:

代码语言:javascript
复制
$mkdir /root/ECC/server_certs/
$cd /root/ECC/server_certs/

生成服务端证书的ECC私钥

我们再一次使用曲线prime256v1 生成ECC的私钥'

代码语言:javascript
复制
openssl ecparam -out server.key -name prime256v1 -genkey

验证曲线:

代码语言:javascript
复制
openssl ecparam -in server.key -text -noout

服务端生成CSR

我们的CA证书生成时,使用的是 openssl.cnf 里的v3_ca 扩展选项

代码语言:javascript
复制
# For the CA policy
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

CA证书会匹配 Country Name, state or Province Name and Organization Name. 所以服务端证书的CSR请求里的值必须匹配CA证书里相应的值。

Common Name 必须是 服务端的主机名(hostname), 客户端与服务端认证时会使用这个Common Name.

生成CSR请求:

代码语言:javascript
复制
openssl req -new -key server.key -out server.csr -sha256 -config /root/ECC/openssl.cnf

服务端生成CA签名的证书

现在我们使用ECC CA 私钥,CA证书,对server.csr进行签名,生成服务端证书:

关注公众号【可为编程】回复【加群】进入技术群一起成长学习!!!

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

代码语言:javascript
复制
openssl ca -keyfile /root/ECC/tls/private/ec-cakey.pem -cert /root/ECC/tls/certs/ec-cacert.pem -in server.csr -days 36500 -out server.crt -config /root/ECC/openssl.cnf -extensions v3_req

验证服务端证书

使用CA证书验证服务端证书

代码语言:javascript
复制
openssl verify -CAfile /root/ECC/tls/certs/ec-cacert.pem server.crt

我们也可以验证服务端证书的签名算法,确保使用的是ECC私钥。

代码语言:javascript
复制
openssl x509 -noout -text -in server.crt | grep -i algorithm

验证index.txt

验证index.txt包含服务端证书的信息,这个服务端证书是我们使用CA证书签名的。01是我们再开始部分写入的序列号,每次签名,这个序列号都会自加1.

4、在客户端生成CA签名的证书

我们新建一个目录来存储客户端生成的CA证书

代码语言:javascript
复制
mkdir /root/ECC/client_cert/
cd /root/ECC/client_cert/

生成客户端证书的ECC私钥

代码语言:javascript
复制
openssl ecparam -out client.key -name prime256v1 -genkey

验证私钥的曲线名称:

代码语言:javascript
复制
$openssl ecparam -in client.key -text -noout

生成客户端证书的CSR请求

代码语言:javascript
复制
openssl req -new -key client.key -out client.csr -sha256 -config /root/ECC/openssl.cnf

生成客户端证书

代码语言:javascript
复制
$openssl ca -keyfile /root/ECC/tls/private/ec-cakey.pem -cert /root/ECC/tls/certs/ec-cacert.pem  -days 36500 -in client.csr -out client.crt -config /root/ECC/openssl.cnf -extensions v3_req

5、问题汇总

代码语言:javascript
复制
openssl TXT_DB error number 2 failed to update database

产生的原因是:

代码语言:javascript
复制
This thing happens when certificates share common data. You cannot have two
certificates that look otherwise the same.

方法一:

修改demoCA下index.txt.attr

代码语言:javascript
复制
unique_subject = yes

改为

代码语言:javascript
复制
unique_subject = no

查看index.txt

6、加密文件转换命令汇总

代码语言:javascript
复制
crt转pem
openssl x509 -in ca.crt -out ca.pem -outform PEM
pem转crt
openssl x509 -in fullchain.pem -out fullchain.crt
pem转key
openssl rsa -in privkey.pem -out privkey.key
ECC算法:key转pem
openssl ec -in server.key -out server_key_to_pem.pem
openssl ec -in client.key -out client_key_to_pem.pem
rsa算法:key转pem
openssl rsa -in server.key -out server_key_to_pem.pem
openssl rsa -in client.key -out client_key_to_pem.pem
key转pkcs8的key
openssl pkcs8 -topk8 -inform PEM -in client.key -outform pem -nocrypt -out client-key-pkcs8-ecc.pem
openssl pkcs8 -topk8 -inform PEM -in server.key -outform pem -nocrypt -out server-key-pkcs8-ecc.pem
生成truststore
keytool -import -alias one_double_truststore -file tls/certs/ec-cacert.pem -keystore one_double_truststore -storepass 123456
生成keystore
openssl pkcs12 -export -in client.crt -inkey client.key -out alone_keystore.p12 -passout pass:"123456"
openssl pkcs12 -export -in client.crt -inkey client.key -out double_keystore.p12 -passout pass:"123456"