公钥密码基础(十):SM2:国密公钥密码算法初步
前言
SM2 是我国标准体系中的椭圆曲线公钥密码算法,覆盖数字签名、公钥加密和密钥交换等功能。它建立在素数有限域上的椭圆曲线点群之上,因此仍然离不开前面文章介绍的几个基础对象:
- 曲线参数和有限域;
- 基点 G G G 及其阶 n n n;
- 私钥标量 d d d;
- 公钥点 P = [ d ] G P=[d]G P=[d]G;
- 模逆、点加法和标量乘法。
但 SM2 不是“把 ECDSA 的名字换成 SM2”,也不是“把 ECDH 的曲线参数换一下”。SM2 在签名中引入了用户身份摘要 Z A Z_A ZA,在加密中规定了基于 KDF 的密文结构,在密钥交换中加入身份绑定和确认信息。这些细节如果遗漏,公式即使能运行,也可能与标准实现不兼容或失去协议安全性。
本文以 SM2 的整体结构为主线,介绍签名、公钥加密和密钥交换的核心流程,重点解释用户身份哈希、签名中的随机数、加密 KDF 和工程实现中的编码问题。具体参数和编码应以使用的国家标准、行业标准及密码库文档为准。
文中公式用于理解协议结构。SM2 的生产实现应使用经过审计的国密密码库,不要根据本文手写一套用于实际业务的密码实现。
现代密码学 专栏:
https://blog.csdn.net/r_feynman_/category_13190241.html
Crypto 密码解析实战靶场:https://blog.csdn.net/r_feynman_/category_13194584.html
一、SM2 的数学基础
1.1 曲线和基点
SM2 使用椭圆曲线点群。抽象地表示为:
E ( F p ) : y 2 ≡ x 3 + a x + b ( m o d p ) E(\mathbb F_p):\quad y^2\equiv x^3+ax+b\pmod p E(Fp):y2≡x3+ax+b(modp)
选定基点 G G G,其阶为 n n n,并有:
n G = O nG=\mathcal O nG=O
用户 A A A 选择私钥:
d A ∈ { 1 , 2 , … , n − 2 } d_A\in\{1,2,\ldots,n-2\} dA∈{1,2,…,n−2}
计算公钥:
P A = [ d A ] G P_A=[d_A]G PA=[dA]G
不同标准会对曲线域参数、点坐标长度、参数命名和编码给出明确规定,不能只根据“都是 256 位椭圆曲线”判断两个实现相互兼容。
1.2 标量与坐标的两个模数
和其他 ECC 协议一样,SM2 中有两个重要模数:
- 坐标和曲线方程在 F p \mathbb F_p Fp 中运算;
- 私钥、临时随机数、签名 r , s r,s r,s 等标量通常在模 n n n 的子群中运算。
例如,点乘中的坐标计算使用模 p p p,但签名中的:
r = ( e + x 1 ) m o d n r=(e+x_1)\bmod n r=(e+x1)modn
使用的是模 n n n。把这里的 n n n 错写成 p p p,会得到看似合理但完全不兼容的签名。
1.3 SM2 的三个功能
SM2 标准体系通常涉及三类流程:
- 数字签名:证明签名者持有私钥,并绑定消息和用户身份;
- 公钥加密:使用接收者公钥保护消息机密性,并通过摘要检查完整性;
- 密钥交换:双方通过临时密钥和长期密钥共同计算共享密钥,并可进行身份确认。
三者底层都使用椭圆曲线点乘,但输入、临时量、哈希和输出格式不同,不能把某一个流程的公式直接套到另一个流程。

图1:SM2三类功能共享ECC点乘骨架图解
SM2签名验签、公钥加密、密钥交换三大功能复用同一套ECC标量乘法底层:私钥 d A d_A dA与基点 G G G执行标量乘法 P A = [ d A ] G P_A=[d_A]G PA=[dA]G计算得到公钥 P A P_A PA;公钥生成后,向上支撑签名验证、公钥加密、密钥交换三类上层能力,工程上可复用点加、点倍的核心代码。

