此前發(fā)布過(guò)SM2、SM3、SM4、ZUC等文章,以及開源的完整python代碼。近些天看到一篇電子科大蘭同學(xué)的碩士畢業(yè)論文(蘭修文. ECC計(jì)算算法的優(yōu)化及其在SM2實(shí)現(xiàn)中的運(yùn)用[D]. 成都: 電子科技大學(xué), 2019),文中采用預(yù)計(jì)算加速SM2橢圓曲線基點(diǎn)點(diǎn)乘,將這個(gè)思路用python代碼實(shí)現(xiàn)后,實(shí)測(cè)比起原來(lái)的SM2又有4-5倍的提升。現(xiàn)把全網(wǎng)最快(也是功能實(shí)現(xiàn)最全)的SM2完整python代碼分享出來(lái)(小弟口出狂言,若班門弄斧,還請(qǐng)大佬勿怪O(∩_∩)O)。愿大家同心協(xié)力推動(dòng)國(guó)密算法普及,為國(guó)家網(wǎng)絡(luò)安全添磚加瓦!
介紹其他國(guó)密算法的鏈接如下:
上一篇SM2:國(guó)密算法 SM2 公鑰加密 非對(duì)稱加密 數(shù)字簽名 密鑰協(xié)商 python實(shí)現(xiàn)完整代碼_qq_43339242的博客-CSDN博客_python sm2
SM3:國(guó)密算法 SM3 消息摘要 雜湊算法 哈希函數(shù) 散列函數(shù) python實(shí)現(xiàn)完整代碼_qq_43339242的博客-CSDN博客_國(guó)密sm3
SM4:國(guó)密算法 SM4 對(duì)稱加密 分組密碼 python實(shí)現(xiàn)完整代碼_qq_43339242的博客-CSDN博客_python國(guó)密算法庫(kù)
ZUC:國(guó)密算法 ZUC流密碼 祖沖之密碼 python代碼完整實(shí)現(xiàn)_qq_43339242的博客-CSDN博客_國(guó)密算法代碼
對(duì)上述幾個(gè)算法和實(shí)現(xiàn)不了解的,建議點(diǎn)進(jìn)去看看。下面這篇文章是對(duì)上述的匯總:
國(guó)密算法 SM2公鑰密碼 SM3雜湊算法 SM4分組密碼 python代碼完整實(shí)現(xiàn)_qq_43339242的博客-CSDN博客_python sm2
所有代碼托管在碼云:hggm - 國(guó)密算法 SM2 SM3 SM4 python實(shí)現(xiàn)完整代碼: 國(guó)密算法 SM2公鑰密碼 SM3雜湊算法 SM4分組密碼 python代碼完整實(shí)現(xiàn) 效率高于所有公開的python國(guó)密算法庫(kù) (gitee.com)
?SM2代碼如下:
import random
import math
from hggm.SM3 import digest as sm3
from Crypto.PublicKey import ECC
from Crypto.Math.Numbers import Integer
from Crypto.Util._raw_api import VoidPointer, SmartPointer
# 轉(zhuǎn)換為bytes,第二參數(shù)為字節(jié)數(shù)(可不填)
def to_byte(x, size=None):
if isinstance(x, int):
if size is None: # 計(jì)算合適的字節(jié)數(shù)
size = 0
tmp = x >> 64
while tmp:
size += 8
tmp >>= 64
tmp = x >> (size << 3)
while tmp:
size += 1
tmp >>= 8
elif x >> (size << 3): # 指定的字節(jié)數(shù)不夠則截取低位
x &= (1 << (size << 3)) - 1
return x.to_bytes(size, byteorder='big')
elif isinstance(x, str):
x = x.encode()
if size is not None and len(x) > size: # 超過(guò)指定長(zhǎng)度
x = x[:size] # 截取左側(cè)字符
return x
elif isinstance(x, bytes):
if size is not None and len(x) > size: # 超過(guò)指定長(zhǎng)度
x = x[:size] # 截取左側(cè)字節(jié)
return x
elif isinstance(x, tuple): # 坐標(biāo)形式(x, y)
if size is None:
size = PARA_SIZE
return to_byte(x[0], size) + to_byte(x[1], size)
elif isinstance(x, Integer):
return to_byte(int(x), size)
elif isinstance(x, ECC.EccPoint):
x, y = x.xy
return to_byte(int(x), PARA_SIZE) + to_byte(int(y), PARA_SIZE)
return bytes(x)
SM2_p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
SM2_a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
SM2_b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
SM2_n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
SM2_Gx = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
SM2_Gy = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
PARA_SIZE = 32 # 參數(shù)長(zhǎng)度(字節(jié))
HASH_SIZE = 32 # sm3輸出256位(32字節(jié))
KEY_LEN = 128 # 默認(rèn)密鑰位數(shù)
SM2_w_l_1 = math.ceil(math.ceil(math.log(SM2_n, 2)) / 2) - 1 # w * 2
# 使用Crypto.PublicKey.ECC庫(kù)生成SM2曲線
def gen_ECC_curve(p=SM2_p, b=SM2_b, n=SM2_n, Gx=SM2_Gx, Gy=SM2_Gy):
# 如果未采用SM2標(biāo)準(zhǔn)規(guī)定的參數(shù),則需要用pre_kG函數(shù)重新預(yù)計(jì)算kG,將輸出矩陣作為全局變量kG_tuple,否則無(wú)法正確計(jì)算
if 'sm2' not in ECC._curves or ECC._curves['sm2'].p != p:
ec_context = VoidPointer()
res = ECC._ec_lib.ec_ws_new_context(ec_context.address_of(), to_byte(p, PARA_SIZE), to_byte(b, PARA_SIZE),
to_byte(n, PARA_SIZE), PARA_SIZE, random.getrandbits(64))
if res:
raise ImportError("Error %d initializing SM2 context" % res)
context = SmartPointer(ec_context.get(), ECC._ec_lib.ec_free_context)
ECC._curves['sm2'] = ECC._Curve(p, b, n, Gx, Gy, None, 256, p, context, '', '')
# 創(chuàng)建SM2點(diǎn)
def SM2_Point(xy):
return ECC.EccPoint(xy[0], xy[1], 'sm2')
# 將字節(jié)轉(zhuǎn)換為int
def to_int(byte):
return int.from_bytes(byte, byteorder='big')
# 將列表元素轉(zhuǎn)換為bytes并連接
def join_bytes(*data_list):
return b''.join(to_byte(i) for i in data_list)
# 求最大公約數(shù)
def gcd(a, b):
return a if b == 0 else gcd(b, a % b)
# 求乘法逆元過(guò)程中的輔助遞歸函數(shù)
def get_(a, b):
if b == 0:
return 1, 0
x1, y1 = get_(b, a % b)
x, y = y1, x1 - a // b * y1
return x, y
# 求乘法逆元
def get_inverse(a, p):
# return pow(a, p-2, p) # 效率較低、n倍點(diǎn)的時(shí)候兩種計(jì)算方法結(jié)果會(huì)有不同
if gcd(a, p) == 1:
x, y = get_(a, p)
return x % p
return 1
# 密鑰派生函數(shù)(從一個(gè)共享的秘密比特串中派生出密鑰數(shù)據(jù))
# SM2第3部分 5.4.3
# Z為bytes類型
# klen表示要獲得的密鑰數(shù)據(jù)的比特長(zhǎng)度(8的倍數(shù)),int類型
# 輸出為bytes類型
def KDF(Z, klen):
ksize = klen >> 3
K = bytearray()
for ct in range(1, math.ceil(ksize / HASH_SIZE) + 1):
K.extend(sm3(Z + to_byte(ct, 4)))
return K[:ksize]
# 計(jì)算比特位數(shù)
def get_bit_num(x):
if isinstance(x, int):
num = 0
tmp = x >> 64
while tmp:
num += 64
tmp >>= 64
tmp = x >> num >> 8
while tmp:
num += 8
tmp >>= 8
x >>= num
while x:
num += 1
x >>= 1
return num
elif isinstance(x, str):
return len(x.encode()) << 3
elif isinstance(x, bytes):
return len(x) << 3
return 0
# 預(yù)計(jì)算kG(輸出txt文本,內(nèi)容為32行255列的橢圓曲線點(diǎn)矩陣)
def pre_kG():
mySM2 = SM2()
kG_point = [k * mySM2.G for k in range(1, 256)]
with open('SM2_kG.txt', 'w') as f:
print('[[' + ','.join(map(lambda p: '(0x%x,0x%x)' % p.xy, kG_point)) + '],', file=f)
for i in range(31):
for p in kG_point:
p *= 256
print('[' + ','.join(map(lambda p: '(0x%x,0x%x)' % p.xy, kG_point)) + '],\n', end='', file=f)
print(']', file=f)
gen_ECC_curve()
# 計(jì)算好的kG點(diǎn)坐標(biāo)(來(lái)自pre_kG函數(shù)輸出的文本內(nèi)容)
kG_tuple = [] # 這里本來(lái)不是空著的!PS:說(shuō)我字?jǐn)?shù)太多,所以[]里的大量?jī)?nèi)容就刪掉了,可以在mian函數(shù)里運(yùn)行pre_kG(),并把生成的文本內(nèi)容復(fù)制進(jìn)[]里面。當(dāng)然,更建議直接去gitee下載完整源代碼(●'?'●)
# 將計(jì)算好的kG點(diǎn)坐標(biāo)轉(zhuǎn)換成SM2點(diǎn)
kG_points = [list(map(SM2_Point, plist)) for plist in kG_tuple]
# 采用預(yù)計(jì)算好的數(shù)據(jù)快速計(jì)算kG
def kG(k):
P, i = None, 0
while k:
last_byte = k & 0xff
if last_byte:
if P is None:
P = SM2_Point(kG_tuple[i][last_byte - 1])
else:
P += kG_points[i][last_byte - 1]
k >>= 8
i += 1
return P
# SM2類繼承ECC
class SM2:
# 默認(rèn)使用SM2推薦曲線參數(shù)
def __init__(self, p=SM2_p, a=SM2_a, b=SM2_b, n=SM2_n, G=(SM2_Gx, SM2_Gy), h=1, # 余因子h默認(rèn)為1
ID=None, sk=None, pk=None, genkeypair=True): # genkeypair表示是否自動(dòng)生成公私鑰對(duì)
gen_ECC_curve(p, b, n, G[0], G[1])
self.p, self.a, self.b, self.n, self.Gx, self.Gy, self.G, self.h = p, a, b, n, G[0], G[1], SM2_Point(G), h
# 除曲線外的其他參數(shù)
self.ID = ID if type(ID) in (int, str) else '' # 身份ID(數(shù)字或字符串)
if sk and pk: # 已提供公私鑰對(duì)
try: # 驗(yàn)證該公私鑰對(duì)
if kG(sk) == SM2_Point(pk): # 通過(guò)驗(yàn)證,即使genkeypair=True也不會(huì)重新生成
self.sk, self.pk = sk, pk # 私鑰(int [1,n-2]),公鑰(x, y)
else: # 不合格則生成
self.sk, self.pk = self.gen_keypair()
except ValueError: # 不在曲線上會(huì)報(bào)錯(cuò),重新生成
self.sk, self.pk = self.gen_keypair()
elif genkeypair: # 自動(dòng)生成合格的公私鑰對(duì)
self.sk, self.pk = self.gen_keypair()
# 預(yù)先計(jì)算用到的常數(shù)
self.Z_tmp = to_byte(a) + to_byte(b) + to_byte(G[0]) + to_byte(G[1]) # Z值的中間部分
if hasattr(self, 'sk'): # 簽名時(shí)
self.d_1 = get_inverse(1 + self.sk, n)
# 判斷是否在橢圓曲線上
def on_curve(self, p):
try:
return SM2_Point(p) # 不報(bào)錯(cuò)則返回SM2_Point對(duì)象
except ValueError: # 報(bào)錯(cuò)說(shuō)明不在曲線上
return False
# 生成密鑰對(duì)
# 返回值:d為私鑰,P為公鑰
# SM2第1部分 6.1
def gen_keypair(self, toTuple=True):
d = random.randint(1, self.n - 2)
P = kG(d)
return d, tuple(map(int, P.xy)) if toTuple else P
# 計(jì)算Z
# SM2第2部分 5.5
# ID為數(shù)字或字符串,P為公鑰(不提供參數(shù)時(shí)返回自身Z值)
def get_Z(self, ID=None, P=None):
save = False
if P is None: # 不提供參數(shù)
if hasattr(self, 'Z'): # 再次計(jì)算,返回曾計(jì)算好的自身Z值
return self.Z
else: # 首次計(jì)算自身Z值
ID = self.ID
P = self.pk
save = True
entlen = get_bit_num(ID)
ENTL = to_byte(entlen, 2)
Z = sm3(join_bytes(ENTL, ID, self.Z_tmp, P))
if save: # 保存自身Z值
self.Z = Z
return Z
# 數(shù)字簽名
# SM2第2部分 6.1
# 輸入:待簽名的消息M、隨機(jī)數(shù)k(不填則自動(dòng)生成)、輸出類型(默認(rèn)bytes)、對(duì)M是否hash(默認(rèn)是)
# 輸出:r, s(int類型)或拼接后的bytes
def sign(self, M, k=None, outbytes=True, dohash=True):
if dohash:
M_ = join_bytes(self.get_Z(), M)
e = to_int(sm3(M_))
else:
e = to_int(to_byte(M))
while True:
if not k:
k = random.randint(1, self.n - 1)
x1 = int(kG(k).x)
r = (e + x1) % self.n
if r == 0 or r + k == self.n:
k = 0
continue
s = self.d_1 * (k - r * self.sk) % self.n
if s:
break
k = 0
return to_byte((r, s), PARA_SIZE) if outbytes else (r, s)
# 數(shù)字簽名驗(yàn)證
# SM2第2部分 7.1
# 輸入:收到的消息M′及其數(shù)字簽名(r′, s′)、簽名者的身份標(biāo)識(shí)IDA及公鑰PA、對(duì)M是否hash(默認(rèn)是)
# 輸出:True or False
def verify(self, M, sig, IDA, PA, dohash=True):
PA_bytes = to_byte(PA)
PA = self.on_curve(PA)
if not PA:
return False # 對(duì)方公鑰不在橢圓曲線上
if isinstance(sig, bytes):
r = to_int(sig[:PARA_SIZE])
s = to_int(sig[PARA_SIZE:])
else:
r, s = sig
if not 1 <= r <= self.n - 1 or not 1 <= s <= self.n - 1:
return False
if dohash:
M_ = join_bytes(self.get_Z(IDA, PA_bytes), M)
e = to_int(sm3(M_))
else:
e = to_int(to_byte(M))
t = (r + s) % self.n
if t == 0:
return False
PA *= t
PA += kG(s)
x1 = int(PA.x)
# x1 = int((kG(s) + t * PA).x)
R = (e + x1) % self.n
return R == r
# A 發(fā)起協(xié)商
# SM2第3部分 6.1 A1-A3
# 返回rA、RA
def agreement_initiate(self):
return self.gen_keypair()
# B 響應(yīng)協(xié)商(option=True時(shí)計(jì)算選項(xiàng)部分)
# SM2第3部分 6.1 B1-B9
def agreement_response(self, RA, PA, IDA, option=False, rB=None, RB=None, klen=None):
# 參數(shù)準(zhǔn)備
PA_bytes = to_byte(PA)
PA = self.on_curve(PA)
if not PA:
return False, '對(duì)方公鑰不在橢圓曲線上'
x1 = RA[0]
RA = self.on_curve(RA)
if not RA:
return False, 'RA不在橢圓曲線上'
if not hasattr(self, 'sk'):
self.sk, self.pk = self.gen_keypair()
ZA = self.get_Z(IDA, PA_bytes)
ZB = self.get_Z()
# B1-B7
if not rB:
rB, RB = self.gen_keypair()
x2 = RB[0]
x_2 = SM2_w_l_1 + (x2 & SM2_w_l_1 - 1)
tB = (self.sk + x_2 * rB) % self.n
x_1 = SM2_w_l_1 + (x1 & SM2_w_l_1 - 1)
RA_bytes = to_byte(RA)
RA *= x_1
RA += PA
RA *= self.h * tB
xV, yV = RA.xy
# V = (self.h * tB) * (x_1 * RA + PA)
if (xV, yV) == (0, 0):
return False, 'V是無(wú)窮遠(yuǎn)點(diǎn)'
if not klen:
klen = KEY_LEN
KB = KDF(join_bytes((xV, yV), ZA, ZB), klen)
if not option:
return True, (RB, KB)
# B8、B10(可選部分)
tmp = join_bytes(yV, sm3(join_bytes(xV, ZA, ZB, RA_bytes, RB)))
SB = sm3(join_bytes(2, tmp))
S2 = sm3(join_bytes(3, tmp))
return True, (RB, KB, SB, S2)
# A 協(xié)商確認(rèn)
# SM2第3部分 6.1 A4-A10
def agreement_confirm(self, rA, RA, RB, PB, IDB, SB=None, option=False, klen=None):
# 參數(shù)準(zhǔn)備
PB_bytes = to_byte(PB)
PB = self.on_curve(PB)
if not PB:
return False, '對(duì)方公鑰不在橢圓曲線上'
x1, x2 = RA[0], RB[0]
RB = self.on_curve(RB)
if not RB:
return False, 'RB不在橢圓曲線上'
if not hasattr(self, 'sk'):
self.sk, self.pk = self.gen_keypair()
ZA = self.get_Z()
ZB = self.get_Z(IDB, PB_bytes)
# A4-A8
x_1 = SM2_w_l_1 + (x1 & SM2_w_l_1 - 1)
tA = (self.sk + x_1 * rA) % self.n
x_2 = SM2_w_l_1 + (x2 & SM2_w_l_1 - 1)
RB_bytes = to_byte(RB)
RB *= x_2
RB += PB
RB *= self.h * tA
xU, yU = RB.xy
# U = (self.h * tA) * (x_2 * RB + PB)
if (xU, yU) == (0, 0):
return False, 'U是無(wú)窮遠(yuǎn)點(diǎn)'
if not klen:
klen = KEY_LEN
KA = KDF(join_bytes((xU, yU), ZA, ZB), klen)
if not option or not SB:
return True, KA
# A9-A10(可選部分)
tmp = join_bytes(yU, sm3(join_bytes(xU, ZA, ZB, RA, RB_bytes)))
S1 = sm3(join_bytes(2, tmp))
if S1 != SB:
return False, 'S1 != SB'
SA = sm3(join_bytes(3, tmp))
return True, (KA, SA)
# B 協(xié)商確認(rèn)(可選部分)
# SM2第3部分 6.1 B10
def agreement_confirm2(self, S2, SA):
if S2 != SA:
return False, 'S2 != SA'
return True, ''
# 加密
# SM2第4部分 6.1
# 輸入:待加密的消息M(bytes或str類型)、對(duì)方的公鑰PB、隨機(jī)數(shù)k(不填則自動(dòng)生成)
# 輸出(True, bytes類型密文)或(False, 錯(cuò)誤信息)
def encrypt(self, M, PB, k=None):
PB = self.on_curve(PB)
if not PB:
return False, '對(duì)方公鑰不在橢圓曲線上'
M = to_byte(M)
klen = get_bit_num(M)
while True:
if not k:
k = random.randint(1, self.n - 1)
PB *= k
x2, y2 = PB.xy
# x2, y2 = (k * PB).xy
t = to_int(KDF(to_byte((x2, y2)), klen))
if t:
break
k = 0 # 若t為全0比特串則繼續(xù)循環(huán)
C1 = to_byte(kG(k), PARA_SIZE) # (x1, y1)
C2 = to_byte(to_int(M) ^ t, klen >> 3)
C3 = sm3(join_bytes(x2, M, y2))
return True, join_bytes(C1, C2, C3)
# 解密
# SM2第4部分 7.1
# 輸入:密文C(bytes類型)
# 輸出(True, bytes類型明文)或(False, 錯(cuò)誤信息)
def decrypt(self, C):
x1 = to_int(C[:PARA_SIZE])
y1 = to_int(C[PARA_SIZE:PARA_SIZE << 1])
C1 = self.on_curve((x1, y1))
if not C1:
return False, 'C1不滿足橢圓曲線方程'
C1 *= self.sk
x2, y2 = C1.xy
# x2, y2 = (self.sk * C1).xy
klen = len(C) - (PARA_SIZE << 1) - HASH_SIZE << 3
t = to_int(KDF(to_byte((x2, y2)), klen))
if t == 0:
return False, 't為全0比特串'
C2 = C[PARA_SIZE << 1:-HASH_SIZE]
M = to_byte(to_int(C2) ^ t, klen >> 3)
u = sm3(join_bytes(x2, M, y2))
C3 = C[-HASH_SIZE:]
if u != C3:
return False, 'u != C3'
return True, M
注意:pycryptodome老版本沒有int * EccPoint實(shí)現(xiàn),新版本(3.14.1)缺SHA256和ARC4的鏈接庫(kù),我用的3.10.1版本是沒問題的(pip install pycryptodome==3.10.1)
下面來(lái)看性能測(cè)試結(jié)果:
機(jī)器配置不高,處理器i3-10110U,不過(guò)差不了太多,相對(duì)速度還是有參考意義的。點(diǎn)乘測(cè)試都是針對(duì)橢圓曲線基點(diǎn)G進(jìn)行,算法一、二、三是本實(shí)現(xiàn)的未加速版本(純python實(shí)現(xiàn),參考上一篇寫SM2的文章),“加速后”指調(diào)用PyCryptodome庫(kù)進(jìn)行SM2橢圓曲線k·G運(yùn)算。PyCryptodome庫(kù)針對(duì)ECC的NIST P-256參數(shù)進(jìn)行了數(shù)學(xué)優(yōu)化,所以ECC計(jì)算k·G很快。但采用預(yù)計(jì)算以后,SM2的k·G已經(jīng)追上ECC,且SM2的簽名、驗(yàn)證比ECC簽名、驗(yàn)證(即ECDSA算法)更快,可惜的是PyCryptodome庫(kù)目前仍未實(shí)現(xiàn)ECC加密和解密。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-484281.html
用法請(qǐng)參考測(cè)試代碼,需要的請(qǐng)下載完整代碼,再次奉上鏈接:hggm - 國(guó)密算法 SM2 SM3 SM4 python實(shí)現(xiàn)完整代碼: 國(guó)密算法 SM2公鑰密碼 SM3雜湊算法 SM4分組密碼 python代碼完整實(shí)現(xiàn) 效率高于所有公開的python國(guó)密算法庫(kù) (gitee.com)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-484281.html
到了這里,關(guān)于國(guó)密算法 SM2 公鑰加密 數(shù)字簽名 密鑰交換 全網(wǎng)最高效的開源python代碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!