深入ASN.1结构手把手教你从PEM文件里‘抠’出ECC公钥的X和Y坐标含Hex解析当你面对一个ECC公钥的PEM文件时是否好奇过那些看似随机的字符背后隐藏着什么秘密本文将带你像密码学侦探一样逐层拆解ASN.1结构最终提取出椭圆曲线公钥的核心坐标X和Y。这不是一篇泛泛而谈的理论文章而是一份实操指南适合那些喜欢知其所以然的技术极客。1. 准备工作理解ECC公钥的基本结构椭圆曲线密码学ECC公钥在存储和传输时通常采用PEM或DER格式封装。这些格式本质上都是ASN.1抽象语法标记一编码的不同表现形式。让我们先理清几个关键概念PEM格式Base64编码的DER数据以-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----包裹DER格式ASN.1的二进制编码规则ASN.1结构一种描述数据结构的标准使用TLV类型-长度-值三元组一个典型的ECC公钥ASN.1结构包含以下层次SubjectPublicKeyInfo :: SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } AlgorithmIdentifier :: SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }在实际的ECC公钥中subjectPublicKey这个BIT STRING里就包含了我们最终要找的X和Y坐标。2. 工具准备与初始文件处理要完成这项解剖工作我们需要准备以下工具OpenSSL命令行工具用于ASN.1解析xxd或hexdump用于十六进制查看Python用于最终坐标提取和验证假设我们有一个名为ecc_pub.pem的公钥文件内容如下-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJzKODBGKcBqeGKOppWWYQWjOL1sR lFfs42d2Sj57NEV0PlWixXmBi1yqUVWmbCbtTCQjS4xDpVozMwZXGVTug -----END PUBLIC KEY-----2.1 将PEM转换为DER格式首先我们需要将这个PEM文件转换为DER格式因为DER是更底层的二进制表示openssl rsa -pubin -in ecc_pub.pem -outform DER -out ecc_pub.der注意虽然我们使用的是RSA子命令但它同样适用于ECC公钥的格式转换3. 逐层解析ASN.1结构现在我们有了DER文件可以开始真正的解剖工作了。3.1 使用OpenSSL解析ASN.1结构运行以下命令查看ASN.1结构openssl asn1parse -in ecc_pub.der -inform DER输出可能类似于0:d0 hl2 l 89 cons: SEQUENCE 2:d1 hl2 l 19 cons: SEQUENCE 4:d2 hl2 l 7 prim: OBJECT :id-ecPublicKey 13:d2 hl2 l 8 prim: OBJECT :prime256v1 23:d1 hl2 l 66 prim: BIT STRING这个输出告诉我们最外层是一个长度为89字节的SEQUENCE序列里面包含一个19字节的SEQUENCE算法标识其中7字节是OID对象标识符标识这是ECC公钥8字节是OID指定使用的椭圆曲线这里是prime256v1最后是一个66字节的BIT STRING这就是包含公钥点的数据3.2 查看原始十六进制数据为了更深入地理解让我们用xxd查看DER文件的十六进制表示xxd ecc_pub.der输出可能类似于00000000: 3059 3013 0607 2a86 48ce 3d02 0106 082a 0Y0...*.H.....* 00000010: 8648 ce3d 0301 0703 4200 0413 328e 0c11 .H.....B...2... 00000020: 8a70 1a9e 18a3 a9a5 65d8 4168 ce2f 5b11 .p......e.Ah./[. 00000030: 9457 ece3 6776 4a3f b9ec d115 d0f9 568b .W..gvJ?......V. 00000040: 15e6 062d 72a9 4556 99b0 9bb5 3090 8d2e ...-r.EV....0... 00000050: 310e 9568 cccc 195c 6553 ba 1..h...\eS.让我们手动解析这个十六进制数据第一个字节0x30表示SEQUENCE第二个字节0x59表示这个SEQUENCE的长度是89字节接下来的0x30开始算法标识的SEQUENCE0x13表示这个SEQUENCE长19字节0x06表示OID类型0x07表示OID长度7字节接下来的7字节0x2a 0x86 0x48 0xce 0x3d 0x02 0x01是ecPublicKey的OID然后是曲线参数的OID0x06(OID),0x08(长度),0x2a 0x86 0x48 0xce 0x3d 0x03 0x01 0x07(prime256v1)0x03表示BIT STRING0x42表示长度66字节0x00是填充位数这里为00x04表示未压缩的公钥点格式接下来的32字节是X坐标最后32字节是Y坐标4. 提取X和Y坐标现在我们已经定位到X和Y坐标的位置接下来是如何精确提取它们。4.1 手动计算偏移量从十六进制数据中BIT STRING从偏移量0x23十进制35开始前两个字节是0x00 0x04填充和格式标记因此X坐标从0x25十进制37开始共32字节Y坐标紧随其后从0x45十进制69开始共32字节4.2 使用dd命令提取坐标我们可以使用dd命令精确提取这些部分提取X坐标dd ifecc_pub.der bs1 skip37 count32 2/dev/null | xxd -p提取Y坐标dd ifecc_pub.der bs1 skip69 count32 2/dev/null | xxd -p4.3 Python实现自动化提取为了更方便地重复这个过程我们可以用Python编写一个脚本import base64 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec def extract_ecc_coordinates(pem_file): # 读取PEM文件 with open(pem_file, rb) as f: pem_data f.read() # 加载公钥 public_key serialization.load_pem_public_key(pem_data) if not isinstance(public_key, ec.EllipticCurvePublicKey): raise ValueError(这不是一个ECC公钥) # 获取公钥点坐标 numbers public_key.public_numbers() x numbers.x y numbers.y # 转换为十六进制 x_hex hex(x)[2:] # 去掉0x前缀 y_hex hex(y)[2:] # 确保长度是64个字符32字节 x_hex x_hex.zfill(64) y_hex y_hex.zfill(64) return x_hex, y_hex # 使用示例 x, y extract_ecc_coordinates(ecc_pub.pem) print(fX坐标: {x}) print(fY坐标: {y})这个脚本使用了cryptography库它提供了更高级的抽象避免了手动解析ASN.1的复杂性。5. 验证提取的坐标为了确保我们提取的坐标是正确的可以进行以下验证5.1 使用OpenSSL验证首先从PEM文件获取公钥的文本表示openssl ec -pubin -in ecc_pub.pem -text -noout输出中应该包含与提取的X和Y坐标相同的十六进制值。5.2 数学验证对于椭圆曲线密码学公钥点必须满足曲线方程。对于prime256v1曲线也称为P-256或secp256r1曲线方程为y² ≡ x³ - 3x b (mod p)其中p 2²⁵⁶ - 2²²⁴ 2¹⁹² 2⁹⁶ - 1b 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B我们可以用Python验证提取的点是否满足这个方程# 续前面的代码 def verify_ecc_point(x_hex, y_hex): # 曲线参数 (prime256v1) p 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF a -3 b 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B x int(x_hex, 16) y int(y_hex, 16) left (y * y) % p right (pow(x, 3, p) a * x b) % p return left right # 使用示例 is_valid verify_ecc_point(x, y) print(f点在曲线上: {is_valid})如果验证通过说明我们提取的坐标是正确的。
深入ASN.1结构:手把手教你从PEM文件里‘抠’出ECC公钥的X和Y坐标(含Hex解析)
深入ASN.1结构手把手教你从PEM文件里‘抠’出ECC公钥的X和Y坐标含Hex解析当你面对一个ECC公钥的PEM文件时是否好奇过那些看似随机的字符背后隐藏着什么秘密本文将带你像密码学侦探一样逐层拆解ASN.1结构最终提取出椭圆曲线公钥的核心坐标X和Y。这不是一篇泛泛而谈的理论文章而是一份实操指南适合那些喜欢知其所以然的技术极客。1. 准备工作理解ECC公钥的基本结构椭圆曲线密码学ECC公钥在存储和传输时通常采用PEM或DER格式封装。这些格式本质上都是ASN.1抽象语法标记一编码的不同表现形式。让我们先理清几个关键概念PEM格式Base64编码的DER数据以-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----包裹DER格式ASN.1的二进制编码规则ASN.1结构一种描述数据结构的标准使用TLV类型-长度-值三元组一个典型的ECC公钥ASN.1结构包含以下层次SubjectPublicKeyInfo :: SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } AlgorithmIdentifier :: SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }在实际的ECC公钥中subjectPublicKey这个BIT STRING里就包含了我们最终要找的X和Y坐标。2. 工具准备与初始文件处理要完成这项解剖工作我们需要准备以下工具OpenSSL命令行工具用于ASN.1解析xxd或hexdump用于十六进制查看Python用于最终坐标提取和验证假设我们有一个名为ecc_pub.pem的公钥文件内容如下-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJzKODBGKcBqeGKOppWWYQWjOL1sR lFfs42d2Sj57NEV0PlWixXmBi1yqUVWmbCbtTCQjS4xDpVozMwZXGVTug -----END PUBLIC KEY-----2.1 将PEM转换为DER格式首先我们需要将这个PEM文件转换为DER格式因为DER是更底层的二进制表示openssl rsa -pubin -in ecc_pub.pem -outform DER -out ecc_pub.der注意虽然我们使用的是RSA子命令但它同样适用于ECC公钥的格式转换3. 逐层解析ASN.1结构现在我们有了DER文件可以开始真正的解剖工作了。3.1 使用OpenSSL解析ASN.1结构运行以下命令查看ASN.1结构openssl asn1parse -in ecc_pub.der -inform DER输出可能类似于0:d0 hl2 l 89 cons: SEQUENCE 2:d1 hl2 l 19 cons: SEQUENCE 4:d2 hl2 l 7 prim: OBJECT :id-ecPublicKey 13:d2 hl2 l 8 prim: OBJECT :prime256v1 23:d1 hl2 l 66 prim: BIT STRING这个输出告诉我们最外层是一个长度为89字节的SEQUENCE序列里面包含一个19字节的SEQUENCE算法标识其中7字节是OID对象标识符标识这是ECC公钥8字节是OID指定使用的椭圆曲线这里是prime256v1最后是一个66字节的BIT STRING这就是包含公钥点的数据3.2 查看原始十六进制数据为了更深入地理解让我们用xxd查看DER文件的十六进制表示xxd ecc_pub.der输出可能类似于00000000: 3059 3013 0607 2a86 48ce 3d02 0106 082a 0Y0...*.H.....* 00000010: 8648 ce3d 0301 0703 4200 0413 328e 0c11 .H.....B...2... 00000020: 8a70 1a9e 18a3 a9a5 65d8 4168 ce2f 5b11 .p......e.Ah./[. 00000030: 9457 ece3 6776 4a3f b9ec d115 d0f9 568b .W..gvJ?......V. 00000040: 15e6 062d 72a9 4556 99b0 9bb5 3090 8d2e ...-r.EV....0... 00000050: 310e 9568 cccc 195c 6553 ba 1..h...\eS.让我们手动解析这个十六进制数据第一个字节0x30表示SEQUENCE第二个字节0x59表示这个SEQUENCE的长度是89字节接下来的0x30开始算法标识的SEQUENCE0x13表示这个SEQUENCE长19字节0x06表示OID类型0x07表示OID长度7字节接下来的7字节0x2a 0x86 0x48 0xce 0x3d 0x02 0x01是ecPublicKey的OID然后是曲线参数的OID0x06(OID),0x08(长度),0x2a 0x86 0x48 0xce 0x3d 0x03 0x01 0x07(prime256v1)0x03表示BIT STRING0x42表示长度66字节0x00是填充位数这里为00x04表示未压缩的公钥点格式接下来的32字节是X坐标最后32字节是Y坐标4. 提取X和Y坐标现在我们已经定位到X和Y坐标的位置接下来是如何精确提取它们。4.1 手动计算偏移量从十六进制数据中BIT STRING从偏移量0x23十进制35开始前两个字节是0x00 0x04填充和格式标记因此X坐标从0x25十进制37开始共32字节Y坐标紧随其后从0x45十进制69开始共32字节4.2 使用dd命令提取坐标我们可以使用dd命令精确提取这些部分提取X坐标dd ifecc_pub.der bs1 skip37 count32 2/dev/null | xxd -p提取Y坐标dd ifecc_pub.der bs1 skip69 count32 2/dev/null | xxd -p4.3 Python实现自动化提取为了更方便地重复这个过程我们可以用Python编写一个脚本import base64 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec def extract_ecc_coordinates(pem_file): # 读取PEM文件 with open(pem_file, rb) as f: pem_data f.read() # 加载公钥 public_key serialization.load_pem_public_key(pem_data) if not isinstance(public_key, ec.EllipticCurvePublicKey): raise ValueError(这不是一个ECC公钥) # 获取公钥点坐标 numbers public_key.public_numbers() x numbers.x y numbers.y # 转换为十六进制 x_hex hex(x)[2:] # 去掉0x前缀 y_hex hex(y)[2:] # 确保长度是64个字符32字节 x_hex x_hex.zfill(64) y_hex y_hex.zfill(64) return x_hex, y_hex # 使用示例 x, y extract_ecc_coordinates(ecc_pub.pem) print(fX坐标: {x}) print(fY坐标: {y})这个脚本使用了cryptography库它提供了更高级的抽象避免了手动解析ASN.1的复杂性。5. 验证提取的坐标为了确保我们提取的坐标是正确的可以进行以下验证5.1 使用OpenSSL验证首先从PEM文件获取公钥的文本表示openssl ec -pubin -in ecc_pub.pem -text -noout输出中应该包含与提取的X和Y坐标相同的十六进制值。5.2 数学验证对于椭圆曲线密码学公钥点必须满足曲线方程。对于prime256v1曲线也称为P-256或secp256r1曲线方程为y² ≡ x³ - 3x b (mod p)其中p 2²⁵⁶ - 2²²⁴ 2¹⁹² 2⁹⁶ - 1b 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B我们可以用Python验证提取的点是否满足这个方程# 续前面的代码 def verify_ecc_point(x_hex, y_hex): # 曲线参数 (prime256v1) p 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF a -3 b 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B x int(x_hex, 16) y int(y_hex, 16) left (y * y) % p right (pow(x, 3, p) a * x b) % p return left right # 使用示例 is_valid verify_ecc_point(x, y) print(f点在曲线上: {is_valid})如果验证通过说明我们提取的坐标是正确的。