图2:两个模数不能混用图解
SM2存在两套独立模运算空间,不可混淆:左侧模 p p p用于曲线相关运算,点加、点倍、曲线方程 y 2 = x 3 + a x + b ( m o d p ) y^2=x^3+ax+b \pmod{p} y2=x3+ax+b(modp)、曲线上点坐标都在 F p \mathbb F_p Fp下计算;右侧模 n n n用于标量相关运算,私钥 d d d、随机数 k k k、签名分量 ( r , s ) (r,s) (r,s)、哈希值 e e e等标量,签名公式均在 m o d n \bmod\ n mod n中计算。
二、SM2 签名中的用户身份摘要 Z A Z_A ZA
2.1 为什么签名不只哈希消息
普通 ECDSA 通常对消息计算:
e = H ( M ) e=H(M) e=H(M)
SM2 签名会先把用户身份、曲线参数和公钥绑定起来,生成用户身份摘要:
Z A = H v ( ENTL ∥ I D A ∥ a ∥ b ∥ x G ∥ y G ∥ x A ∥ y A ) Z_A=H_v(\operatorname{ENTL}\mathbin\|ID_A\mathbin\|a\mathbin\|b\mathbin\|x_G\mathbin\|y_G\mathbin\|x_A\mathbin\|y_A) ZA=Hv(ENTL∥IDA∥a∥b∥xG∥yG∥xA∥yA)
其中:
- H v H_v Hv 是标准规定的哈希算法;
- I D A ID_A IDA 是签名者的用户身份标识;
- ENTL \operatorname{ENTL} ENTL 表示身份标识的比特长度编码;
- a , b a,b a,b 是曲线参数;
- ( x G , y G ) (x_G,y_G) (xG,yG) 是基点坐标;
- ( x A , y A ) (x_A,y_A) (xA,yA) 是签名者公钥坐标;
- ∥ \| ∥ 表示按规定格式拼接。
然后对消息计算:
e = H v ( Z A ∥ M ) e=H_v(Z_A\mathbin\|M) e=Hv(ZA∥M)
这意味着 SM2 的签名摘要不仅取决于消息,还取决于用户身份、曲线参数和公钥。相同消息由不同用户签名时,摘要通常不同。
2.2 I D ID ID 不是可有可无的备注
实现 SM2 时必须明确:
- 使用什么用户身份字符串;
- 身份字符串采用什么字符编码;
- ENTL \operatorname{ENTL} ENTL 按比特还是字节计算;
- 拼接时每个整数使用多少字节、什么字节序;
- 签名者和验证者是否使用完全相同的身份与参数。
不能一方使用默认身份,另一方使用业务用户名;也不能把 UTF-8 字节长度直接当成标准要求的比特长度而不确认编码规则。 Z A Z_A ZA 不一致时,即使私钥和消息都正确,签名也会验证失败。
2.3 预计算 Z A Z_A ZA
在一个用户、曲线参数和身份固定的场景中, Z A Z_A ZA 可以预先计算并缓存,以减少每条消息的重复工作。但缓存必须与以下信息绑定:
- 用户身份;
- 公钥;
- 曲线参数;
- 使用的哈希算法和编码规则。
公钥或身份发生变化时,不能继续复用旧的 Z A Z_A ZA。

图1: Z A Z_A ZA 输入拼接结构图解
Z A = H ( ENTL ∥ I D A ∥ a ∥ b ∥ G ∥ P A ) Z_A = H(\operatorname{ENTL} \mathbin\| ID_A \mathbin\| a \mathbin\| b \mathbin\| G \mathbin\| P_A) ZA=H(ENTL∥IDA∥a∥b∥G∥PA),按固定字节长度依次拼接:ENTL占2字节、 I D A ID_A IDA占16字节、曲线参数 a a a占32字节、 b b b占32字节、基点坐标 G ( x G , y G ) G(x_G,y_G) G(xG,yG)占64字节、公钥坐标 P A ( x A , y A ) P_A(x_A,y_A) PA(xA,yA)占64字节,整体作为哈希输入用于生成用户身份摘要。

