由asn1编码引发对sm2签名长度思考

本文源于工作中遇到的SM2签名长度问题,深入探讨了ASN.1编码对签名长度的影响,包括asn.1的TLV结构、高位扩展以及不同环境下的编解码处理策略,强调了使用库进行asn.1编解码的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

起因

工作相关的朋友咨询我sm2签名长度的问题,故有了这篇文章。

过程
  1. 问:按照国密规范,签名长度64,为什么长度可变?

答:要按这个假设,应该的范围是68,69,70,71,72。
最好是按asn1解析

  1. 问:我刚才看了下GM/T 0003.2中的规定,看上去签名结果就应该是(r,s)的字符串形式64个字节的,我后来看了下这个文档的附录A中的两个用例,签名的结果都是r:32字节 s:32字节 签名结果为(r,s) 64字节

答:但是会有格式的问题,不是值的问题。

  1. 确实,我还没找到()这个代表什么意思,并不是直接r||s的表示

答:那r和s其实也就是转换成字节串的坐标了,()应该是坐标表示

  1. 看下了009的7.3,签名asn.1定义确实是64字节的,格式中没有找到处理R和S的其他标识字段,INTEGER应该不是结构吧?

应该是asn1的编码问题,按照asn.1的TLV这个结构

  1. V好像是1或者两个字节?根据V的长度,L可能是1或者2个字节?

V是32。我的理解是,由于T与V的特殊组合,导致实际的V做了高位扩展。这个假设是TL不变的。按照道理说应该不变,若这样,会涉及兼容问题,以及牵扯出的维护的问题。所以TL应该不变,V在特定表示T时,由于其包含内容的特殊性,导致实际的值是会扩展的。

  1. 芯片出来的就是单纯的没有asn.1编码的R||S
  2. <
<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标准兼容
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值