证书中提取公钥数据
要从证书中提取公钥数据,最直接的方法就是使用 OpenSSL 工具。公钥数据本身是一个结构化的信息块,它不仅包含用于加解密的“钥匙材料”,还包含描述这把“钥匙”类型的“标签”。
如何提取公钥数据
如果有证书文件(如 cert.pem),在终端执行以下命令,即可将公钥提取并保存到一个单独的文件中(public.pem):
openssl x509 -in cert.pem -pubkey -noout > public.pem |
-in cert.pem:指定输入的证书文件。-pubkey:输出证书中的公钥。-noout:不输出证书中其他冗余的信息。> public.pem:将公钥内容保存到public.pem文件。
公钥数据里包含哪些信息
提取出的公钥文件,在X.509标准中被称为 SubjectPublicKeyInfo 。它由两个核心部分组成,这与之前讨论的OID正好对应:
| 组成部分 | 具体内容 | 对应之前提到的OID | 作用解释 |
|---|---|---|---|
| 1. 算法标识符 | 公钥算法OID + 相关参数 | 1.2.840.10045.2.1 (ecPublicKey) 和 1.2.840.10045.3.1.7 (prime256v1) |
它是一个“标签”,告诉应用程序“这是一把ECC公钥,并且它使用的是NIST P-256这条特定曲线”。 |
| 2. 公钥位串 | 实际的密钥字节数据 | 无直接对应的OID | 这是真正的“钥匙材料”。对于的ECC证书,它包含公钥的X和Y坐标(拼接在一起的一个比特串),是用于实际密码学操作的数学值。 |
举个例子
对之前提供的证书执行提取命令后,得到的公钥文件内容大致如下:
-----BEGIN PUBLIC KEY----- |
这个PEM文件里,BEGIN PUBLIC KEY 和 END PUBLIC KEY 之间的部分就是Base64编码后的完整 SubjectPublicKeyInfo 结构。其中:
- 算法标识符被隐式地编码在开头的
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD这部分数据里,它解析出来就是ecPublicKey和prime256v1这两个OID。 - 公钥位串则是剩下的
QgAEksxVdqkoID+Ae+4AkkeuLAzK4DZxgodOWetXQTWZOgIQT8bfsyyiN+HYBS1lqVXnTK5tTrmma...部分。
公钥的数学构成
公钥的具体内容取决于所使用的密码算法,常见的有 RSA 和 ECC(椭圆曲线密码)。
RSA 公钥
- 组成:模数 (n) 和公钥指数 (e)。
- (n) 是两个大素数的乘积,长度通常为 2048 位或更高。
- (e) 通常取固定值 65537。
- 作用:加密时用 ((n, e)) 将明文转换成密文;验证签名时用它们还原签名哈希。
ECC 公钥(对应证书中的情况)
- 组成:一条椭圆曲线参数和一个曲线上的点 (Q = (x, y))。
- 曲线参数包括:素数域 (p)、系数 (a, b)、基点 (G)、阶 (n) 等(这些由曲线 OID 统一指定,例如证书中的
prime256v1即 NIST P-256 曲线)。 - 公钥点 (Q) 是私钥 (d) 与基点 (G) 的标量乘法结果:(Q = dG)。
- 曲线参数包括:素数域 (p)、系数 (a, b)、基点 (G)、阶 (n) 等(这些由曲线 OID 统一指定,例如证书中的
- 作用:用于 ECDH 密钥交换(双方各自用对方公钥和自己的私钥算出共享密钥)或 ECDSA 签名验证(用公钥验证签名)。
公钥的编码格式
在数字证书(X.509)中,公钥以 SubjectPublicKeyInfo 结构存储,这是一个 ASN.1 定义的标准化格式,包含两部分:
SubjectPublicKeyInfo ::= SEQUENCE { |
- algorithm:标识公钥算法及其参数(如
ecPublicKey+prime256v1OID)。 - subjectPublicKey:实际的密钥位串,对 ECC 来说就是经过压缩或未压缩的公钥点坐标。
PEM 格式是先将这个 DER 编码的二进制数据进行 Base64 编码,再添加 -----BEGIN PUBLIC KEY----- 和 -----END PUBLIC KEY----- 封装而成。
从提供的证书中提取并解析公钥实例
提取公钥 PEM 文件
假设将证书保存为 cert.pem,执行:openssl x509 -in cert.pem -pubkey -noout > public.pem
得到 public.pem 内容如下(与之前看到的一致):-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEksxVdqkoID+Ae+4AkkeuLAzK4DZx
godOWetXQTWZOgIQT8bfsyyiN+HYBS1lqVXnTK5tTrmaRqwvGxFvQwMMWQ==
-----END PUBLIC KEY-----
使用 OpenSSL 查看公钥详细信息
openssl ec -pubin -in public.pem -text -noout |
输出:read EC key
Private-Key: (256 bit)
pub:
04:92:cc:55:76:a9:28:20:3f:80:7b:ee:00:92:47:
ae:2c:0c:ca:e0:36:71:82:87:4e:59:eb:57:41:35:
99:3a:02:10:4f:c6:df:b3:2c:a2:37:e1:d8:05:2d:
65:a9:55:e7:4c:ae:6d:4e:b9:9a:46:ac:2f:1b:11:
6f:43:03:0c:59
ASN1 OID: prime256v1
NIST CURVE: P-256
解读输出:
Private-Key: (256 bit):表示这是一个 256 位的 ECC 密钥对(此处只显示公钥,所以没有私钥信息)。pub:后面的十六进制串是未压缩的公钥点:- 开头的
04是未压缩格式的标识字节,表示后面紧跟着两个 32 字节(256 位)的大整数,分别代表 (x) 和 (y) 坐标。 - 从
92:cc:...到03:0c:59一共 64 字节,前 32 字节是 (x) 坐标,后 32 字节是 (y) 坐标。
- 开头的
ASN1 OID: prime256v1和NIST CURVE: P-256表明这条曲线就是在证书中看到的prime256v1。
这个公钥在证书中的含义
- 该公钥属于证书持有者(
*.aa.demo.com.cn)。 - 任何拥有该公钥的实体都可以:
- 验证由该证书对应私钥生成的数字签名(例如在 TLS 握手中,浏览器用它验证服务器的签名)。
- 如果用于密钥交换(如 ECDH),可以用它和临时私钥计算出共享密钥,但实际 TLS 中更多是用于签名验证,临时密钥交换使用临时生成的密钥对(ECDHE)。
公钥的典型使用场景举例
加密数据(以 RSA 为例)
- 假设 Alice 想给 Bob 发送加密邮件。Bob 事先将自己的 RSA 公钥发给 Alice。
- Alice 用 Bob 的公钥 ((n, e)) 加密对称密钥(比如 AES 密钥),将密文发送给 Bob。
- Bob 用自己的 RSA 私钥解密得到对称密钥,再用对称密钥解密邮件正文。
验证数字签名(以 ECDSA 为例)
- 服务器拥有一个证书,其中包含其 ECC 公钥。浏览器已经信任签发该证书的 CA。
- 在 TLS 握手过程中,服务器用其私钥对握手消息(包括协商的参数)进行签名,并将签名发送给浏览器。
- 浏览器从服务器证书中取出 ECC 公钥,使用
ecdsaWithSHA256算法验证签名。如果验证通过,证明该服务器确实持有与证书公钥对应的私钥,从而确认了服务器身份。
密钥交换(以 ECDH 为例)
- 在 ECDHE 密钥交换中,客户端和服务器各自生成临时 ECC 密钥对,并将自己的临时公钥发给对方。
- 双方用自己的临时私钥和对方的临时公钥进行 ECDH 计算,得到相同的共享密钥(预主密钥),再衍生出会话密钥。
- 这里,证书中的静态公钥通常不直接用于密钥交换,而是用于对临时公钥进行签名,以防止中间人攻击。
- 公钥是公开的密钥材料,它由算法类型和具体的密钥数值组成。
- 在 X.509 证书中,公钥被封装在
SubjectPublicKeyInfo结构中,包含了算法标识(OID)和密钥位串。 - 通过 OpenSSL 等工具可以方便地提取并查看公钥的详细内容,例如证书中的 ECC 公钥就是曲线
prime256v1上的一个点 ((x, y))。 - 公钥的主要作用是加密和签名验证,配合私钥完成安全通信中的身份认证、数据机密性和完整性保护。
理解公钥的构成和用法,是掌握 PKI 和 TLS 等安全协议的基础。
公钥信息详解
在 X.509 证书中,公钥信息(SubjectPublicKeyInfo)不仅包含密钥本身的比特串,还包含一个算法标识符,它明确地指出了这个公钥属于哪种密码算法,以及该算法所需的参数。正是这个标识符,让应用程序知道“这是一把什么样的钥匙”以及“该如何使用它”。
算法标识符的结构
在 ASN.1 语法中,算法标识符定义为:
AlgorithmIdentifier ::= SEQUENCE { |
algorithm:一个对象标识符(OID),唯一标识一种算法(如 RSA、ECDSA、ecPublicKey 等)。parameters:与算法相关的参数,其类型和内容由algorithmOID 决定。可以是NULL,也可以是具体的参数值(如椭圆曲线的参数)。
在证书的 SubjectPublicKeyInfo 中,这个 AlgorithmIdentifier 就出现在公钥比特串之前,用来描述后面跟着的密钥材料的类型。
针对证书中 ECC 公钥的详细解析
之前提供的证书中,公钥算法是 ECC(elliptic curve cryptography),具体标识为:
algorithm:1.2.840.10045.2.1(ecPublicKey)
这个 OID 表明这是一个用于 椭圆曲线公钥 的通用标识符,它不指定具体的曲线,只说明这是 EC 公钥。parameters:1.2.840.10045.3.1.7(prime256v1)
这是一个命名曲线的 OID,它标识了具体使用的椭圆曲线参数(即 NIST P-256 曲线,也称 secp256r1)。曲线参数包括域大小p、椭圆曲线方程y² = x³ + ax + b中的系数a和b、基点G、基点的阶n等。所有这些数学参数都通过这个 OID 间接指定,应用程序只需知道这个 OID 就能从内置的曲线参数表中找到对应的曲线数据。
用 OpenSSL 查看原始 ASN.1 结构
可以用 openssl asn1parse 解析证书,定位到 SubjectPublicKeyInfo 部分:
openssl asn1parse -in cert.pem -i |
输出片段(经过精简并注释):
0:d=0 hl=4 l= 714 cons: SEQUENCE (证书整体) |
- 第 98 行:
algorithm字段,值为id-ecPublicKeyOID。 - 第 107 行:
parameters字段,值为prime256v1OID。 - 第 117 行:
subjectPublicKey比特串,包含公钥点的坐标。
这就清晰地展示了:算法标识符告诉解析器“这是一个 EC 公钥,并且使用的是 prime256v1 曲线”。
为什么需要分开指定算法和参数?
- 算法 OID 告诉“这是什么类型的公钥”(RSA、EC、DSA 等),决定了后续比特串的解析方式。例如,对于 EC 公钥,比特串就是一个椭圆曲线上的点;对于 RSA 公钥,比特串中则包含模数
n和指数e。 - 参数 OID 提供了算法运行所需的具体环境。对于 EC,曲线参数决定了点运算的数学规则;对于 RSA,参数通常为 NULL,因为模数长度等信息已经包含在公钥比特串中。
这样设计的好处是灵活性和可扩展性:可以用同一个算法 OID(如 ecPublicKey)搭配不同的曲线参数(如 prime256v1、secp384r1),而无需为每一条曲线都定义一个新的算法 OID。
其他算法的参数示例
RSA 公钥的算法标识符
RSA 公钥的算法 OID 是 1.2.840.113549.1.1.1 (rsaEncryption),其 parameters 字段通常为 NULL(因为密钥本身的模数和指数已经编码在比特串中,不需要额外参数)。
用 ASN.1 解析 RSA 证书的 SubjectPublicKeyInfo 会看到类似:
... |
显式参数 vs. 命名参数
对于椭圆曲线,有两种方式指定参数:
- 命名曲线(最常用):通过 OID 引用预先定义的标准曲线(如 prime256v1)。这样参数字段就是一个 OID。
- 显式参数:直接将曲线的所有数学参数(p, a, b, G, n, h)嵌入到
parameters字段中。这种方式很少用,因为它会显著增加证书大小,且不同实现可能解析不一致。
例如,一个显式参数的 parameters 可能是一个包含多个整数的序列。但为了简洁和互操作性,几乎所有的实践都采用命名曲线。
实际应用中的意义
当应用程序(如浏览器、TLS 库)解析证书时:
- 它首先读取
SubjectPublicKeyInfo.algorithm.algorithm,知道这是一个 EC 公钥。 - 然后读取
parameters,发现是prime256v1,于是从内置表中加载该曲线的所有数学参数。 - 最后读取
subjectPublicKey比特串,按照 EC 公钥的格式(通常以04开头的未压缩点)解析出 x 和 y 坐标。 - 至此,应用程序获得了完整的公钥信息:算法类型 + 曲线参数 + 具体密钥点。后续无论是验证签名还是进行 ECDH 密钥交换,都可以正确执行。
- 标识公钥算法及其参数 是通过
AlgorithmIdentifier结构实现的,包含算法 OID 和可选的参数。 - 在 ECC 证书中,算法 OID
ecPublicKey说明是椭圆曲线公钥,参数 OID(如prime256v1)指定具体的命名曲线。 - 这种分离设计让密码算法和其运行环境可以独立变化,增强了 PKI 体系的灵活性。
- 通过 ASN.1 解析工具可以直观地看到这些标识符在证书中的存储形式,它们是确保证书互操作性的关键元数据。
未压缩和压缩的公钥点
未压缩和压缩的公钥点是椭圆曲线密码学中同一公钥的两种不同序列化格式。它们的核心区别在于:未压缩格式直接包含公钥点的完整X和Y坐标,而压缩格式只包含X坐标和一个标识Y坐标符号的标志位,接收方可以通过椭圆曲线方程从X坐标还原出Y坐标。
核心区别与识别方法
| 特性 | 未压缩格式 (Uncompressed) | 压缩格式 (Compressed) |
|---|---|---|
| 首字节前缀 | 0x04 |
0x02 或 0x03 |
| 内容 | X坐标(32字节) + Y坐标(32字节) | X坐标(32字节) |
| 总长度 (以的NIST P-256曲线为例) | 1 + 32 + 32 = 65 字节 |
1 + 32 = 33 字节 |
| Y坐标的确定 | 直接从数据中读取 | 需要根据前缀(0x02表示Y为偶数/正,0x03表示Y为奇数/负)和曲线方程计算得出 |
识别方法:最直接的方法就是看公钥字节流的第一个字节。
- 如果第一个字节是
0x04,那么这就是一个未压缩的公钥。 - 如果第一个字节是
0x02或0x03,那么这就是一个压缩的公钥。
如何使用
在实际编程中,现代密码学库通常都提供了处理这两种格式的便捷方法。
格式转换
可以轻松地将一种格式转换为另一种。例如,在 Node.js 的 crypto 模块中,可以使用 ECDH.convertKey() 方法将压缩的公钥转换为未压缩的格式。
// 示例:将压缩的公钥转换为未压缩格式 |
从字节流加载公钥
许多库的接口足够智能,可以自动识别这两种格式。例如在 Rust 的 ecdsa 库或 Python 的 ecdsa 库中,from_bytes 或 from_string 方法通常能够根据前缀字节自动判断并正确加载。
# 示例(使用 python-ecdsa 库) |
为什么需要压缩?
- 节省空间:正如在区块链或某些证书场景中看到的,压缩格式可以将公钥的存储空间减少近一半,这对于存储空间或带宽有限的环境(如比特币交易)非常有用。
- 网络传输优化:像TLS这样的协议也曾探讨过使用压缩格式来减少握手过程中的数据量,尽管在TLS 1.3中相关的协商机制已被弃用,但通过定义新的支持组(如
secp256r1c),压缩格式仍然可以被使用。
OID与名称的对应关系
要查询公钥算法OID与名称的对应关系,有多种途径可以实现,从权威的在线数据库到本地的命令行工具。
如何查询OID与名称的对应关系
可以通过以下几种方式进行查询:
权威在线数据库
- OID Repository (oid-info.com):这是一个非常全面的OID权威信息库。可以直接在网站上输入OID(例如
1.2.840.10045.2.1),即可查询到它的定义、描述、参考来源等详细信息。 - IANA (Internet Assigned Numbers Authority):互联网号码分配局,管理着大量与网络协议相关的OID,特别是PKIX(公钥基础设施X.509)工作小组定义的OID。
本地命令行工具
- OpenSSL:如果已经安装了OpenSSL,它内置了OID的解析功能。可以使用以下命令来查询:
# 直接通过名称查找OID
openssl asn1parse -strparse 1 -oid
# 或者使用 -oid 选项配合其它命令,例如查看证书时同时显示OID名称
openssl x509 -in cert.pem -text -noout -nameopt multiline -oid
# 具体查找某个OID对应的名称
openssl x509 -in cert.pem -text -noout -nameopt multiline -oid 2>&1 | grep -E "1.2.840.10045.2.1|ecPublicKey" - Linux内核源码:在Linux系统的开发包中,OID的定义通常可以在
include/linux/oid_registry.h文件中找到。这是一个硬编码的列表,虽然不直接提供查询命令,但可以作为一个权威的本地参考。
特定厂商或项目的文档
- 许多加密库或云服务商的文档中会提供他们支持的算法及其OID列表。例如,Securosys的文档中就详细列出了各种ECC曲线及其对应的OID,Yubico的开发者文档也提供了
Oids类的字段说明。
常见OID及其简要说明
为了方便查阅,常见的OID分类整理如下。
公钥算法
这些OID用于标识证书主体公钥的类型。
| OID | 名称 | 简要说明 |
|---|---|---|
1.2.840.113549.1.1.1 |
rsaEncryption | RSA公钥加密算法,应用最广泛的非对称算法之一。 |
1.2.840.10045.2.1 |
ecPublicKey | 椭圆曲线公钥算法,标识这是一个ECC公钥。 |
1.2.156.197.1.301 |
SM2 | 中国国家密码管理局公布的椭圆曲线公钥密码算法。 |
签名算法
这些OID用于标识证书或数据签名的算法,它通常组合了哈希算法和公钥算法。
| OID | 名称 | 简要说明 |
|---|---|---|
1.2.840.113549.1.1.11 |
sha256WithRSAEncryption | SHA-256哈希结合RSA加密的签名算法。 |
1.2.840.10045.4.3.2 |
ecdsa-with-SHA256 | ECDSA签名算法与SHA-256哈希函数结合,这正是证书中使用的签名OID。 |
1.2.156.197.1.501 |
sm2sign-with-sm3 | 中国SM2签名算法与SM3哈希函数结合。 |
椭圆曲线参数 (命名曲线)
这些OID作为ecPublicKey算法的参数,指定了具体使用的是哪一条椭圆曲线。
| OID | 名称 | 简要说明 |
|---|---|---|
1.2.840.10045.3.1.7 |
prime256v1 | 也称为NIST P-256或secp256r1,是证书中ECC公钥所使用的曲线。 |
1.3.132.0.34 |
secp384r1 | 也称为NIST P-384,一种安全性更高的椭圆曲线。 |
1.3.132.0.10 |
secp256k1` | 比特币和许多其他加密货币所使用的椭圆曲线。 |
国家商用密码算法 (中国)
以下是符合中国标准的一些常用算法OID。
| OID | 名称 | 简要说明 |
|---|---|---|
1.2.156.197.1.301 |
SM2 | 椭圆曲线公钥密码算法(用于签名、加密、密钥交换)。 |
1.2.156.197.1.401 |
SM3 | 密码杂凑算法(哈希算法)。 |
1.2.156.197.1.104 |
SM4 | 分组密码算法(对称加密)。 |
为什么公钥可以被公开提取,却不能用来伪造证书简答
因为证书的完整性和真实性依赖于CA(证书颁发机构)的数字签名,而这个签名只能用 CA 的私钥生成。从证书中提取的公钥只是证书的一部分(属于证书持有者),它无法生成或验证 CA 的签名。因此,即使拥有完整的证书内容(包括公钥),也无法在没有 CA 私钥的情况下“重新生成”一个能被他人信任的证书。
详细解释
一张 X.509 数字证书由三部分核心数据构成:
- 主体信息:包括证书持有者的名称、公钥、有效期、序列号、颁发者等。
- 签名算法:CA 对上述信息进行签名时使用的算法(如
ecdsaWithSHA256)。 - CA 的数字签名值:CA 使用自己的私钥对第1部分(主体信息)进行哈希和加密运算后得到的签名。
当从证书中提取公钥时,得到的是 第1部分中的“主体公钥”,它是证书持有者的公钥,而不是 CA 的公钥。CA 的公钥通常存储在另一个证书(CA 的根证书或中间证书)中,用于验证签名。
为什么无法根据提取的公钥还原整个证书?
缺少 CA 的签名:证书的“法律效力”完全来自 CA 的数字签名。这个签名是 CA 用其私钥对整个主体信息(包括的公钥、域名、有效期等)计算出来的。如果篡改任何一位数据(比如修改有效期),签名就会失效。要产生新的有效签名,必须拥有 CA 的私钥——而这是绝对保密的。
公钥与私钥的分离:从证书中提取的公钥是公开的,任何人都可以拿到。但对应的私钥(证书持有者自己保管的)是不公开的。CA 的私钥更是最高机密。所以,用公开的公钥去“签名”是行不通的,因为签名操作需要私钥。
证书结构中的固定内容:证书还包含颁发者名称、序列号、有效期、扩展信息等。这些信息必须与 CA 签名时的内容完全一致。即使复制了所有这些内容,也无法复制 CA 的私钥来重新生成签名。
一个类比:房产证与公章
可以把证书想象成一本房产证:
- 证书内容:房产证上写的房屋地址、面积、所有人姓名(相当于证书中的主体信息和公钥)。
- CA 的数字签名:相当于房产证上盖的房管局公章(CA 的私钥盖的章)。
- 提取的公钥:相当于房产证上写的“所有人姓名”,这个信息是公开的,谁都可以看到。
问题:知道了房屋所有人姓名,就能自己打印一本房产证,然后盖上房管局的公章吗?不能,因为没有房管局的公章(CA 私钥)。即使完全复制了房产证上的所有文字,没有那个官方公章,它就是一张废纸,没人会相信。
为什么证书必须包含公钥?
公钥放在证书里的目的,就是让通信对方能够安全地获得这个公钥,并确信这个公钥属于证书上声明的实体。而保证这种“确信”的,正是 CA 的签名。
所以,证书 = 可信任的包装(CA 签名) + 核心内容(含公钥)。提取公钥只是取出了核心内容,但没有那个可信任的包装,它就无法在公开网络上建立信任。
如果尝试伪造证书会怎样?
假设想伪造一个 *.aa.demo.com.cn 的证书:
- 可以从合法证书中提取出它的公钥。
- 可以复制它的所有主体信息(域名、有效期等)。
- 用自己的私钥对这份伪造的信息签名(或者随便编一个签名)。
- 把这个伪造的证书发给浏览器。
浏览器验证时会:
- 查看颁发者信息(比如说是由 GSM Association 签发的)。
- 去系统中找 GSM Association 的根证书(里面包含 GSM Association 的公钥)。
- 用 GSM Association 的公钥去验证提供的证书上的签名。
结果:验证失败,因为的签名不是用 GSM Association 的私钥生成的,浏览器会弹出安全警告,拒绝连接。
- 不能根据提取的公钥生成原来的证书数据,因为证书的完整性依赖于 CA 的私钥签名。
- 公钥只是证书内容的一部分,用于后续的安全通信(如验证对方身份、加密会话),而证书本身的合法性由 CA 签名保证。
- 证书的信任链正是通过“公钥验证签名”的方式层层递进,确保每一个证书都是可信的。