图2:身份ID变更对 Z A Z_A ZA的影响图解
Z A Z_A ZA是哈希输出,雪崩效应显著:仅修改输入里的用户 I D A ID_A IDA,最终生成的32字节 Z A Z_A ZA摘要会整体发生改变,不会只局部变动;这也是SM2中签名、验签双方 Z A Z_A ZA必须完全一致,否则验签失败的核心原因。
三、SM2 数字签名流程
3.1 签名输入
签名者拥有私钥 d A d_A dA 和公钥 P A = [ d A ] G P_A=[d_A]G PA=[dA]G。对消息 M M M:
- 计算用户身份摘要 Z A Z_A ZA;
- 计算:
e = H v ( Z A ∥ M ) e=H_v(Z_A\mathbin\|M) e=Hv(ZA∥M) - 生成一次性随机数 k k k;
- 通过点乘得到临时点;
- 计算签名 ( r , s ) (r,s) (r,s)。
3.2 选择 nonce 并计算临时点
随机选择:
k ← $ { 1 , 2 , … , n − 1 } k\xleftarrow{\$}\{1,2,\ldots,n-1\} k${1,2,…,n−1}
计算:
( x 1 , y 1 ) = [ k ] G (x_1,y_1)=[k]G (x1,y1)=[k]G
然后令:
r = ( e + x 1 ) m o d n r=(e+x_1)\bmod n r=(e+x1)modn
如果 r = 0 r=0 r=0,重新选择 k k k。
3.3 计算 s s s
SM2 签名的核心公式为:
s = ( ( 1 + d A ) − 1 ( k − r d A ) ) m o d n s=((1+d_A)^{-1}(k-rd_A))\bmod n s=((1+dA)−1(k−rdA))modn
如果:
r + k = n r+k=n r+k=n
或:
s = 0 s=0 s=0
则按照标准重新选择 nonce 并重试。最终签名为:
σ = ( r , s ) \sigma=(r,s) σ=(r,s)
与 ECDSA 相比,SM2 的 s s s 公式结构不同,出现了 ( 1 + d A ) − 1 (1+d_A)^{-1} (1+dA)−1,并且 r r r 使用了 e + x 1 e+x_1 e+x1 的形式。不能仅仅修改 ECDSA 的哈希算法或曲线参数,就把 ECDSA 实现当作 SM2。
3.4 SM2 签名验证
验证者首先检查:
1 ≤ r ≤ n − 1 , 1 ≤ s ≤ n − 1 1\le r\le n-1,\qquad1\le s\le n-1 1≤r≤n−1,1≤s≤n−1
然后根据同样的身份和公钥计算:
e = H v ( Z A ∥ M ) e=H_v(Z_A\mathbin\|M) e=Hv(ZA∥M)
计算:
t = ( r + s ) m o d n t=(r+s)\bmod n t=(r+s)modn
若 t = 0 t=0 t=0,验证失败。
接着计算临时点:
( x 1 ′ , y 1 ′ ) = [ s ] G + [ t ] P A (x_1',y_1')=[s]G+[t]P_A (x1′,y1′)=[s]G+[t]PA
最后计算:
R = ( e + x 1 ′ ) m o d n R=(e+x_1')\bmod n R=(e+x1′)modn
当且仅当:
R = r R=r R=r
时接受签名。
3.5 验证公式的代数关系
由签名公式:
s ( 1 + d A ) ≡ k − r d A ( m o d n ) s(1+d_A)\equiv k-rd_A\pmod n s(1+dA)≡k−rdA(modn)
整理得到:
s + r ≡ k + ( r + s ) d A ( m o d n ) s+r\equiv k+(r+s)d_A\pmod n s+r≡k+(r+s)dA(modn)
令:
t = r + s ( m o d n ) t=r+s\pmod n t=r+s(modn)
则:
s + t d A ≡ k ( m o d n ) s+td_A\equiv k\pmod n s+tdA≡k(modn)
于是:
[ s ] G + [ t ] P A = [ s ] G + [ t ] [ d A ] G = [ s + t d A ] G = [ k ] G = ( x 1 , y 1 ) \begin{aligned} [s]G+[t]P_A &=[s]G+[t][d_A]G\\ &=[s+td_A]G\\ &=[k]G\\ &=(x_1,y_1) \end{aligned} [s]G+[t]PA=[s]G+[t][dA]G=[s+tdA]G=[k]G=(x1,y1)
所以验证者恢复了签名阶段的临时点,最终得到:
R = ( e + x 1 ) m o d n = r R=(e+x_1)\bmod n=r R=(e+x1)modn=r
这解释了验证公式为什么成立。

图1:SM2签名从身份绑定摘要到验证点流程图解
SM2签名完整链路:先拼接 Z A ∥ M Z_A \mathbin\| M ZA∥M,经SM3哈希得到 e e e;随机数 k k k做标量乘法得到临时点 k G = ( x 1 , y 1 ) kG=(x_1,y_1) kG=(x1,y1),再计算出签名分量 ( r , s ) (r,s) (r,s);验签阶段通过公式 s G + t P A sG + tP_A sG+tPA还原临时点,完成校验。

图2:身份、消息与签名公式的绑定效果图解
标准SM2签名仅在【原ID+原消息】场景下验证通过;更换ID、篡改消息都会直接拒绝;如果实现时省略 Z A Z_A ZA,则无论原ID原消息、换ID、篡改消息全部验证失败,直观体现 Z A Z_A ZA对身份的强绑定作用。
四、SM2 签名中的 nonce 灾难
4.1 重复 nonce 的后果
SM2 的签名公式为:
s ( 1 + d A ) ≡ k − r d A ( m o d n ) s(1+d_A)\equiv k-rd_A\pmod n s(1+dA)≡k−rdA(modn)
如果两条不同消息错误地使用相同的 k k k,它们会得到相同的临时点 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),但 不一定得到相同的 r r r。这是因为 SM2 的 r r r 还包含消息摘要:
r i = ( e i + x 1 ) m o d n r_i=(e_i+x_1)\bmod n ri=(ei+x1)modn
设两次签名为 ( r 1 , s 1 ) (r_1,s_1) (r1,s1) 和 ( r 2 , s 2 ) (r_2,s_2) (r2,s2)。由签名公式:
s i ( 1 + d A ) ≡ k − r i d A ( m o d n ) s_i(1+d_A)\equiv k-r_i d_A\pmod n si(1+dA)≡k−ridA(modn)
整理可得:
d A ≡ ( k − s i ) ( r i + s i ) − 1 ( m o d n ) d_A\equiv(k-s_i)(r_i+s_i)^{-1}\pmod n dA≡(k−si)(ri+si)−1(modn)
令 A i = r i + s i A_i=r_i+s_i Ai=ri+si。当相关差值可逆时,两条签名可以联立恢复 nonce:
k ≡ ( s 1 A 2 − s 2 A 1 ) ( A 2 − A 1 ) − 1 ( m o d n ) k\equiv\bigl(s_1A_2-s_2A_1\bigr)(A_2-A_1)^{-1}\pmod n k≡(s1A2−s2A1)(A2−A1)−1(modn)
然后代回:
d A ≡ ( k − s 1 ) A 1 − 1 ( m o d n ) d_A\equiv(k-s_1)A_1^{-1}\pmod n dA≡(k−s1)A1−1(modn)
恢复结果应通过:
[ d A ] G = ? P A [d_A]G\stackrel{?}=P_A [dA]G=?PA
进行验证。由于 SM2 的 r r r 包含 e e e,不能直接照搬 ECDSA 中“寻找相同 r r r”的排查规则;但重复 nonce 仍然是严重漏洞,攻击者可以利用多条签名之间的代数关系恢复长期私钥。
因此,SM2 和 ECDSA 一样,签名 nonce 必须独立、不可预测且不能重复。不要因为 SM2 的签名公式不同,就认为随机数故障不再危险。
4.2 随机源和确定性生成
SM2 标准流程通常要求生成安全随机 nonce。工程实现可以根据适用标准和密码库支持,使用经过认证的随机源或规范允许的确定性生成机制,但必须保证:
- 不同消息不会重复使用同一 nonce;
- 随机状态不会因进程回滚、虚拟机快照或容器克隆而重复;
- nonce 生成过程不泄露私钥;
- 失败重试不会产生可预测序列。
对签名服务进行审计时,应重点检查随机源初始化、并发调用、异常重启和硬件随机数故障。

