SM2 签名算法 ASN.1 DER 编码 - 签名长度
原创
利刃信安
利刃信安
利刃信安
2026年8月9日 04:00
北京
在小说阅读器读本章
去阅读
SM2 签名算法 ASN.1 DER 编码 - 签名长度
01
背景
SM2 签名原生格式为 r || s,各 32 字节(256 位),共 64 字节。工程上通常用 ASN.1 DER 格式封装:
...c
SEQUENCE { r INTEGER, s INTEGER }
r、s 均为区间 [1, n-1] 内的正整数(n 为 SM2 曲线阶,仅比 2^256 小约 2^-32 量级)。DER 对正整数的编码只有两条规则:
- 1
数值表示为最短的大端字节串:剥离全部冗余前导 0x00(值为 0 时编码为单个 0x00 字节)。
2. 2
若最短字节串首字节的最高位(MSB)为 1,则需在最前面补 0x00,避免被解析为负数。
对 32 字节原始值(r 或 s),设其首部连续 0x00 的个数为 k(0 ≤ k ≤ 31),首个非 0 字节为 B:
| 形态 | 编码后值长度 L | INTEGER 长度字段 |
|---|---|---|
| k = 0,B 的 MSB = 0 | 32 | 0x20 |
| k = 0,B 的 MSB = 1 | 33(前补 0x00) | 0x21 |
| k ≥ 1,B 的 MSB = 0 | 32 - k | 0x1F 及以下 |
| k ≥ 1,B 的 MSB = 1 | 33 - k | 0x20 及以下 |
k ≥ 1 且 B 的 MSB=1 时(如原始值 00B1...),前导 00 必须保留:若删去,首字节变为 0xB1(MSB=1),又需补回 00,等于没删。k ≥ 2(如 000035...)的概率约 2^-16 量级,但规则 1 同样要求全部剥离。
02
总长度公式
记 L\_r、L\_s 为 r、s 编码后的值长度,则:
...Code
SEQUENCE 内容长度 = (1 + 1 + L\_r) + (1 + 1 + L\_s) = 4 + L\_r + L\_s
签名总长度 = 2 + (4 + L\_r + L\_s) = 6 + L\_r + L\_s
常见情形 L ∈ {31, 32, 33},因此总长度 ∈ {68, 69, 70, 71, 72}。SEQUENCE 长度字段取内容长度,66~70 对应 0x42 ~ 0x46,均为单字节。
03
长度分布
r、s 在 [1, n-1] 上近似均匀分布,可精确计算各总长度的占比:
| 总长度 | 组成(L\_r, L\_s) | 占比 |
|---|---|---|
| 68 | (31, 31),或 (30, 32)、(29, 33) 等 | ≈ 0.001% |
| 69 | (31, 32)、(32, 31) 等 | ≈ 0.19% |
| 70 | (32, 32),或 (31, 33)、(33, 31) | ≈ 25.0% |
| 71 | (32, 33)、(33, 32) | ≈ 49.8% |
| 72 | (33, 33) | ≈ 25.0% |
71 字节最常见(约一半),70 与 72 各占约四分之一,69 约千分之二,68 平均约 9 万条签名出现一次。
04
71 字节(最常见)
一个分量补 0x00(33 字节),另一个保持 32 字节:
- 情形 A:r 补 0、s 不变 → 30 45 + 02 21 00 + r(32B) + 02 20 + s(32B)
- 情形 B:s 补 0、r 不变 → 30 45 + 02 20 + r(32B) + 02 21 00 + s(32B)
示例(对应情形 B):
...Code
30 45
02 20 038A7E7FCADB57D3509E689ABEA97AF39E7CC3C9435ACD545F7191D272B8AE57
02 21 00 CB4C0D66BD68D74538320C969E7564A40C7A08627D136992F440A549030346AF
- r 原值 038A...,首字节 0x03(MSB=0),保持 32 字节
- s 原值 CB4C...,首字节 0xCB(MSB=1),补 00 变 33 字节
05
70 字节
三种子情况:
- (A) r、s 均无前导 0 且首字节 MSB=0,无需任何处理
- (B) 一个分量补 0、一个分量删 0,净增量 0(示例 1)
- (C) 一个分量前导 0x00 必须保留,另一个保持 32 字节(示例 2)
示例 1(r 删 0、s 补 0):
...Code
30 44
02 1F 638026332E9FCD750647AFCFC2A5CFD3639D5BF1C9D619F23740241257FD3C
02 21 00 9862D169D9F5BDA16848DB5D1BEDE5A616914680B6CADF1AE373A3D03D365ACF
- r 原值 006380...,0x63(MSB=0)删 0 变 31 字节
- s 原值 9862...,0x98(MSB=1)补 0 变 33 字节
示例 2(s 前导 0x00 必须保留):
...Code
30 44
02 20 36A245F75927E7B8B09C9827F92B3913117771679B70838273BA547751778FC5
02 20 00B1E4BCF9FF3ABADC596936FFBA0CA59C1A0E812DD7F5A9BEDC36863C11D119
- s 原值 00B1...,0xB1(MSB=1),00 必须保留,仍为 32 字节
- 0084D6C1...
(第 2 字节 0x84,MSB=1)同属此情形
06
72 字节
r、s 均需补 0x00:
...Code
30 46 + 02 21 00 + r(32B) + 02 21 00 + s(32B)
07
69 字节
一个分量删 0(31 字节),另一个保持 32 字节。
情形 A(r 删 0):
...Code
30 43
02 1F 2DF69838E2E40DE378C9B8A2FD40FB87425AF4DB9297B9337A9B6586CBFAF3
02 20 67301CAC496D878723EC946F570922A3D84A6B8D2E618C6AD907E36B13D4833F
- r 原值 002DF698...,0x2D(MSB=0)删 0 变 31 字节
情形 B(s 删 0):
...Code
30 43
02 20 53EB989118AD14E1CA91A93769CCE234079169A94BFF645240D7DF59FFEF8B5E
02 1F 3B27E6F7849714C6A8EFD43C9E8045BA9B01477C7ADC3C7AFDF8056EBE05CF
- s 原值 003B27E6...,0x3B(MSB=0)删 0 变 31 字节
08
68 字节(极罕见)
两个分量均删 0(31 + 31 字节);若某分量含多个前导 0x00(如 30 + 32 字节),总长同样为 68。占比约 0.001%,平均约 9 万条签名出现一次。
示例(两个分量各删一个前导 0,随机生成并经 DER 解析校验):
...Code
30 42
02 1F 5AD46B800F372B76CD2609B4F11D6B19E2A4B32864BF89A6EABBBBEEF8965B
02 1F 78F2DFC4670D9D037163CF95EA6C930943FEC3F1FA94B39445AB72BB9CAA66
- r 原值 005AD46B...,0x5A(MSB=0)删 0 变 31 字节
- s 原值 0078F2DF...,0x78(MSB=0)删 0 变 31 字节
09
判断流程
对每个分量取 32 字节无符号表示,统计首部连续 0x00 个数 k 与首个非 0 字节 B:
- 1
k = 0 且 B 的 MSB=0:原样输出,L = 32(0x20)
2. 2
k = 0 且 B 的 MSB=1:前补 0x00,L = 33(0x21)
3. 3
k ≥ 1 且 B 的 MSB=0:剥离 k 个 0x00,L = 32 - k
4. 4
k ≥ 1 且 B 的 MSB=1:剥离前导 0 后再补 0x00(净效果是保留一个 0x00),L = 33 - k
代入 总长度 = 6 + L\_r + L\_s,先写 SEQUENCE 内容长度(4 + L\_r + L\_s),再依次写两个 INTEGER 的 02、长度、值的 TLV。
参考实现:
...python
def der\_signature(raw\_r: bytes, raw\_s: bytes) -> bytes:
\"\"\"raw\_r / raw\_s 为 32 字节原始签名分量,返回完整 DER 编码。\"\"\"
def encode\_int(raw: bytes) -> bytes:
b = raw.lstrip(b\"\x00\") or b\"\x00\"
if b[0] & 0x80:
b = b\"\x00\" + b
return b\"\x02\" + bytes([len(b)]) + b
body = encode\_int(raw\_r) + encode\_int(raw\_s)
return b\"\x30\" + bytes([len(body)]) + body
10
小结
| 签名总长度 | 组成(L\_r, L\_s) | SEQUENCE 头 | 占比 |
|---|---|---|---|
| 68 | 0x1F + 0x1F(或含更短分量) | 30 42 | ≈ 0.001% |
| 69 | 0x1F + 0x20 | 30 43 | ≈ 0.19% |
| 70 | 0x20 + 0x20,或 0x21 + 0x1F | 30 44 | ≈ 25.0% |
| 71 | 0x21 + 0x20 | 30 45 | ≈ 49.8% |
| 72 | 0x21 + 0x21 | 30 46 | ≈ 25.0% |
工程要点:
- 接收缓冲区按 72 字节上限预留即可覆盖全部情形;编码时长度字段必须按上述规则动态计算,否则会出现长度与实际内容不符的非法编码。
- 69 字节及以下合计占比不足 0.2%,但严格验签实现(如 GmSSL、国密安全芯片)会逐字节校验 DER 结构,不应假设签名为固定长度。
我是利刃信安,网络安全和密码安全、数据安全领域的小白。
如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连,我们下篇见
点赞
在看
转发
如有疑问,请联系:Mannix6
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论