素域上SM2选用
y
2
=
x
3
+
a
x
+
b
y^2=x^3+ax+b
y2=x3+ax+b作为加密曲线,令
E
(
F
P
)
=
(
x
,
y
)
∣
y
2
=
x
3
+
a
x
+
b
∪
O
E(F_P)={(x,y)|y^2=x^3+ax+b}∪O
E(FP)=(x,y)∣y2=x3+ax+b∪O, 包含了椭圆曲线上的所有点,其中O是一个无穷远点,不能用有序整数对(x,y)即仿射坐标表示,用
E
(
F
q
)
E(F_q)
E(Fq)表示
E
(
F
q
)
E(F_q)
E(Fq)上元素的个数,称为椭圆曲线的阶。SM2算法使用固定的
F
p
F_p
Fp域,系统参数如下:
F
p
F_p
Fp域的特征p,p是大素数;
F
p
F_p
Fp中的两个元素a和b,它们定义曲线E的方程:
y
2
=
x
3
+
a
x
+
b
y^2=x^3+ax+b
y2=x3+ax+b,a、b满足
(
4
a
3
+
27
b
2
)
m
o
d
p
≠
0
(4a^3+27b^2 )modp≠0
(4a3+27b2)modp=0
基点
G
=
(
x
G
,
y
G
)
∈
E
(
F
P
)
,
G
≠
O
G=(x_G,y_G)∈E(F_P ),G≠O
G=(xG,yG)∈E(FP),G=O
基点G的阶n,n是大素数,要求
n
>
2
191
且
n
>
4
p
1
⁄
2
n>2^{191}且n>4p^{1⁄2}
n>2191且n>4p1⁄2
其中p、a、b、
x
G
x_G
xG、
y
G
y_G
yG和n均为m比特长度的大整数,也可以看作m/8字节长度的字符串,G可以看作一个有序整数对,也可以看作一个m/4字节长度的字符串。 这几个参量的选择,直接影响了算法的安全性。若某椭圆曲线存在优于𝑛^(1∕2)级计算复杂度的攻击方法,则称此曲线为弱椭圆曲线,如Fa上的超奇异曲线(有限域Fa的特征整除q+1-#E(Fq))和 上的异常曲线(#𝐸(𝐹𝑝)=𝑝 )是弱椭圆曲线。在选取时应避开弱椭圆曲线。 为了抗击Pollard-ρ攻击,所选取椭圆曲线的阶 的分解式中应该包含一个大的素数因子,目前应不小于160b比特。 椭圆曲线参量值一般要求满足以下几个条件:
(1)p越大越安全,但越大,计算速度会变慢;
(2)p≠n×h(G为基点,n为点G的阶,h 称为余因子,是椭圆曲线上所有点的个数与n相除的整数部分,h≤4 );
4
a
3
+
27
b
2
≠
0
(
m
o
d
p
)
4a^3+27b^2≠0(modp)
4a3+27b2=0(modp)
(3)
p
×
t
≠
1
(
m
o
d
n
)
,
1
≤
t
≤
20
p×t≠1(modn),1≤t≤20
p×t=1(modn),1≤t≤20
4
a
3
+
27
b
2
≠
0
(
m
o
d
p
)
4a^3+27b^2≠0(modp)
4a3+27b2=0(modp)n 为素数。 已知素域的规模p,求解比特串SEED及 中的元素a、b的步骤如下:
(1)任意选择长度至少为192的比特串SEED;
(2)计算
H
=
H
256
(
S
E
E
D
)
,
并
记
H
=
(
h
255
;
h
254
;
h
253
;
⋅
⋅
⋅
;
h
0
)
H=H_{256} (SEED),并记 H=(h_{255};h_{254};h_{253};⋅⋅⋅;h_0 )
H=H256(SEED),并记H=(h255;h254;h253;⋅⋅⋅;h0),H256是输出杂凑值长度为256比特的密码杂凑函数;
(3)置
R
=
∑
i
=
0
255
h
i
2
i
R=∑_{i=0}^{255}h_i 2^i
R=i=0∑255hi2i
产
生
随
机
整
数
d
[
1
,
n
−
2
]
产生随机整数 d [1,n-2]
产生随机整数d[1,n−2]
G
为
基
点
,
计
算
点
P
=
(
x
P
,
y
P
)
=
[
d
]
G
;
G为基点,计算点 P=(xP,yP)=[d]G;
G为基点,计算点P=(xP,yP)=[d]G;
密
钥
对
为
:
(
d
,
P
)
其
中
,
d
为
私
钥
,
P
为
公
钥
密钥对为: (d,P) 其中,d为私钥,P为公钥
密钥对为:(d,P)其中,d为私钥,P为公钥
一个很典型的例子:
a =0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
b =0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
p =0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
x_g =0x32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7
y_g =0xbc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0
n =0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
SM签名
M
M
M为待签名消息,数字签名结果为
(
r
,
s
)
(r,s)
(r,s) ,用户密钥对
(
d
,
P
)
(d,P)
(d,P)。
实现步骤:
e
=
h
a
s
h
(
M
)
⇒
获
取
消
息
散
列
值
e=hash(M) \qquad \Rightarrow获取消息散列值
e=hash(M)⇒获取消息散列值
使
用
随
机
数
,
计
算
椭
圆
曲
线
点
(
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
=
0
或
者
r
+
k
=
n
,
继
续
第
2
步
。
r=(e+x1) \mod n \qquad \Rightarrow判断:r=0 或者 r+k=n, 继续第2步。
r=(e+x1)modn⇒判断:r=0或者r+k=n,继续第2步。
s
=
(
(
1
+
d
)
−
1
∗
(
k
−
r
∗
d
)
)
m
o
d
n
,
若
s
=
0
,
继
续
第
2
步
s= ((1+d)^{-1} * (k-r*d)) \mod n , 若s=0,继续第2步
s=((1+d)−1∗(k−r∗d))modn,若s=0,继续第2步
r
,
s
为
签
名
信
息
。
r,s 为签名信息。
r,s为签名信息。
SM验签
M
M
M为明文,
(
r
,
s
)
(r,s)
(r,s)为签名结果,用户公钥
P
P
P
实现步骤:
e
=
h
a
s
h
(
M
)
e=hash(M)
e=hash(M)
t
=
(
r
+
s
)
m
o
d
n
t=(r+s) \mod n
t=(r+s)modn
(
x
,
y
)
=
[
s
]
G
+
[
t
]
P
(x,y)=[s]G + [t]P
(x,y)=[s]G+[t]P
R
=
(
e
+
x
)
m
o
d
n
R=(e+x)\mod n
R=(e+x)modn
计
算
R
是
否
等
于
r
计算R是否等于r
计算R是否等于r
[
s
]
G
[s]G
[s]G +
[
t
]
P
[t]P
[t]P的结果可以推导出等于
[
k
]
G
[k]G
[k]G。
验证原理
[
s
]
G
+
[
t
]
P
=
s
G
+
(
r
+
s
)
P
[s]G+[t]P=sG+(r+s)P
[s]G+[t]P=sG+(r+s)P
=
s
G
+
(
r
+
s
)
d
G
\qquad \qquad \quad=sG+(r+s)dG
=sG+(r+s)dG
=
s
G
+
s
d
G
+
r
d
G
\qquad \qquad \quad=sG+sdG+rdG
=sG+sdG+rdG
=
(
1
+
d
)
s
G
+
r
d
G
\qquad \qquad \quad=(1+d)sG+rdG
=(1+d)sG+rdG
=
(
1
+
d
)
(
1
+
d
)
−
1
(
k
−
r
d
)
G
+
r
d
G
\qquad \qquad \quad=(1+d)(1+d)^{−1}(k−rd)G+rdG
=(1+d)(1+d)−1(k−rd)G+rdG
=
(
k
−
r
d
)
G
+
r
d
G
\qquad \qquad \quad=(k−rd)G+rdG
=(k−rd)G+rdG
=
k
G
−
r
d
G
+
r
d
G
\qquad \qquad \quad=kG−rdG+rdG
=kG−rdG+rdG
=
k
G
=
(
x
1
,
y
1
)
\qquad \qquad \quad=kG=(x1,y1)
=kG=(x1,y1)
SM加密
M为明文字符串
获
取
随
机
数
k
获取随机数 k
获取随机数k
(
x
1
,
y
1
)
=
[
k
]
G
(x1, y1) = [k]G
(x1,y1)=[k]G
S
=
[
h
]
P
⇒
h
为
余
因
子
S=[h]P \qquad \Rightarrow h为余因子
S=[h]P⇒h为余因子
C
1
=
(
x
2
,
y
2
)
=
[
k
]
P
C1=(x2,y2)= [k]P
C1=(x2,y2)=[k]P
t
=
K
D
F
(
x
2
∥
y
2
,
k
l
e
n
)
⇒
k
l
e
n
为
M
的
长
度
。
K
D
F
是
s
m
2
的
密
钥
派
生
函
数
t=KDF(x2\parallel y2,klen) \qquad \Rightarrow klen为M的长度。KDF是sm2的密钥派生函数
t=KDF(x2∥y2,klen)⇒klen为M的长度。KDF是sm2的密钥派生函数
C
2
=
M
+
t
C2 = M+t
C2=M+t
C
3
=
H
a
s
h
(
x
2
∥
M
∥
y
2
)
C3 = Hash(x2\parallel M\parallel y2)
C3=Hash(x2∥M∥y2)
C
=
C
1
∥
C
2
∥
C
3
C = C1\parallel C2\parallel C3
C=C1∥C2∥C3
SM解密
C为密文字符串,klen为密文中C2的长度
C
1
=
C
里
面
获
取
,
验
证
C
1
是
否
满
足
椭
圆
曲
线
。
⇒
C
2
长
度
确
定
,
可
以
获
取
C
1
内
容
。
C1 = C里面获取 ,验证C1是否满足椭圆曲线。 \qquad \Rightarrow C2长度确定,可以获取C1内容。
C1=C里面获取,验证C1是否满足椭圆曲线。⇒C2长度确定,可以获取C1内容。
S
=
[
h
]
C
1
,
S
为
无
穷
点
,
退
出
。
S=[h]C1,S为无穷点,退出。
S=[h]C1,S为无穷点,退出。
(
x
2
,
y
2
)
=
[
d
]
C
1
(x2,y2)=[d]C1
(x2,y2)=[d]C1
t
=
K
D
F
(
m
2
∥
y
2
,
k
l
e
n
)
t=KDF(m2\parallel y2,klen)
t=KDF(m2∥y2,klen)
M
~
=
C
2
+
t
\widetilde{M} = C2+t
M=C2+t
u
=
H
a
s
h
(
x
2
∥
M
~
∥
y
2
)
,
u
?
=
=
C
3
u=Hash(x2\parallel \widetilde{M} \parallel y2), u ?== C3
u=Hash(x2∥M∥y2),u?==C3
M
~
为
明
文
\widetilde{M}为明文
M为明文
代码实现
首先,需要导入包gmssl
pip install gmssl
生成公私钥算法 sm2utils.py
from random import SystemRandom
classCurveFp:def__init__(self, A, B, P, N, Gx, Gy, name):
self.A = A
self.B = B
self.P = P
self.N = N
self.Gx = Gx
self.Gy = Gy
self.name = name
sm2p256v1 = CurveFp(
name="sm2p256v1",
A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,
B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93,
P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,
N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123,
Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7,
Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0)defmultiply(a, n, N, A, P):return fromJacobian(jacobianMultiply(toJacobian(a), n, N, A, P), P)defadd(a, b, A, P):return fromJacobian(jacobianAdd(toJacobian(a), toJacobian(b), A, P), P)definv(a, n):if a ==0:return0
lm, hm =1,0
low, high = a % n, n
while low >1:
r = high//low
nm, new = hm-lm*r, high-low*r
lm, low, hm, high = nm, new, lm, low
return lm % n
deftoJacobian(Xp_Yp):
Xp, Yp = Xp_Yp
return(Xp, Yp,1)deffromJacobian(Xp_Yp_Zp, P):
Xp, Yp, Zp = Xp_Yp_Zp
z = inv(Zp, P)return((Xp * z**2)% P,(Yp * z**3)% P)defjacobianDouble(Xp_Yp_Zp, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
ifnot Yp:return(0,0,0)
ysq =(Yp **2)% P
S =(4* Xp * ysq)% P
M =(3* Xp **2+ A * Zp **4)% P
nx =(M**2-2* S)% P
ny =(M *(S - nx)-8* ysq **2)% P
nz =(2* Yp * Zp)% P
return(nx, ny, nz)defjacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
Xq, Yq, Zq = Xq_Yq_Zq
ifnot Yp:return(Xq, Yq, Zq)ifnot Yq:return(Xp, Yp, Zp)
U1 =(Xp * Zq **2)% P
U2 =(Xq * Zp **2)% P
S1 =(Yp * Zq **3)% P
S2 =(Yq * Zp **3)% P
if U1 == U2:if S1 != S2:return(0,0,1)return jacobianDouble((Xp, Yp, Zp), A, P)
H = U2 - U1
R = S2 - S1
H2 =(H * H)% P
H3 =(H * H2)% P
U1H2 =(U1 * H2)% P
nx =(R **2- H3 -2* U1H2)% P
ny =(R *(U1H2 - nx)- S1 * H3)% P
nz =(H * Zp * Zq)% P
return(nx, ny, nz)defjacobianMultiply(Xp_Yp_Zp, n, N, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
if Yp ==0or n ==0:return(0,0,1)if n ==1:return(Xp, Yp, Zp)if n <0or n >= N:return jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P)if(n %2)==0:return jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n //2, N, A, P), A, P)if(n %2)==1:return jacobianAdd(jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n //2, N, A, P), A, P),(Xp, Yp, Zp), A, P)classPrivateKey:def__init__(self, curve=sm2p256v1, secret=None):
self.curve = curve
self.secret = secret or SystemRandom().randrange(1, curve.N)defpublicKey(self):
curve = self.curve
xPublicKey, yPublicKey = multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)return PublicKey(xPublicKey, yPublicKey, curve)deftoString(self):return"{}".format(str(hex(self.secret))[2:].zfill(64))classPublicKey:def__init__(self, x, y, curve):
self.x = x
self.y = y
self.curve = curve
deftoString(self, compressed=True):return{True:str(hex(self.x))[2:],False:"{}{}".format(str(hex(self.x))[2:].zfill(64),str(hex(self.y))[2:].zfill(64))}.get(compressed)if __name__ =="__main__":
priKey = PrivateKey()
pubKey = priKey.publicKey()print(priKey.toString())print(pubKey.toString(compressed =False))