图1:相同nonce:临时点复用,但r仍受消息影响图解
若两次签名使用同一个随机数 k k k,临时点 k G kG kG完全一致;但由于消息不同, e e e值不同,最终算出的签名分量 r 1 r_1 r1与 r 2 r_2 r2并不相等,不能直接通过r判断nonce复用。

图2:重复nonce导致的代数恢复结果图解
SM2签名一旦出现两次不同消息复用同一个nonce k k k的情况,可以通过两组 ( r 1 , s 1 ) (r_1,s_1) (r1,s1)、 ( r 2 , s 2 ) (r_2,s_2) (r2,s2)代数推导,先恢复出临时随机数 k k k,进一步直接算出签名者私钥 d A d_A dA,完整破解密钥。
五、SM2 公钥加密
5.1 加密目标
SM2 公钥加密的目标是让发送者使用接收者公钥加密消息,只有接收者持有私钥才能恢复消息。它是一个混合结构:
- 用临时点乘得到共享椭圆曲线点;
- 用 KDF 从共享点导出掩码;
- 用异或保护消息;
- 用哈希生成完整性校验值。
5.2 加密过程
接收者 Bob 的公钥为:
P B = [ d B ] G P_B=[d_B]G PB=[dB]G
发送者对消息 M M M 选择随机数:
k ← $ { 1 , 2 , … , n − 1 } k\xleftarrow{\$}\{1,2,\ldots,n-1\} k${1,2,…,n−1}
先计算临时公钥点:
C 1 = [ k ] G = ( x 1 , y 1 ) C_1=[k]G=(x_1,y_1) C1=[k]G=(x1,y1)
再计算共享点:
( x 2 , y 2 ) = [ k ] P B (x_2,y_2)=[k]P_B (x2,y2)=[k]PB
使用 KDF 导出与消息同长度的掩码:
t = KDF ( x 2 ∥ y 2 , klen ) t=\operatorname{KDF}(x_2\mathbin\|y_2,\operatorname{klen}) t=KDF(x2∥y2,klen)
如果 KDF 输出全为零,按标准重新选择 k k k。否则计算:
C 2 = M ⊕ t C_2=M\oplus t C2=M⊕t
再计算完整性摘要:
C 3 = H v ( x 2 ∥ M ∥ y 2 ) C_3=H_v(x_2\mathbin\|M\mathbin\|y_2) C3=Hv(x2∥M∥y2)
密文由 C 1 , C 2 , C 3 C_1,C_2,C_3 C1,C2,C3 按标准规定的顺序编码。许多 SM2 实现使用:
C = C 1 ∥ C 3 ∥ C 2 C=C_1\mathbin\|C_3\mathbin\|C_2 C=C1∥C3∥C2
但历史实现和库接口可能接受其他顺序,不能只根据字段名称猜测编码。
5.3 解密过程
Bob 解析并验证 C 1 C_1 C1 后,使用私钥计算:
( x 2 , y 2 ) = [ d B ] C 1 (x_2,y_2)=[d_B]C_1 (x2,y2)=[dB]C1
因为:
[ d B ] C 1 = [ d B ] [ k ] G = [ k ] [ d B ] G = [ k ] P B [d_B]C_1=[d_B][k]G=[k][d_B]G=[k]P_B [dB]C1=[dB][k]G=[k][dB]G=[k]PB
所以 Bob 得到与发送者相同的共享点。接着:
- 用 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 重新计算 KDF 掩码 t t t;
- 计算 M = C 2 ⊕ t M=C_2\oplus t M=C2⊕t;
- 重新计算:
C 3 ′ = H v ( x 2 ∥ M ∥ y 2 ) C_3'=H_v(x_2\mathbin\|M\mathbin\|y_2) C3′=Hv(x2∥M∥y2) - 比较 C 3 ′ C_3' C3′ 与密文中的 C 3 C_3 C3。
只有完整性校验成功时才应返回明文。校验失败不能返回部分明文、详细错误差异或可被利用的中间结果。
5.4 为什么必须验证 C 3 C_3 C3
如果只做:
M = C 2 ⊕ KDF ( x 2 ∥ y 2 ) M=C_2\oplus\operatorname{KDF}(x_2\mathbin\|y_2) M=C2⊕KDF(x2∥y2)
而不验证 C 3 C_3 C3,攻击者就可以篡改 C 2 C_2 C2,导致解密结果中的对应比特发生翻转。 C 3 C_3 C3 把共享点和完整明文绑定起来,是 SM2 加密流程中的完整性保护部分。
在现代系统中,还要统一错误处理和时序,避免通过“密文格式错误”“KDF 全零”“摘要不一致”等不同错误响应构造解密预言机。

