简述

SM2是非对称加密算法

它是基于椭圆曲线密码的公钥密码算法标准,其秘钥长度256bit,包含数字签名、密钥交换和公钥加密,用于替换RSA/DH/ECDSA/ECDH等国际算法。可以满足电子认证服务系统等应用需求,由国家密码管理局于2010年12月17号发布。

SM2采用的是ECC 256位的一种,其安全强度比RSA 2048位高,且运算速度快于RSA。随着密码技术和计算技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。SM2算法在安全性、性能上都具有优势。

基础知识:椭圆曲线知识点、SM3算法

获取公私钥:

椭圆曲线方程: y 2 = x 3 + a x + b m o d    p y^2=x^3+ax+b \mod p y2=x3+ax+bmodp

  1. 确 认 a 、 b 、 p , 确 认 曲 线 。 确认a、b、p,确认曲线。 abp线
  2. 选 择 一 个 点 P ( x g , y g ) 为 基 点 。 选择一个点P(x_g,y_g)为基点。 P(xg,yg)
  3. 对 曲 线 做 切 线 、 x 对 称 点 运 行 。 次 数 为 d , 运 算 倍 点 为 Q 对曲线做切线、x对称点运行。次数为 d,运算倍点为Q 线线xd,Q
  4. d 为 私 钥 , Q 为 公 钥 d 为私钥,Q为公钥 d,Q

密钥对的生成:

  1. 产 生 随 机 整 数 d [ 1 , n − 2 ] 产生随机整数 d [1,n-2] d[1,n2]
  2. G 为 基 点 , 计 算 点 P = ( x P , y P ) = [ d ] G ; G为基点,计算点 P=(xP,yP)=[d]G; GP=(xP,yP)=[d]G;
  3. 密 钥 对 为 : ( d , P ) 其 中 , d 为 私 钥 , P 为 公 钥 密钥对为: (d,P) 其中,d为私钥,P为公钥 :(d,P)dP

一个很典型的例子:

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)

实现步骤:

  1. e = h a s h ( M ) ⇒ 获 取 消 息 散 列 值 e=hash(M) \qquad \Rightarrow获取消息散列值 e=hash(M)
  2. 产 生 随 机 数 k ⇒ 以 便 即 使 是 同 一 个 消 息 , 每 次 签 名 出 来 的 结 果 不 同 。 产生随机数 k \qquad \Rightarrow以便即使是同一个消息,每次签名出来的结果不同。 k便使
  3. 使 用 随 机 数 , 计 算 椭 圆 曲 线 点 ( x 1 , y 1 ) = [ k ] G 使用随机数,计算椭圆曲线点(x_1,y_1)=[k]G 使线(x1,y1)=[k]G
  4. 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)modnr=0r+k=n,2
  5. 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(krd))modn,s=02
  6. r , s 为 签 名 信 息 。 r,s 为签名信息。 r,s

SM验签

M M M为明文, ( r , s ) (r,s) (r,s)为签名结果,用户公钥 P P P

实现步骤:

  1. e = h a s h ( M ) e=hash(M) e=hash(M)
  2. t = ( r + s ) m o d    n t=(r+s) \mod n t=(r+s)modn
  3. ( x , y ) = [ s ] G + [ t ] P (x,y)=[s]G + [t]P (x,y)=[s]G+[t]P
  4. R = ( e + x ) m o d    n R=(e+x)\mod n R=(e+x)modn
  5. 计 算 R 是 否 等 于 r 计算R是否等于r Rr

