RSA 讲解里的这些字母命名的是一个对象内部的字段,而不是 PKI 工具分发的文件。私钥文件保存着一个结构:靠前的字段是公开的,靠后的字段绝不能离开终结隧道的机器。这两件事实之间,就放着“哪个文件回答哪个问题”的大部分实务问题。本文先读结构,再读那些检查它而不把它倒出来的命令。

在寻找素数之前先确认密钥是 RSA

证书证明某个密钥存在且已被签名,并不证明该密钥是 RSA。系统也可能建立在椭圆曲线或 Ed25519 之上,同一套目录布局对它们都适用。在开始寻找根本不存在的素因子之前,先检查算法。

这三条只读命令回答该问题,并记下工具版本。

openssl version
openssl x509 -in vpn-client.crt -noout -text
openssl pkey -in vpn-client.key -noout -text

版本很重要,因为选项名在主要版本之间挪过位置。在证书输出里找到公钥算法那一行和密钥长度;在密钥输出里,RSA 密钥会打印模数和公开指数。椭圆曲线密钥则打印一条具名曲线,再怎么搜索也得不到 n 或 q。

读结构,而不是读 base64

PEM 是包在头行里的 base64,解码得到 DER,也就是序列化结构,而不是一个巨大的整数。RSA 私钥装在两种常见容器里,PKCS#1 和 PKCS#8,两者都归结为同一组数字。

两个素数的情况下,私有结构带有九个字段:版本、模数 n、公开指数 e、私有指数 d、素数 p 和 q、两个 CRT 指数以及一个 CRT 系数。RFC 8017 第 A.1 节定义了这些组成部分和 ASN.1 布局,它也允许两个以上的素数,所以通用解析器可能打印额外的素数条目。注意顺序,因为它解释了一个常见错误:公开的那一半存放在私钥文件内部,因此存在公开字段并不使该文件可以安全分享。它之后的一切都是必须留在主机上的材料。

编码加了一层很容易与内容混淆的东西。openssl asn1parse 把结构画成树,于是字段顺序变得可见;它不解密任何东西,也不告诉你密钥是否可用。

在不打印密钥的前提下把证书与密钥配对

接下来的问题通常是:证书和密钥是否属于同一对。比较模数能回答这一点,但它也会把接近私密的材料抄进终端记录,而记录可能进工单。比较公开的那一半能回答同一个问题,屏幕上不留下任何敏感内容。

openssl pkey -in vpn-client.key -pubout -out /tmp/key.pub
openssl x509 -in vpn-client.crt -pubkey -noout > /tmp/cert.pub
cmp /tmp/key.pub /tmp/cert.pub && echo MATCH

两个文件完全相同,说明证书带有该密钥的公开一半;有差异则说明你拿错了对。这种不匹配,是隧道协商顺利却拒绝认证的最常见原因。之后删掉那两个临时文件;它们不含秘密,但在事故处理到一半时,干净的目录更容易推演。

弄清相邻文件不是什么

证书不是一把用 CA 证书就能解开的加密公钥;它是公钥与身份数据之间的一份签名绑定。CA 的私有签名密钥与它自己的证书、与每一个客户端密钥都分开保管。请求同样不是证书:它携带被请求的信息和公钥,并对请求本身签名,所以它是登记材料,而不是权利凭证。

FileAnswersDoes not prove
vpn-client.keyPossession of the private keyThat the identity is enrolled
vpn-client.crtA signed binding of public key to identityThat the client holds the matching key
vpn-client.csrThe requested binding and public keyThat access was granted
dh2048.pemGroup parameters for key exchangeAny RSA component
crl.pemRevocation status for certificatesAnything about a private key

其中有两项经常被误当成 RSA 参数,所以值得说两遍。Diffie-Hellman 参数文件保存的是用于建立密钥的群参数,不含任何 RSA 素数,尽管它同样会打印成一个大素数。吊销列表公布的是证书状态,不是私有指数可能藏身的地方。

因此每一行清单都该记四件事:对象类型、所有者、用途,以及是否包含私密材料。最后一列决定这个文件可以复制到哪里,而它恰恰是常被跳过的那一列。

记下配对结果,然后验证握手

动手改动之前,把校验输出连同日期和 OpenSSL 版本一起保存下来。这样以后轮换密钥时,比较的是证据,而不是记忆。

openssl verify -CAfile ca.crt vpn-client.crt
openssl x509 -in vpn-client.crt -noout -subject -issuer -dates

公钥比较之后,走一遍真正消费这对密钥的路径:把隧道升起来,确认证书被出示并被接受,再看网关记录了哪一个身份。如果密钥与证书相符、认证却仍然失败,问题在信任、身份映射或访问策略,而不在这一对本身。

回滚时,把旧的密钥和证书放在带日期的目录里,并让两者一起恢复,绝不要只恢复其中一样。只有密钥没有证书、或只有证书没有密钥,从外部看失败方式完全相同,这会让恢复了一半的密钥对在凌晨两点变得极难收拾。

相关文章