图1:SM2密文结构:C1 || C3 || C2图解
SM2标准密文由三部分拼接而成:C1为临时公钥点,占65字节;C3为完整性摘要,占32字节;C2为明文异或生成的密文,占37字节,整体顺序为C1 || C3 || C2。

图2:为什么解密后必须验证C3图解
C3是完整性校验字段,不是冗余装饰。正常密文解密后校验C3匹配;若传输中C2比特被篡改,解密后重新计算的摘要会和C3不匹配,以此检测密文是否被修改。
六、SM2 密钥交换概览
6.1 为什么需要密钥交换协议
单独的静态 ECDH 只说明双方可以得到同一个共享点,但未必提供:
- 双方身份绑定;
- 临时密钥带来的前向安全性;
- 双方确认对方确实计算出了同一个会话密钥;
- 会话参数和密钥长度绑定。
SM2 密钥交换在长期密钥的基础上引入临时密钥、身份摘要和确认值,形成更完整的协议流程。
6.2 基本角色和参数
假设 Alice 和 Bob 各自拥有:
- 长期私钥 d A , d B d_A,d_B dA,dB;
- 长期公钥 P A , P B P_A,P_B PA,PB;
- 用户身份 I D A , I D B ID_A,ID_B IDA,IDB;
- 临时私钥 r A , r B r_A,r_B rA,rB;
- 临时公钥 R A = [ r A ] G , R B = [ r B ] G R_A=[r_A]G,R_B=[r_B]G RA=[rA]G,RB=[rB]G。
双方会根据身份、公钥和曲线参数计算各自的身份摘要。协议还会对临时点的坐标进行约化和组合,具体公式与角色、发起方/响应方顺序以及标准版本有关。
6.3 流程的高层结构
典型流程可以理解为:
- Alice 和 Bob 生成临时密钥对;
- 双方交换临时公钥;
- 各自使用长期私钥、对方长期公钥以及临时密钥计算共享点;
- 把共享点坐标、身份摘要、临时公钥和会话上下文输入 KDF;
- 生成确认哈希,双方互相发送并验证;
- 只有确认成功后才把派生结果作为会话密钥。
确认值的作用是让一方证明自己确实计算出了指定共享点,而不是只收到一个看起来合法的公钥。实际部署中,必须严格按照标准规定的角色顺序、字段拼接顺序和确认值计算方式实现。
6.4 密钥交换中的实现边界
- 长期公钥和临时公钥都要验证;
- 临时私钥必须来自安全随机源;
- 不能忽略用户身份摘要;
- KDF 输入必须包含标准要求的上下文;
- 需要区分确认前的中间值和最终会话密钥;
- 失败时不能把部分中间结果交给上层;
- 双方应明确谁负责发起、谁负责响应以及密钥长度。