[ 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+sd​G+rd​G =sG+sdG+rdG
= ( 1 + d ​ ) s G + r d ​ G \qquad \qquad \quad=(1+d​)sG+rd​G =(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(krd)G+rdG
= ( k − r d ​ ) G + r d ​ G \qquad \qquad \quad=(k−rd​)G+rd​G =(krd)G+rdG
= k G − r d G + r d G \qquad \qquad \quad=kG−rdG+rdG =kGrdG+rdG
= k G = ( x 1 ​ , y 1 ​ ) ​ \qquad \qquad \quad=kG=(x1​,y1​)​ =kG=(x1,y1)

SM加密

M为明文字符串

  1. 获 取 随 机 数 k 获取随机数 k k
  2. ( x 1 , y 1 ) = [ k ] G (x1, y1) = [k]G (x1,y1)=[k]G
  3. S = [ h ] P ⇒ h 为 余 因 子 S=[h]P \qquad \Rightarrow h为余因子 S=[h]Ph
  4. C 1 = ( x 2 , y 2 ) = [ k ] P C1=(x2,y2)= [k]P C1=(x2,y2)=[k]P
  5. 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(x2y2,klen)klenMKDFsm2
  6. C 2 = M + t C2 = M+t C2=M+t
  7. C 3 = H a s h ( x 2 ∥ M ∥ y 2 ) C3 = Hash(x2\parallel M\parallel y2) C3=Hash(x2My2)
  8. C = C 1 ∥ C 2 ∥ C 3 C = C1\parallel C2\parallel C3 C=C1C2C3

SM解密

C为密文字符串,klen为密文中C2的长度

  1. C 1 = C 里 面 获 取 , 验 证 C 1 是 否 满 足 椭 圆 曲 线 。 ⇒ C 2 长 度 确 定 , 可 以 获 取 C 1 内 容 。 C1 = C里面获取 ,验证C1是否满足椭圆曲线。 \qquad \Rightarrow C2长度确定,可以获取C1内容。 C1=CC1线C2C1
  2. S = [ h ] C 1 , S 为 无 穷 点 , 退 出 。 S=[h]C1,S为无穷点,退出。 S=[h]C1S退
  3. ( x 2 , y 2 ) = [ d ] C 1 (x2,y2)=[d]C1 (x2,y2)=[d]C1
  4. t = K D F ( m 2 ∥ y 2 , k l e n ) t=KDF(m2\parallel y2,klen) t=KDF(m2y2,klen)
  5. M ~ = C 2 + t \widetilde{M} = C2+t M =C2+t
  6. 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(x2M y2),u?==C3
  7. M ~ 为 明 文 \widetilde{M}为明文 M

代码实现

首先,需要导入包gmssl

pip install gmssl

生成公私钥算法
sm2utils.py

from random import SystemRandom

class CurveFp:
	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
)

def multiply(a, n, N, A, P):
	return fromJacobian(jacobianMultiply(toJacobian(a), n, N, A, P), P)

def add(a, b, A, P):
	return fromJacobian(jacobianAdd(toJacobian(a), toJacobian(b), A, P), P)

def inv(a, n):
	if a == 0:
		return 0
	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

def toJacobian(Xp_Yp):
	Xp, Yp = Xp_Yp
	return (Xp, Yp, 1)

def fromJacobian(Xp_Yp_Zp, P):
	Xp, Yp, Zp = Xp_Yp_Zp
	z = inv(Zp, P)
	return ((Xp * z**2) % P, (Yp * z**3) % P)

def jacobianDouble(Xp_Yp_Zp, A, P):
	Xp, Yp, Zp = Xp_Yp_Zp
	if not 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)

def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
	Xp, Yp, Zp = Xp_Yp_Zp
	Xq, Yq, Zq = Xq_Yq_Zq
	if not Yp:
		return (Xq, Yq, Zq)
	if not 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)

def jacobianMultiply(Xp_Yp_Zp, n, N, A, P):
	Xp, Yp, Zp = Xp_Yp_Zp
	if Yp == 0 or n == 0:
		return (0, 0, 1)
	if n == 1:
		return (Xp, Yp, Zp)
	if n < 0 or 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)

class PrivateKey:
	def __init__(self, curve=sm2p256v1, secret=None):
		self.curve = curve
		self.secret = secret or SystemRandom().randrange(1, curve.N)

	def publicKey(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)

	def toString(self):
		return "{}".format(str(hex(self.secret))[2:].zfill(64))

class PublicKey:
	def __init__(self, x, y, curve):
		self.x = x
		self.y = y
		self.curve = curve

	def toString(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))

加解密算法
封装类 sm2encryp.py

from gmssl import sm2
from base64 import b64encode, b64decode
# sm2的公私钥
SM2_PRIVATE_KEY = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
SM2_PUBLIC_KEY = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'

sm2_crypt = sm2.CryptSM2(public_key=SM2_PUBLIC_KEY, private_key=SM2_PRIVATE_KEY)


class sm2Encrypt:
    # 加密
    def encrypt(self, info):
        encode_info = sm2_crypt.encrypt(info.encode(encoding="utf-8"))
        encode_info = b64encode(encode_info).decode()  # 将二进制bytes通过base64编码
        return encode_info

    # 解密
    def decrypt(self, info):
        decode_info = b64decode(info.encode())  # 通过base64解码成二进制bytes
        decode_info = sm2_crypt.decrypt(decode_info).decode(encoding="utf-8")
        return decode_info


if __name__ == "__main__":
    origin_pwd = '123456'
    sm2 = sm2Encrypt()
    # 加密的密码
    encrypy_pwd = sm2.encrypt(origin_pwd)
    print(encrypy_pwd)
    # 解密的密码
    decrypt_pwd = sm2.decrypt(encrypy_pwd)
    print(decrypt_pwd)

当跟sm2encryp.py在一个文件夹是可以直接引用它

from sm2encryp import sm2Encrypt
pass_encrypt = sm2Encrypt()
pwd = pass_encrypt.decrypt("H24OlVZgSTtevCW138O+C5PlZp8OiD920JnpVr7r9ndkGBWFZUVDD48iIVrZRnamgosV5910m9k0438WpIyi0guEt8F5inG7Y5A51whRfdPZ+qdvWVQxI857CBEzkb3h1bMp1ETQ")
print(pwd)

参考

https://www.jianshu.com/p/efc43060e0aa
https://blog.csdn.net/u013137970/article/details/84573200
https://blog.csdn.net/u014651560/article/details/113744296

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