<think>SM2公钥的ASN.1结构遵循X.509和SECG标准。具体来说,SM2公钥在ASN.1中表示为BIT STRING,其内容为未压缩的椭圆曲线公钥点(04||X||Y)。而整个公钥文件(如PEM或DER)则使用SubjectPublicKeyInfo结构。
SubjectPublicKeyInfo结构包含两个主要部分:
1. algorithm:标识公钥的算法(对于SM2,使用OID 1.2.156.10197.1.301)
2. subjectPublicKey:公钥的位字符串(BIT STRING),包含未压缩的公钥点(04||X||Y)
详细结构如下:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
对于SM2公钥:
- algorithm字段的OID为:1.2.156.10197.1.301(这是SM2椭圆曲线公钥的OID)
- subjectPublicKey是一个BIT STRING,其值为:0x04 || X坐标(32字节) || Y坐标(32字节)
因此,一个完整的SM2公钥DER编码的十六进制示例可能如下(以实际解析为准):
30 59 30 13 06 07 2A 86 48 CE 3D 02 01 06 08 2A 81 1C CF 55 01 82 2D 03 42 00 04 95 96 C6 F6 60 0C A5 BF DF 98 2F 9C E6 40 BC D0 8A 9D FA D1 69 31 68 FF E3 6E 83 E4 2A 3E A0 F1 F9 C5 00 EA 9A ED 4F 12 7A FC 06 41 94 CF 5B 50 1F 73 EC F1 D0 3C 43 24 CE AC 48 F4 66 F6 DA FA
分解:
- 30 59: 整个SubjectPublicKeyInfo是一个SEQUENCE,长度为89字节(0x59)
- 30 13: AlgorithmIdentifier序列,长度为19字节(0x13)
- 06 07: OBJECT IDENTIFIER,7字节 -> 2A 86 48 CE 3D 02 01(即1.2.840.10045.2.1,这是ecPublicKey的OID)
- 06 08: 另一个OBJECT IDENTIFIER,8字节 -> 2A 81 1C CF 55 01 82 2D(即1.2.156.10197.1.301,SM2曲线的OID)
- 03 42: BIT STRING,长度为66字节(0x42,即66字节,其中包含一个填充字节00和65字节的公钥点数据)
- 00: 填充位(0表示没有未使用的位)
- 04: 未压缩公钥点标识
- 后面64字节:X和Y坐标(各32字节)
注意:在早期的SM2实现中,有时会省略SM2曲线OID而只使用ecPublicKey OID,但根据GM/T 0009-2012标准,应该同时包含ecPublicKey和SM2曲线OID。
下面我们使用GmSSL命令行和编程方式解析SM2公钥的ASN.1结构。
### 方法1:使用GmSSL命令行解析
```bash
# 生成一个SM2密钥对(如果已有公钥可跳过)
gmssl sm2keygen -pass 1234 -out sm2.pem -pubout sm2pub.pem
# 将公钥转换为DER格式
gmssl pkey -pubin -in sm2pub.pem -outform DER -out sm2pub.der
# 解析DER文件
gmssl asn1parse -inform DER -in sm2pub.der
```
输出示例:
```
0:d=0 hl=2 l= 89 cons: SEQUENCE
2:d=1 hl=2 l= 19 cons: SEQUENCE
4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
13:d=2 hl=2 l= 8 prim: OBJECT :SM2
23:d=1 hl=2 l= 66 prim: BIT STRING
```
### 方法2:使用C语言解析DER公钥
以下代码读取DER格式的SM2公钥文件,并解析其ASN.1结构:
```c
#include <stdio.h>
#include <openssl/asn1.h>
#include <openssl/objects.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
void print_hex(const char *label, const unsigned char *data, size_t len) {
printf("%s: ", label);
for (size_t i = 0; i < len; i++) {
printf("%02X", data[i]);
}
printf("\n");
}
int main() {
const char *der_file = "sm2pub.der";
// 读取DER文件
FILE *fp = fopen(der_file, "rb");
if (!fp) {
perror("Failed to open DER file");
return 1;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 读取DER数据
unsigned char *der_data = malloc(size);
if (!der_data) {
perror("malloc failed");
fclose(fp);
return 1;
}
if (fread(der_data, 1, size, fp) != size) {
perror("fread failed");
free(der_data);
fclose(fp);
return 1;
}
fclose(fp);
// 打印DER数据的十六进制
print_hex("DER data", der_data, size);
// 解析DER公钥
const unsigned char *p = der_data;
EVP_PKEY *pkey = d2i_PUBKEY(NULL, &p, size);
if (!pkey) {
fprintf(stderr, "d2i_PUBKEY failed\n");
free(der_data);
return 1;
}
// 获取算法标识
int pkey_type = EVP_PKEY_id(pkey);
printf("Public key algorithm: %s\n", OBJ_nid2ln(pkey_type));
// 如果是EC密钥,获取曲线
if (pkey_type == EVP_PKEY_EC) {
EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey);
const EC_GROUP *group = EC_KEY_get0_group(ec_key);
int nid = EC_GROUP_get_curve_name(group);
printf("Curve name: %s\n", OBJ_nid2ln(nid));
// 获取公钥点
const EC_POINT *point = EC_KEY_get0_public_key(ec_key);
unsigned char *pub_buf = NULL;
size_t pub_len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
if (pub_len > 0) {
pub_buf = malloc(pub_len);
if (pub_buf) {
EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pub_buf, pub_len, NULL);
print_hex("Public key point", pub_buf, pub_len);
free(pub_buf);
}
}
EC_KEY_free(ec_key);
}
// 清理
EVP_PKEY_free(pkey);
free(der_data);
return 0;
}
```
### 代码解释:
1. **读取DER文件**:以二进制方式读取整个DER文件到内存。
2. **解析公钥**:使用OpenSSL的`d2i_PUBKEY`函数将DER数据转换为EVP_PKEY结构。
3. **获取算法信息**:
- 使用`EVP_PKEY_id`获取公钥算法类型(如EVP_PKEY_EC)
- 使用`OBJ_nid2ln`获取算法名称
4. **获取曲线信息**:
- 如果是EC密钥,则获取曲线名称(SM2曲线NID为1172)
5. **提取公钥点**:
- 使用`EC_POINT_point2oct`获取未压缩格式的公钥点(04开头,后接64字节)
### 编译与运行:
```bash
gcc -o parse_sm2_der parse_sm2_der.c -lcrypto
./parse_sm2_der
```
### 输出示例:
```
DER data: 3059301306072A8648CE3D020106082A811CCF5501822D034200049596C6F6600CA5BFDF982F9CE640BCD08A9DFAD1693168FFE36E83E42A3EA0F1F9C500EA9AED4F127AFC064194CF5B501F73ECF1D03C4324CEAC48F466F6DAFA
Public key algorithm: id-ecPublicKey
Curve name: SM2
Public key point: 049596C6F6600CA5BFDF982F9CE640BCD08A9DFAD1693168FFE36E83E42A3EA0F1F9C500EA9AED4F127AFC064194CF5B501F73ECF1D03C4324CEAC48F466F6DAFA
```
### ASN.1结构详细字段说明:
- 整个结构为SEQUENCE(30)
- 算法标识部分(SEQUENCE):
- OID:1.2.840.10045.2.1(ecPublicKey,06 07 2A 86 48 CE 3D 02 01)
- 参数:另一个OID,1.2.156.10197.1.301(SM2曲线,06 08 2A 81 1C CF 55 01 82 2D)
- 公钥位串(BIT STRING,03):
- 第一个字节00表示没有未使用的位
- 剩余字节为公钥点:04(未压缩)后接64字节(X和Y坐标各32字节)
§§相关问题§§:
1. SM2私钥的ASN.1结构包含哪些字段?
2. 如何手动构建一个SM2公钥的DER编码?
3. 在OpenSSL中如何注册和使用SM2 OID?
4. SM2公钥在传输过程中是否需要使用证书?如何自签名SM2证书?</think># SM2公钥的ASN.1结构详解
SM2公钥的ASN.1结构遵循X.509标准,使用`SubjectPublicKeyInfo`结构表示。完整的结构包含算法标识和公钥位串两部分,下面我将详细解释每个字段及其含义。
## ASN.1结构定义
完整的SM2公钥ASN.1结构如下:
```asn1
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER, -- 公钥算法标识
parameters ANY DEFINED BY algorithm OPTIONAL -- 算法参数
}
```
## 具体字段解析
### 1. 算法标识部分 (AlgorithmIdentifier)
| 字段 | 类型 | 值 | 说明 |
|------|------|----|------|
| algorithm | OBJECT IDENTIFIER | 1.2.840.10045.2.1 | 椭圆曲线公钥算法标识 (ecPublicKey) |
| parameters | OBJECT IDENTIFIER | 1.2.156.10197.1.301 | SM2专用曲线OID |
### 2. 公钥数据部分 (subjectPublicKey)
| 字段 | 类型 | 值 | 说明 |
|------|------|----|------|
| subjectPublicKey | BIT STRING | 04 + X + Y | 未压缩公钥点格式,总长度65字节 |
公钥位串的详细结构:
- 第一个字节:`0x04`,表示未压缩格式
- 后续32字节:X坐标(大端序)
- 最后32字节:Y坐标(大端序)
## 示例解析
以下是一个实际的SM2公钥DER编码的十六进制表示(基于之前提供的公钥):
```
30 59 30 13 06 07 2A 86 48 CE 3D 02 01 06 08 2A 81 1C CF 55 01 82 2D 03 42 00 04 95 96 C6 F6 60 0C A5 BF DF 98 2F 9C E6 40 BC D0 8A 9D FA D1 69 31 68 FF E3 6E 83 E4 2A 3E A0 F1 F9 C5 00 EA 9A ED 4F 12 7A FC 06 41 94 CF 5B 50 1F 73 EC F1 D0 3C 43 24 CE AC 48 F4 66 F6 DA FA
```
### 逐字节解析:
1. **外层SEQUENCE (30 59)**:
- `30`: SEQUENCE标签
- `59`: 长度89字节
2. **AlgorithmIdentifier (30 13)**:
- `30`: SEQUENCE标签
- `13`: 长度19字节
- `06 07 2A 86 48 CE 3D 02 01`: ecPublicKey OID (1.2.840.10045.2.1)
- `06 08 2A 81 1C CF 55 01 82 2D`: SM2曲线OID (1.2.156.10197.1.301)
3. **公钥位串 (03 42 00)**:
- `03`: BIT STRING标签
- `42`: 长度66字节
- `00`: 未使用位数为0
- `04`: 未压缩公钥格式标识
- 后续64字节:公钥点数据 (X:32字节, Y:32字节)
## 使用OpenSSL解析ASN.1结构
以下是C语言代码示例,展示如何解析SM2公钥的ASN.1结构:
```c
#include <stdio.h>
#include <openssl/asn1.h>
#include <openssl/ec.h>
#include <openssl/obj_mac.h>
#include <openssl/pem.h>
void print_hex(const char *label, const unsigned char *data, size_t len) {
printf("%s: ", label);
for (size_t i = 0; i < len; i++) {
printf("%02X", data[i]);
}
printf("\n");
}
int main() {
const char *pem_file = "sm2_pubkey.pem";
// 1. 读取PEM文件
FILE *fp = fopen(pem_file, "r");
if (!fp) {
perror("Failed to open PEM file");
return 1;
}
EVP_PKEY *pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
if (!pkey) {
fprintf(stderr, "Failed to read public key\n");
return 1;
}
// 2. 获取算法标识
int pkey_type = EVP_PKEY_id(pkey);
printf("Public key algorithm: %s\n", OBJ_nid2ln(pkey_type));
// 3. 获取算法参数(曲线OID)
if (pkey_type == EVP_PKEY_EC) {
EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(pkey);
const EC_GROUP *group = EC_KEY_get0_group(ec_key);
int nid = EC_GROUP_get_curve_name(group);
printf("Curve name: %s (NID %d)\n", OBJ_nid2ln(nid), nid);
// 4. 获取公钥点数据
const EC_POINT *point = EC_KEY_get0_public_key(ec_key);
unsigned char *pub_buf = NULL;
size_t pub_len = EC_POINT_point2oct(group, point,
POINT_CONVERSION_UNCOMPRESSED,
NULL, 0, NULL);
if (pub_len > 0) {
pub_buf = malloc(pub_len);
EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED,
pub_buf, pub_len, NULL);
print_hex("Public key point", pub_buf, pub_len);
free(pub_buf);
}
EC_KEY_free(ec_key);
}
// 5. 获取DER编码的完整结构
unsigned char *der = NULL;
int der_len = i2d_PUBKEY(pkey, &der);
if (der_len > 0) {
print_hex("Full DER encoding", der, der_len);
OPENSSL_free(der);
}
EVP_PKEY_free(pkey);
return 0;
}
```
### 代码说明:
1. **读取公钥文件**:使用`PEM_read_PUBKEY`加载PEM格式的公钥
2. **获取算法信息**:
- `EVP_PKEY_id`获取算法类型
- `OBJ_nid2ln`转换为可读名称
3. **获取曲线信息**:
- 对于EC密钥,使用`EC_GROUP_get_curve_name`获取曲线名称
- SM2曲线的NID为1172
4. **提取公钥点**:
- `EC_POINT_point2oct`将公钥点转换为字节表示
- `POINT_CONVERSION_UNCOMPRESSED`指定未压缩格式
5. **获取完整DER编码**:
- `i2d_PUBKEY`获取公钥的DER编码
### 编译与运行:
```bash
gcc -o parse_sm2 parse_sm2.c -lcrypto
./parse_sm2
```
### 输出示例:
```
Public key algorithm: id-ecPublicKey
Curve name: SM2 (NID 1172)
Public key point: 049596C6F6600CA5BFDF982F9CE640BCD08A9DFAD1693168FFE36E83E42A3EA0F1F9C500EA9AED4F127AFC064194CF5B501F73ECF1D03C4324CEAC48F466F6DAFA
Full DER encoding: 3059301306072A8648CE3D020106082A811CCF5501822D034200049596C6F6600CA5BFDF982F9CE640BCD08A9DFAD1693168FFE36E83E42A3EA0F1F9C500EA9AED4F127AFC064194CF5B501F73ECF1D03C4324CEAC48F466F6DAFA
```
## ASN.1结构要点总结
1. **算法标识**:
- 必须包含两个OID:ecPublicKey (1.2.840.10045.2.1) 和 SM2曲线 (1.2.156.10197.1.301)
- 这两个OID共同标识这是SM2公钥
2. **公钥格式**:
- 必须使用未压缩格式 (0x04前缀)
- X和Y坐标各32字节(256位)
- 总长度固定为65字节(04 + 32 + 32)
3. **位串封装**:
- 公钥点封装在BIT STRING中
- BIT STRING的第一个字节表示未使用位数(总是0x00)
4. **兼容性考虑**:
- 某些实现可能省略SM2曲线OID,但这不符合标准
- 完整的结构确保与GM/T 0009-2012标准兼容