图1:SM2密钥交换不是一次简单的ECDH图解
SM2密钥交换同时使用双方长期密钥 ( d A / P A 、 d B / P B ) (d_A/P_A、d_B/P_B) (dA/PA、dB/PB)与临时密钥 ( r A / R A 、 r B / R B ) (r_A/R_A、r_B/R_B) (rA/RA、rB/RB),结合身份ID_A、ID_B算出共享点,经KDF生成会话密钥 K K K,还会交互确认值S1/S2做校验;相比普通ECDH增加了身份绑定与双向确认,工程实现需要严格遵循标准的角色顺序和确认值公式。
七、SM2 与 ECDSA、ECDH 的区别
| 对比项 | ECDSA | SM2 签名 | ECDH | SM2 密钥交换 |
|---|---|---|---|---|
| 核心目标 | 数字签名 | 数字签名 | 共享密钥 | 带身份确认的共享密钥 |
| 消息摘要 | 通常 H ( M ) H(M) H(M) | H ( Z A ∣ M ) H(Z_A|M) H(ZA∣M) | 不直接签名消息 | 含身份和会话上下文 |
| 签名关系 | s = k − 1 ( z + r d ) s=k^{-1}(z+rd) s=k−1(z+rd) | s = ( 1 + d ) − 1 ( k − r d ) s=(1+d)^{-1}(k-rd) s=(1+d)−1(k−rd) | 不适用 | 不适用 |
| 用户身份摘要 | 通常不内置 | Z A Z_A ZA 是重要组成 | 需要额外认证 | 身份绑定在协议中 |
| 加密 KDF | 不适用 | 加密流程使用 KDF | 共享点后需 KDF | 共享点后需 KDF |
| 完整性 | 签名本身 | 签名本身 | 需要认证协议 | 通过确认与协议组合 |
表格只比较核心结构。具体安全性还取决于曲线参数、编码、随机数、密钥派生、点验证和协议实现。

