本文的目的是把读者培养成数字证书专家,完整阅读并理解文章后,再遇到数字证书,因心中深谙其原理,可以在好友面前侃侃而谈,成为群体的焦点,话不多说一起来学习吧。
- 初识数字证书
- 证书的层次
- 证书的核心:证书中的公钥
- 证书信任的基础:证书签名算法
- 扩展字段
- 总结
- 为什么签名动作需要先Hash再加密?
- 公钥和私钥哪个公开就是公钥,这个说法对不对?
一、初识数字证书
小A生成了一对非对称密钥对,并在此基础上生成了一个数字证书。他可以让任何人使用该证书中包含的公钥和自己通信。并且不用担心自己的公钥被恶意篡改,因为有issuer即CA来证明这张证书的合法性。
以GoogleSearch证书为例。从浏览器导出后,解析证书内容如下(已隐藏扩展部分)。
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
16:b4:fb:e6:b6:0e:72:aa:0a:7c:27:5b:da:88:00:3e
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = US, O = Google Trust Services, CN = WE2
Validity
Not Before: Apr 29 19:30:10 2025 GMT
Not After : Jul 22 19:30:09 2025 GMT
Subject: CN = www.google.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:a8:c7:46:f7:51:33:79:ec:e1:c2:fb:6c:cb:07:
7f:97:29:3e:0f:3b:71:09:1e:03:b6:f2:dc:27:89:
3e:e0:31:01:ab:81:86:9d:64:5d:86:57:c2:10:c0:
fa:a2:9e:c3:48:3d:9a:93:37:84:9a:84:a3:c0:ad:
65:17:2c:ad:50
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
……
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:21:96:3d:d1:0d:f0:08:11:0d:1c:24:8f:7b:17:
e4:84:01:d7:e3:99:76:c3:bc:b7:fc:6c:ba:c5:9d:a0:12:2c:
02:20:49:94:df:5a:ae:da:45:56:b6:83:89:24:b3:b5:46:9a:
c0:6c:5f:71:65:3c:e7:67:d4:52:02:fd:f9:2b:26:2d
整个证书包含的信息,一句话概括就是签发者(Issuer这行)为持有者www.google.com(Subject这行)的可信任性进行了背书。或者再详细说,签发者使用自己的私钥和ecdsa-with-SHA256算法,对持有者的公钥(Subject Public Key Info这行)和其他信息,做签名。这就是最重要的信息。本文重点放在一些关键细节,例如公钥算法、签名算法、证书的链式结构。其他的字段见名知意不再浪费篇幅。
二、证书的层次
假设有一家总部位于南京的公司,为了响应出海战略,在上海成立了二级子公司,主营Saas化产品和服务。
我们举的这个例子,将证书的层次结构和公司的层次结构进行重叠,更便于理解。
可以设计3级证书体系。南京总部创建根证书(实际会购买权威签发)、上海子公司使用的二级证书,从总部根证书签发。子公司的证书管理服务使用二级证书签发自己的公有云服务证书。证书逐级认证。来快速过一遍3级证书链的生成命令和证书数据,预设每张证书都使用EC椭圆曲线加密公钥,曲线全部为P-256.
注意,本小节我们只需要关注证书的层次结构信息即可,较复杂的签名算法和公钥稍后会详细讲。
生成 1 证书
注意,这就是自签名证书,签发方Issuer和服务方Subject相同。根证书都是自签名结构。
opensslec param -name prime256v1 -genkey -noout -out root_ec_private.key
openssl req -new -key root_ec_private.key -out root_ec_csr.pem -subj "/C=CN/ST=Nanjing/O=SA/CN=Root CA"
openssl req -x509 -key root_ec_private.key -in root_ec_csr.pem -out root_ec_cert.pem -days 365
openssl x509 -in root_ec_cert.pem -text –noout
另外请注意,其中的扩展字段即 x509v3 extensions 中包含一个 CA=TRUE的属性,只要是作为非终结点证书的该字段都必须要设置,否则会无法对子证书验签。
生成 2 证书
openssl ecparam -name prime256v1 -genkey -noout -out intermediate_1_ec_private.key
openssl req -new -key intermediate_1_ec_private.key -out intermediate_1_ec_csr.pem -subj "/C=CN/ST=Shanghai/O=SA/CN=Intermediate CA"
由于2层证书仍然是个中间证书,需要作为签发证书(即还有子证书),所以需要设置 CA=True。另外还需要将pathlen参数放大为层数-1,即basicconstraints下的pathlen参数。
编辑证书签发配置文件intermediate_1_csr.cnf,增加自定义签发配置。
vi intermediate_1_csr.cnf
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ v3_req ]
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Shanghai
organizationName = SA
commonName = Intermediate CA
使用根证书对该证书进行签发。
openssl x509 -req -in intermediate_1_ec_csr.pem -CA root_ec_cert.pem -CAkey root_ec_private.key -CAcreateserial -out intermediate_1_ec_cert.pem -days 365 -sha256 -extfile intermediate_1_csr.cnf -extensions v3_req
可以看到证书的Issuer是南京总部公司,Subject是子公司的名称。
还可以使用下面命令进行1证书验证2证书
openssl verify -CAfile root_ec_cert.pem intermediate_1_ec_cert.pem
生成 3 证书
继续如法炮制,重复上面的证书创建命令
openssl ecparam -name prime256v1 -genkey -noout -out leaf_2_ec_private.key
openssl req -new -key leaf_2_ec_private.key -out leaf_2_ec_csr.pem -subj "/C=CN/ST=Beijing/O=SA/CN=leaf Device CA"
创建自定义签发配置文件。
vi leaf_2_csr.cnf
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ v3_req ]
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Beijing
organizationName = SA
commonName = Intermediate Device CA
签发子证书
openssl x509 -req -in leaf_2_ec_csr.pem -CA intermediate_1_ec_cert.pem -CAkey intermediate_1_ec_private.key -CAcreateserial -out leaf_2_ec_cert.pem -days 365 -sha256 -extfile intermediate_1_csr.cnf -extensions v3_req
验证第 3 级证书时,可以有 2 种方法。第一种是将中间证书作为 untrusted 参数传给 openssl。
openssl verify -CAfile root_ec_cert.pem -untrusted intermediate_1_ec_cert.pem leaf_ec_cert.pem
第二种是将根证书和全部的中间证书按照顺序写到文本文件中,然后使用该证书链进行验证。
cat root_ec_cert.pem intermediate_1_ec_cert.pem >chain.pem
openssl verify -CAfile chain.pem leaf_2_ec_cert.pem
小结
- 全部的证书创建时都是 EC-P256椭圆曲线类型的,这也是目前最常用的密钥类型。
- 自签名的证书,颁发者Issuer和持有者Subject是相同的。
- 注意看每个证书的颁发者Issuer和持有者Subject,证书逐级签发,逐级认证。
- 验证需要带完整链。
三、证书中的公钥
每一个证书持有者都有一个密钥对,私钥自持,公钥公开,由于要解决公钥信任问题所以要由权威CA签发成以数字证书形式传递。
密钥对的算法,理论上说非对称算法都可以生成密钥对,但由于证书作为证书签发方在签发子证书时,会使用私钥给子证书签名,而不是全部的非对称加密算法都有签名功能的,例如x25519仅支持协商。这点在后面还会强调。
本文以OpenSSL 1.1.1t为例,生成ed448、ec、dsa、ed25519、rsa、这5个算法公钥的证书,并供读者参考比较结构差异。
ED448
opensslgenpkey -algorithm ed448 -out endpoint_4_ed448_private.key
openssl req -new -key endpoint_4_ed448_private.key -out endpoint_4_ed448_csr.pem -subj "/C=CN/ST=Endpoint/O=SA/CN=Device"
openssl x509 -req -in endpoint_4_ed448_csr.pem
-CA ./intermediate_2_ec_cert.pem
-CAkey./intermediate_2_ec_private.key
-CAcreateserial
-out endpoint_4_ed448_cert.pem
-days 365 -sha256
椭圆曲线
椭圆曲线算法有一组曲线可以选择,一般最常用的是p-256即secp256k1曲线。查看openssl支持哪些曲线,可以用下面命令。
openssl ecparam -list_curves
# 有下面的常用曲线
secp224r1 : NIST/SECG curve over a 224 bit prime field
secp256k1 : SECG curve over a 256 bit prime field
secp384r1 : NIST/SECG curve over a 384 bit prime field
secp521r1 : NIST/SECG curve over a 521 bit prime field
prime256v1: X9.62/SECG curve over a 256 bit prime field
构建椭圆曲线证书
openssl ecparam -name secp256k1 -genkey -out endpoint_4_ec.key
openssl req -new -key endpoint_4_ec.key -out endpoint_4_ec_csr.pem -subj "/C=CN/ST=Endpoint/O=SA/CN=Device"
openssl x509 -req -in endpoint_4_ec_csr.pem
-CA ./intermediate_2_ec_cert.pem
-CAkey ./intermediate_2_ec_private.key
-CAcreateserial
-out endpoint_4_ec_cert.pem
-days 365 -sha256
DSA算法
openssldsaparam -out dsa_param.pem 1024
opensslgendsa -out dsa_private_key.pemdsa_param.pem
openssldsa -in dsa_private_key.pem -pubout -out dsa_public_key.pem
opensslreq -new -key dsa_private_key.pem -out test_dsa_csr.pem -subj "/C=CN/ST=Endpoint/O=SA/CN=Device"
openssl x509 -req -in test_dsa_csr.pem
-CA ./intermediate_2_ec_cert.pem
-CAkey ./intermediate_2_ec_private.key
-CAcreateserial
-out endpoint_4_dsa_cert.pem
-days 365 -sha256
ED25519类型
C:Usersuser>ssh root@192.168.1.100
The authenticity of host '192.168.1.100 (192.168.1.100)' can't be established.
ED25519 key fingerprint is SHA256:LrIlpqE/CMksxRGM0EnAQ+sWUjZho.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.100' (ED25519) to the list of known hosts.
root@192.168.1.100's password:
创建一个ED25519证书
openssl genpkey -algorithm ed25519 -out endpoint_4_private_ed25519.key
openssl req -new -key endpoint_4_private_ed25519.key -outendpoint_4_csr.pem -subj "/C=CN/ST=Endpoint/O=SA/CN=Device"
openssl x509 -req -in endpoint_4_csr.pem
-CA ./intermediate_2_ec_cert.pem
-CAkey ./intermediate_2_ec_private.key
-CAcreateserial
-out endpoint_4_cert.pem
-days 365 -sha256
RSA算法
RSA算法应该是最广为人知的非对称加密算法了,其基于大素数分解问题,但因安全性不如椭圆曲线算法,公钥过大等问题逐渐被椭圆曲线取代。
openssl genpkey -algorithm RSA -out endpoint_4_private_rsa.key -pkeyopt rsa_keygen_bits:2048
openssl req -new -key endpoint_4_private_rsa.key -out endpoint_4_csr.pem -subj "/C=CN/ST=Endpoint/O=SA/CN=Device"
openssl x509 -req -in endpoint_4_csr.pem
-CA ./intermediate_2_ec_cert.pem
-CAkey ./intermediate_2_ec_private.key
-CAcreateserial
-out endpoint_4_cert.pem
-days 365 -sha256
小结
请重点关注各种算法密钥的如下字段:
- 每种算法公钥部分的数据。即Subject Public Key Info:字段。重点对比每种算法的密钥量(ed25519公钥长度最小,dsa公钥长度最多),附属参数(rsa的exponent参数,ec的曲线类别,dsa的PQG三个参数),这些是进一步学习每种算法的切入点。
- 每种签名算法都是相同的,即Signature Algorithm:都是ecdsa-with-SHA256,是否可以使用其他签名算法?有哪些签名算法可以使用?是否可以使用MD5?如果不能回答这些问题,请读者继续阅读。
四、证书签名算法
签名算法 = 哈希算法 + 非对称加密的签名运算
哈希算法就是散列函数,例如SHA256,SHA3,SHA1,MD5也可以但不安全已禁止使用。
非对称加密的签名运算就是上层证书对应的私钥的加密运算。签发子证书时使用父证书颁发者的私钥,因此签名算法和父证书的密钥类型是相关的。ECC根证书是签不出 RSA-with-<Hash算法>的,只能签发 ECDSA-with-<Hash算法>类型的证书。 DSA根证书也只能签出DSA-with-<Hash算法>,RSA根证书也只能签出RSA-with-<Hash算法>。因此父证书的密钥类型就决定了签发出的子证书中的 Signature Algorithm 字段,顶多改一下签名时的Hash函数。
另外,也不是什么算法密钥都支持签名操作的。如 RSA、DSA、ECDSA、Ed25519等算法都有签名能力,作为父证书的签发密钥没问题。但X25519算法没有签名能力,这种算法和密钥只是为密钥交换存在的,无法对子证书的 CSR进行签名,因此X25519公钥类型的证书无法拥有子证书,openssl命令也不支持生成X25519类型的证书。本文后面会使用python强制生成,虽签名校验都是通过的,但只能作为终结点证书使用。
现在来回答上一节最后的问题,因为这几个证书的CA根证书是一张EC类型的证书,所以只能签发出ecdsa-with-<Hash算法>的签名。
总结一下,对于数字证书的签名字段Signature Algorithm,有下面的结论。
- 包含一个Hash算法和一个加密签名算法。
- 加密签名算法是父证书的私钥决定的。所以看到是ECDSA-with-XXX就知道父证书是一个EC椭圆类型的公钥,看到RSAWithEncryption就知道父证书是一个RSA公钥。
- 有些加密算法是不能用于签名的,原因后面的文章会说。X25519这种算法一定不是父证书的公钥类型。
- 因为有安全性低被弃用的算法,不能用来签名的算法,以及缺乏实现的算法(在openssl中没实现)。所以可以用作证书公钥类型的算法数量看起来就不是太多。
- 顺便说一下,证书签发时输入的数据不只是子证书的公钥,还包含证书的其他字段,感兴趣的读者可以看一下证书 tbs(to be signed)数据结构。
五、扩展字段(可选)
以上是证书最重要的描述信息,但还有一些扩展字段用在描述证书本身以及包含的密钥的使用方法等声明式的属性。例如该证书是否允许作为CA签发下级证书,pathlen属性限制证书链的最大长度,避免证书链的签发被滥用,过长的证书链会影响验签。密钥使用方法声明用于说明该密钥使用的限制。以下是证书扩展字段的简述,详细信息读者可在使用时再搜索对应资料。
- KeyUsage 扩展字段用于说明证书中的公钥的用法。例如X25519用于密钥协商,此配置表明证书密钥专用于密钥协商协议(如 TLS 的 ECDHE 密钥交换)。
- ExtendedKeyUsage 扩展包含一个OID,例如解析`1.3.101.110` 属于 Curve25519 相关标识符,常见于X25519算法的椭圆曲线 Diffie-Hellman (ECDH) 密钥协商。
六、总结
还有一些其他的问题可以讨论,比如
- 为什么签名动作需要先Hash再加密?
以1024位RSA加密为例,RSA加密的明文长度受填充机制影响,例如PKCS#1 v1.5填充时最大明文为 117字节(128-11字节填充),OAEP填充时明文长度更短(约86字节)。对于更长的数据,只能先Hash成短数据然后再加密才行。或者分块加密再拼接一起,
- 公钥和私钥哪个公开就是公钥,这种说法是错误的。不要把大素数相乘理解为简单的运算,椭圆算法的私钥和公钥也是一个是标量一个是曲线上的点,完全不同。哪个能做公钥哪个能做私钥是因数学原理而定的。扩展可以阅读密码学浅入浅出ECIES之椭圆曲线加密算法
就酱,下一篇ecdsa数学原理和量子密码学
原文始发于微信公众号(青木生长):一文精通数字证书的签名算法
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论