八、SM2 工程实现与 CTF 排查
8.1 签名验证失败时
按顺序检查:
- 曲线参数、基点和阶是否一致;
- 公钥是否满足曲线方程;
- 用户身份 I D ID ID 是否一致;
- ENTL \operatorname{ENTL} ENTL 是否按规定计算;
- Z A Z_A ZA 的拼接字段和字节序是否一致;
- 消息是否使用了原始字节而不是错误的字符串表示;
- 签名是原始
r || s还是 ASN.1 DER; - r , s r,s r,s 是否在合法范围;
- 签名公式是否误用了 ECDSA;
- 验证时是否错误地把 p p p 用作 n n n。
8.2 SM2 密文解密失败时
重点检查:
- C1 是否包含正确前缀和坐标长度;
- C1 是否在曲线上且不为无穷远点;
- 密文顺序是
C1C3C2还是其他约定; - KDF 输入是否为规定的坐标拼接;
- KDF 长度是否按明文字节长度;
- 是否正确处理 KDF 全零重试;
- C3 的输入顺序是否为 x 2 ∥ M ∥ y 2 x_2\|M\|y_2 x2∥M∥y2;
- 比较 C3 时是否使用恒定时间比较。
8.3 不要把小曲线结果当作 SM2 安全性
如果题目把 SM2 曲线替换成很小的 p p p,可以枚举点集、计算基点阶并暴力求离散对数。但这只是 CTF 的参数退化题,不能说明真实 SM2 可以被同样方法破解。
Notebook 中使用的教学参数是:
- E ( F 17 ) : y 2 = x 3 + 2 x + 2 E(\mathbb F_{17}): y^2=x^3+2x+2 E(F17):y2=x3+2x+2;
- 基点 G = ( 5 , 1 ) G=(5,1) G=(5,1),阶为 19 19 19;
- 设教学私钥为 d = 13 d=13 d=13,得到 Q = [ d ] G = ( 16 , 4 ) Q=[d]G=(16,4) Q=[d]G=(16,4);
- 通过枚举点乘结果恢复 d = 13 d=13 d=13。
这组参数只用于展示“参数过小会让离散对数变成可枚举问题”,不代表真实 SM2 曲线存在同样弱点。

九、常见误区
9.1 SM2 就是 ECDSA 加上国密哈希
错误。SM2 签名有不同的身份摘要和签名公式,SM2 加密和密钥交换也有独立的流程。
9.2 Z A Z_A ZA 可以省略
错误。对 SM2 签名来说, Z A Z_A ZA 是把用户身份、公钥和曲线参数绑定到消息摘要的重要部分。省略它会得到另一个协议。
9.3 密文只需要 C1 和 C2
错误。C3 是完整性校验摘要。只解密 C2 而不验证 C3,会让攻击者可以篡改明文。
9.4 只要使用国密算法就自动安全
错误。算法名称不能替代安全实现。随机数、点验证、编码、错误处理、密钥存储和协议组合任何一环出错,都可能导致实际系统不安全。
9.5 SM2 的 nonce 可以固定
错误。SM2 签名的 nonce 与 ECDSA 一样必须安全生成。重复或泄露会导致签名关系泄露私钥。

十、总结
SM2 建立在 ECC 点群之上,但有自己的标准化协议结构:
- 签名先计算用户身份摘要:
Z A = H v ( ENTL ∥ I D A ∥ a ∥ b ∥ G ∥ P A ) Z_A=H_v(\operatorname{ENTL}\|ID_A\|a\|b\|G\|P_A) ZA=Hv(ENTL∥IDA∥a∥b∥G∥PA)
再对 Z A ∥ M Z_A\|M ZA∥M 计算摘要,并使用:
r = ( e + x 1 ) m o d n r=(e+x_1)\bmod n r=(e+x1)modn
s = ( 1 + d A ) − 1 ( k − r d A ) m o d n s=(1+d_A)^{-1}(k-rd_A)\bmod n s=(1+dA)−1(k−rdA)modn - 加密使用临时点、共享点、KDF、异或和 C3 摘要,通常形成
C1 || C3 || C2的密文结构; - 密钥交换把长期密钥、临时密钥、身份摘要、KDF 和确认值组合起来,提供更完整的会话密钥协商。
理解 SM2 的关键不是死记几条公式,而是记住哪些信息必须绑定:用户身份、公钥、曲线参数、消息、临时随机数和协议上下文。 下一篇将从“公钥属于谁”这个问题出发,介绍数字证书、CA、证书链和 PKI 信任体系。

:SM2:国密公钥密码算法初步&spm=1001.2101.3001.5002&articleId=164097491&d=1&t=3&u=e08d5de4014d4cbe80b8b15eb9b42b0c)
2万+

被折叠的 条评论
为什么被折叠?



