当前位置:   article > 正文

国密算法 SM2 公钥加密 非对称加密 数字签名 密钥协商 python实现完整代码_python sm2加密

python sm2加密

SM2算法是国家密码管理局于2010年12月颁布的中国商用公钥密码标准算法。SM2基于椭圆曲线离散对数问题,计算复杂度是指数级(暂未发现亚指数级或多项式级的计算方法),相较于广泛应用的RSA公钥密码算法,在同等安全程度要求下,SM2所需密钥长度小、处理速度快。由于SM2在安全性、运算性能等方面都优于RSA算法,且具有自主知识产权,我国计划在商用密码体系中用SM2替换RSA算法。

椭圆曲线密码(ECC)的安全性明显强于RSA,参考下图:

采用Python语言编写的国密工具包主要是gmssl-python库和snowland-smx-python(pysmx)库,二者较为完整地实现了SM2、SM3、SM4等国密算法。本工具包涉及的散列运算使用了pysmx库的SM3算法,pysmx库对SM3算法的实现高效而优雅,在此向pysmx库的作者致以诚挚的敬意和感谢!

相较于现有Python国密算法工具包的SM2模块,本工具包的优势主要体现在以下3个方面:

1. 首次开源SM2密钥协商算法。gmssl库和pysmx库仅实现了SM2签名和验证、加密和解密算法,没有实现SM2密钥协商算法,互联网上也未找到实现SM2密钥协商算法的Python代码,故本工具包是首次在互联网上开源SM2密钥协商算法的Python代码。

2. 算法实现更为健壮和完整。gmssl库和pysmx库中的椭圆曲线点乘算法仅能输入有限域内的乘数(否则报错),所实现的SM2签名/验证算法不包含标准要求的Z值计算和Hash变换,除核心算法(密钥生成、签名、验证、加密、解密等)之外还缺少标准描述的一些辅助算法,gmssl库仅能输入bytes类型消息;本工具包的点乘算法能够输入任意自然数作为乘数并保证正确性,SM2签名/验证算法完整实现了Z值计算和Hash变换,除核心算法之外还实现了标准描述的一些重要辅助函数(如公钥验证、椭圆曲线系统参数验证等)。

3. 性能更佳。本工具包通过采用更高效的点乘算法、减少数据类型转换、充分运用算术运算加速技巧等途径,明显提高了计算效率。以SM2算法耗时的主要来源——椭圆曲线点乘运算为例进行测试,同等条件下本工具包的平均耗时约为gmssl库的35.5%、pysmx库的61.8%,实际运行签名与验证、加解密等算法同样具备上述幅度的性能优势。

上图中的前三个算法是本工具包实现的(具体描述参考国密局2010年SM2文档,下有链接),实测算法2性能最好,默认用的算法2。

对于需应用国密SM2算法的Python项目,可直接调用本工具包实现SM2数字签名与验证、加解密以及密钥协商等功能,也可基于本工具包提供的椭圆曲线运算相关函数自行设计算法和协议。

参考文献:

  1. 国家密码管理局关于发布《SM2椭圆曲线公钥密码算法》公告[EB/OL].(2010-12-17) [2022-02-20].https://sca.gov.cn/sca/xwdt/2010-12/17/content_1002386.shtml.
  2. GMSSL[EB/OL].(2020-12-09) [2022-02-20].https://github.com/duanhongyi/gmssl.
  3. snowland-smx[EB/OL].(2021-01-27) [2022-02-20].https://gitee.com/snowlandltd/ snowland-smx-python.

“没有网络安全,就没有国家安全。”让我们共同努力,推动国密算法更深层次、更广泛的研究和应用,为国家网络信息安全和自主化尽绵薄之力。

是否觉得看到上面一句就结束了?O(∩_∩)O

代码分三个部分,第一部分是椭圆曲线基础运算封装的类,第二部分是SM2封装的类,第三部分是测试代码。最简单而安全的密钥协商,可以用里面的ECDH,运行很快,当然SM2更安全!同时致敬DH算法,为信息网络安全耕耘已近半个世纪!几种密钥协商算法的运行时间如下图所示:

上图结果均不包括通信开销,其中ECDH和SM2用的是SM2 GB(GB/T 32918.5-2017,信息安全技术 SM2椭圆曲线公钥密码算法 第5部分:参数定义)规定的椭圆曲线参数(与参考文献1《SM2椭圆曲线公钥密码算法》中推荐的参数是一样的),这也是本工具包默认使用的参数。

测试代码按照参考文献1(国密局2010年《SM2椭圆曲线公钥密码算法》)的参数,复现了其结果,说明代码实现是标准且正确的。由于要复现结果,测试代码调用函数的时候输入了固定参数,其实好多参数是不用输入的,不输入就会使用SM2默认参数,或者随机数。

废话不多说了,代码中有详尽注释。SM2怎么用?照着测试代码用就行!

 下面是完整代码(是否完整?能不能跑?一试便知!)。

  1. import random
  2. import time
  3. import math
  4. import numpy as np
  5. from pysmx.SM3 import digest as sm3
  6. # 小素数列表,加快判断素数速度
  7. small_primes = np.array([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
  8. 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109,
  9. 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191,
  10. 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269,
  11. 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353,
  12. 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439,
  13. 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523,
  14. 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617,
  15. 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709,
  16. 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811,
  17. 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907,
  18. 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997])
  19. def is_prime(num):
  20. # 排除0,1和负数
  21. if num < 2:
  22. return False
  23. # 排除小素数的倍数
  24. for prime in small_primes:
  25. if num % prime == 0:
  26. return False
  27. # 未分辨出来的大整数用rabin算法判断
  28. return rabin_miller(num)
  29. def rabin_miller(num):
  30. s = num - 1
  31. t = 0
  32. while s & 1 == 0:
  33. s >>= 1
  34. t += 1
  35. for trials in range(5):
  36. a = random.randrange(2, num - 1)
  37. v = pow(a, s, num)
  38. if v != 1:
  39. i = 0
  40. while v != (num - 1):
  41. if i == t - 1:
  42. return False
  43. else:
  44. i = i + 1
  45. v = v * v % num
  46. return True
  47. # 将字节转换为int
  48. def to_int(byte):
  49. return int.from_bytes(byte, byteorder='big')
  50. # 转换为bytes,第二参数为字节数(可不填)
  51. def to_byte(x, size=None):
  52. if isinstance(x, int):
  53. if size is None: # 计算合适的字节数
  54. size = 0
  55. tmp = x >> 64
  56. while tmp:
  57. size += 8
  58. tmp >>= 64
  59. tmp = x >> (size << 3)
  60. while tmp:
  61. size += 1
  62. tmp >>= 8
  63. elif x >> (size << 3): # 指定的字节数不够则截取低位
  64. x &= (1 << (size << 3)) - 1
  65. return x.to_bytes(size, byteorder='big')
  66. elif isinstance(x, str):
  67. x = x.encode()
  68. if size != None and len(x) > size: # 超过指定长度
  69. x = x[:size] # 截取左侧字符
  70. return x
  71. elif isinstance(x, bytes):
  72. if size != None and len(x) > size: # 超过指定长度
  73. x = x[:size] # 截取左侧字节
  74. return x
  75. elif isinstance(x, tuple) and len(x) == 2 and type(x[0]) == type(x[1]) == int:
  76. # 针对坐标形式(x, y)
  77. return to_byte(x[0], size) + to_byte(x[1], size)
  78. return bytes(x)
  79. # 将列表元素转换为bytes并连接
  80. def join_bytes(data_list):
  81. return b''.join([to_byte(i) for i in data_list])
  82. # 求最大公约数
  83. def gcd(a, b):
  84. return a if b == 0 else gcd(b, a % b)
  85. # 求乘法逆元过程中的辅助递归函数
  86. def get_(a, b):
  87. if b == 0:
  88. return 1, 0
  89. x1, y1 = get_(b, a % b)
  90. x, y = y1, x1 - a // b * y1
  91. return x, y
  92. # 求乘法逆元
  93. def get_inverse(a, p):
  94. # return pow(a, p-2, p) # 效率较低、n倍点的时候两种计算方法结果会有不同
  95. if gcd(a, p) == 1:
  96. x, y = get_(a, p)
  97. return x % p
  98. return 1
  99. def get_cpu_time():
  100. return time.perf_counter()
  101. # 密钥派生函数(从一个共享的秘密比特串中派生出密钥数据)
  102. # SM2第3部分 5.4.3
  103. # Z为bytes类型
  104. # klen表示要获得的密钥数据的比特长度(8的倍数),int类型
  105. # 输出为bytes类型
  106. def KDF(Z, klen):
  107. ksize = klen >> 3
  108. K = bytearray()
  109. for ct in range(1, math.ceil(ksize / HASH_SIZE) + 1):
  110. K.extend(sm3(Z + to_byte(ct, 4)))
  111. return K[:ksize]
  112. # 计算比特位数
  113. def get_bit_num(x):
  114. if isinstance(x, int):
  115. num = 0
  116. tmp = x >> 64
  117. while tmp:
  118. num += 64
  119. tmp >>= 64
  120. tmp = x >> num >> 8
  121. while tmp:
  122. num += 8
  123. tmp >>= 8
  124. x >>= num
  125. while x:
  126. num += 1
  127. x >>= 1
  128. return num
  129. elif isinstance(x, str):
  130. return len(x.encode()) << 3
  131. elif isinstance(x, bytes):
  132. return len(x) << 3
  133. return 0
  134. # 椭圆曲线密码类(实现一般的EC运算,不局限于SM2)
  135. class ECC:
  136. def __init__(self, p, a, b, n, G, h=None):
  137. self.p = p
  138. self.a = a
  139. self.b = b
  140. self.n = n
  141. self.G = G
  142. if h:
  143. self.h = h
  144. self.O = (-1, -1) # 定义仿射坐标下无穷远点(零点)
  145. # 预先计算Jacobian坐标两点相加时用到的常数
  146. self._2 = get_inverse(2, p)
  147. self.a_3 = (a + 3) % p
  148. # 椭圆曲线上两点相加(仿射坐标)
  149. # SM2第1部分 3.2.3.1
  150. # 仅提供一个参数时为相同坐标点相加
  151. def add(self, P1, P2=None):
  152. x1, y1 = P1
  153. if P2 is None or P1 == P2: # 相同坐标点相加
  154. # 处理无穷远点
  155. if P1 == self.O:
  156. return self.O
  157. # 计算斜率k(k已不具备明确的几何意义)
  158. k = (3 * x1 * x1 + self.a) * get_inverse(2 * y1, self.p) % self.p
  159. # 计算目标点坐标
  160. x3 = (k * k - x1 - x1) % self.p
  161. y3 = (k * (x1 - x3) - y1) % self.p
  162. else:
  163. x2, y2 = P2
  164. # 处理无穷远点
  165. if P1 == self.O:
  166. return P2
  167. if P2 == self.O:
  168. return P1
  169. if x1 == x2:
  170. return self.O
  171. # 计算斜率k
  172. k = (y2 - y1) * get_inverse(x2 - x1, self.p) % self.p
  173. # 计算目标点坐标
  174. x3 = (k * k - x1 - x2) % self.p
  175. y3 = (k * (x1 - x3) - y1) % self.p
  176. return x3, y3
  177. # 椭圆曲线上的点乘运算(仿射坐标)
  178. def multiply(self, k, P):
  179. # 判断常数k的合理性
  180. assert type(k) is int and k >= 0, 'factor value error'
  181. # 处理无穷远点
  182. if k == 0 or P == self.O:
  183. return self.O
  184. if k == 1:
  185. return P
  186. elif k == 2:
  187. return self.add(P)
  188. elif k == 3:
  189. return self.add(P, self.add(P))
  190. elif k & 1 == 0: # k/2 * P + k/2 * P
  191. return self.add(self.multiply(k >> 1, P))
  192. elif k & 1 == 1: # P + k/2 * P + k/2 * P
  193. return self.add(P, self.add(self.multiply(k >> 1, P)))
  194. # 输入P,返回-P
  195. def minus(self, P):
  196. Q = list(P)
  197. Q[1] = -Q[1]
  198. return tuple(Q)
  199. # Jacobian加重射影坐标下两点相加
  200. # SM2第1部分 A.1.2.3.2
  201. # 输入点包含两项时为仿射坐标,三项为Jacobian加重射影坐标,两点坐标系可不同
  202. # 两点相同时省略第二个参数
  203. def Jacb_add(self, P1, P2=None):
  204. if P2 is None or P1 == P2: # 相同点相加
  205. # 处理无穷远点
  206. if P1 == self.O:
  207. return self.O
  208. # 根据参数包含的项数判断坐标系(是仿射坐标则转Jacobian坐标)
  209. x1, y1, z1 = P1 if len(P1) == 3 else (*P1, 1)
  210. # t1 = 3 * x1**2 + self.a * pow(z1, 4, self.p)
  211. # t2 = 4 * x1 * y1**2
  212. # t3 = 8 * pow(y1, 4, self.p)
  213. # x3 = (t1**2 - 2 * t2) % self.p
  214. # y3 = (t1 * (t2 - x3) - t3) % self.p
  215. # z3 = 2 * y1 * z1 % self.p
  216. z3 = (y1 * z1 << 1) % self.p
  217. if z3 == 0: # 处理无穷远点
  218. return self.O
  219. T2 = y1 * y1 % self.p
  220. T4 = (T2 << 3) % self.p
  221. T5 = x1 * T4 % self.p
  222. T6 = z1 * z1 % self.p
  223. T1 = (x1 + T6) * (x1 - T6) * 3 % self.p
  224. T1 = (T1 + self.a_3 * T6 * T6) % self.p
  225. T3 = T1 * T1 % self.p
  226. T2 = T2 * T4 % self.p
  227. x3 = (T3 - T5) % self.p
  228. T4 = T5 + (T5 + self.p >> 1) - T3 if T5 & 1 else T5 + (T5 >> 1) - T3
  229. T1 = T1 * T4 % self.p
  230. y3 = (T1 - T2) % self.p
  231. else: # 不同点相加
  232. # 处理无穷远点
  233. if P1 == self.O:
  234. return P2
  235. if P2 == self.O:
  236. return P1
  237. # 根据参数包含的项数判断坐标系(是仿射坐标则转Jacobian坐标)
  238. x1, y1, z1 = P1 if len(P1) == 3 else (*P1, 1)
  239. x2, y2, z2 = P2 if len(P2) == 3 else (*P2, 1)
  240. if z2 != 1 and z1 != 1:
  241. z1_2 = z1 * z1 % self.p
  242. z2_2 = z2 * z2 % self.p
  243. t1 = x1 * z2_2 % self.p
  244. t2 = x2 * z1_2 % self.p
  245. t3 = t1 - t2
  246. z3 = z1 * z2 * t3 % self.p
  247. if z3 == 0: # 处理无穷远点
  248. return self.O
  249. t4 = y1 * z2 * z2_2 % self.p
  250. t5 = y2 * z1 * z1_2 % self.p
  251. t6 = t4 - t5
  252. t7 = t1 + t2
  253. t8 = t4 + t5
  254. t3_2 = t3 * t3 % self.p
  255. x3 = (t6 * t6 - t7 * t3_2) % self.p
  256. t9 = (t7 * t3_2 - (x3 << 1)) % self.p
  257. y3 = (t9 * t6 - t8 * t3 * t3_2) * self._2 % self.p
  258. else: # 可简化计算
  259. if z1 == 1: # 确保第二个点的z1=1
  260. x1, y1, z1, x2, y2 = x2, y2, z2, x1, y1
  261. T1 = z1 * z1 % self.p
  262. T2 = y2 * z1 % self.p
  263. T3 = x2 * T1 % self.p
  264. T1 = T1 * T2 % self.p
  265. T2 = T3 - x1
  266. z3 = z1 * T2 % self.p
  267. if z3 == 0: # 处理无穷远点
  268. return self.O
  269. T3 = T3 + x1
  270. T1 = T1 - y1
  271. T4 = T2 * T2 % self.p
  272. T5 = T1 * T1 % self.p
  273. T2 = T2 * T4 % self.p
  274. T3 = T3 * T4 % self.p
  275. T4 = x1 * T4 % self.p
  276. x3 = T5 - T3 % self.p
  277. T2 = y1 * T2 % self.p
  278. T3 = T4 - x3
  279. T1 = T1 * T3 % self.p
  280. y3 = T1 - T2 % self.p
  281. # T1 = z1 * z1 % self.p
  282. # T3 = x2 * T1 % self.p
  283. # T2 = T3 - x1
  284. # z3 = z1 * T2 % self.p
  285. # if z3 == 0: # 处理无穷远点
  286. # return self.O
  287. # T1 = (T1 * y2 * z1 - y1) % self.p
  288. # T4 = T2 * T2 % self.p
  289. # x3 = T1 * T1 - (T3 + x1) * T4 % self.p
  290. # T1 = T1 * (x1 * T4 - x3) % self.p
  291. # y3 = T1 - y1 * T2 * T4 % self.p
  292. return x3, y3, z3
  293. # Jacobian加重射影坐标下的点乘运算
  294. # SM2第1部分 A.3
  295. # 输入点包含两项时为仿射坐标,三项为Jacobian坐标
  296. # conv=True时结果转换为仿射坐标,否则不转换
  297. # algo表示选择的算法, r表示算法三(滑动窗法)的窗口值
  298. def Jacb_multiply(self, k, P, conv=True, algo=2, r=5):
  299. # 处理无穷远点
  300. if k == 0 or P == self.O:
  301. return self.O
  302. # 仿射坐标转Jacobian坐标
  303. # if len(P) == 2:
  304. # P = (*P, 1)
  305. # 算法一:二进制展开法
  306. if algo == 1:
  307. Q = P
  308. for i in bin(k)[3:]:
  309. Q = self.Jacb_add(Q)
  310. if i == '1':
  311. Q = self.Jacb_add(Q, P)
  312. # 算法二:加减法
  313. elif algo == 2:
  314. h = bin(3 * k)[2:]
  315. k = bin(k)[2:]
  316. k = '0' * (len(h) - len(k)) + k
  317. Q = P
  318. minusP = self.minus(P)
  319. for i in range(1, len(h) - 1):
  320. Q = self.Jacb_add(Q)
  321. if h[i] == '1' and k[i] == '0':
  322. Q = self.Jacb_add(Q, P)
  323. elif h[i] == '0' and k[i] == '1':
  324. Q = self.Jacb_add(Q, minusP)
  325. # 算法三:滑动窗法
  326. # 当k为255/256位时,通过test_r函数测试,r=5复杂度最低
  327. elif algo == 3:
  328. k = bin(k)[2:]
  329. l = len(k)
  330. if r >= l: # 如果窗口大于k的二进制位数,则本算法无意义
  331. return self.Jacb_multiply(int(k, 2), P, conv, 2)
  332. # 保存P[j]值的字典
  333. P_ = {1: P, 2: self.Jacb_add(P)}
  334. for i in range(1, 1 << (r - 1)):
  335. P_[(i << 1) + 1] = self.Jacb_add(P_[(i << 1) - 1], P_[2])
  336. t = r
  337. while k[t - 1] != '1':
  338. t -= 1
  339. hj = int(k[:t], 2)
  340. Q = P_[hj]
  341. j = t
  342. while j < l:
  343. if k[j] == '0':
  344. Q = self.Jacb_add(Q)
  345. j += 1
  346. else:
  347. t = min(r, l - j)
  348. while k[j + t - 1] != '1':
  349. t -= 1
  350. hj = int(k[j:j + t], 2)
  351. Q = self.Jacb_add(self.Jacb_multiply(1 << t, Q, False, 2), P_[hj])
  352. j += t
  353. return self.Jacb_to_affine(Q) if conv else Q
  354. # Jacobian加重射影坐标转仿射坐标
  355. # SM2第1部分 A.1.2.3.2
  356. def Jacb_to_affine(self, P):
  357. if len(P) == 2: # 已经是仿射坐标
  358. return P
  359. x, y, z = P
  360. # 处理无穷远点
  361. if z == 0:
  362. return self.O
  363. z_ = get_inverse(z, self.p) # z的乘法逆元
  364. x2 = x * z_ * z_ % self.p
  365. y2 = y * z_ * z_ * z_ % self.p
  366. return x2, y2
  367. # 判断是否为无穷远点(零点)
  368. def is_zero(self, P):
  369. if len(P) == 2: # 仿射坐标
  370. return P == self.O
  371. else: # Jacobian加重射影坐标
  372. return P[2] == 0
  373. # 判断是否为域Fp中的元素
  374. # 可输入多个元素,全符合才返回True
  375. def on_Fp(self, *x):
  376. for i in x:
  377. if 0 <= i < self.p:
  378. pass
  379. else:
  380. return False
  381. return True
  382. # 判断是否在椭圆曲线上
  383. def on_curve(self, P):
  384. if self.is_zero(P):
  385. return False
  386. if len(P) == 2: # 仿射坐标
  387. x, y = P
  388. return y * y % self.p == (x * x * x + self.a * x + self.b) % self.p
  389. else: # Jacobian加重射影坐标
  390. x, y, z = P
  391. return y * y % self.p == (x * x * x + self.a * x * pow(z, 4, self.p) + self.b * pow(z, 6, self.p)) % self.p
  392. # 生成密钥对
  393. # 返回值:d为私钥,P为公钥
  394. # SM2第1部分 6.1
  395. def gen_keypair(self):
  396. d = random.randint(1, self.n - 2)
  397. P = self.Jacb_multiply(d, self.G)
  398. return d, P
  399. # 公钥验证
  400. # SM2第1部分 6.2.1
  401. def pk_valid(self, P):
  402. # 判断点P的格式
  403. if P and len(P) == 2 and type(P[0]) == type(P[1]) == int:
  404. pass
  405. else:
  406. self.error = '格式有误' # 记录错误信息
  407. return False
  408. # a) 验证P不是无穷远点O
  409. if self.is_zero(P):
  410. self.error = '无穷远点'
  411. return False
  412. # b) 验证公钥P的坐标xP和yP是域Fp中的元素
  413. if not self.on_Fp(*P):
  414. self.error = '坐标值不是域Fp中的元素'
  415. return False
  416. # c) 验证y^2 = x^3 + ax + b (mod p)
  417. if not self.on_curve(P):
  418. self.error = '不在椭圆曲线上'
  419. return False
  420. # d) 验证[n]P = O
  421. if not self.is_zero(self.Jacb_multiply(self.n, P, False)):
  422. self.error = '[n]P不是无穷远点'
  423. return False
  424. return True
  425. # 确认目前已有公私钥对
  426. def confirm_keypair(self):
  427. if not hasattr(self, 'pk') or not self.pk_valid(self.pk) or self.pk != self.Jacb_multiply(self.sk, self.G):
  428. # 目前没有合格的公私钥对则生成
  429. while True:
  430. d, P = self.gen_keypair()
  431. if self.pk_valid(P): # 确保公钥通过验证
  432. self.sk, self.pk = d, P
  433. return
  434. # 国家密码管理局:SM2椭圆曲线公钥密码算法推荐曲线参数
  435. SM2_p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
  436. SM2_a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
  437. SM2_b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
  438. SM2_n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
  439. SM2_Gx = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
  440. SM2_Gy = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
  441. PARA_SIZE = 32 # 参数长度(字节)
  442. HASH_SIZE = 32 # sm3输出256位(32字节)
  443. KEY_LEN = 128 # 默认密钥位数
  444. # SM2类继承ECC
  445. class SM2(ECC):
  446. # 默认使用SM2推荐曲线参数
  447. def __init__(self, p=SM2_p, a=SM2_a, b=SM2_b, n=SM2_n, G=(SM2_Gx, SM2_Gy), h=None,
  448. ID=None, sk=None, pk=None, genkeypair=True): # genkeypair表示是否自动生成公私钥对
  449. if not h: # 余因子h默认为1
  450. h = 1
  451. ECC.__init__(self, p, a, b, n, G, h)
  452. self.keysize = len(to_byte(n)) # 密钥长度(字节)
  453. if type(ID) in (int, str): # 身份ID(数字或字符串)
  454. self.ID = ID
  455. else:
  456. self.ID = ''
  457. if sk and pk: # 如果提供的公私钥对通过验证,即使genkeypair=True也不会重新生成
  458. self.sk = sk # 私钥(int [1,n-2])
  459. self.pk = pk # 公钥(x, y)
  460. self.confirm_keypair() # 验证该公私钥对,不合格则生成
  461. elif genkeypair: # 自动生成合格的公私钥对
  462. self.confirm_keypair()
  463. # 预先计算用到的常数
  464. if hasattr(self, 'sk'): # 签名时
  465. self.d_1 = get_inverse(1 + self.sk, self.n)
  466. # 椭圆曲线系统参数验证
  467. # SM2第1部分 5.2.2
  468. def para_valid(self):
  469. # a) 验证q = p是奇素数
  470. if not is_prime(self.p):
  471. self.error = 'p不是素数' # 记录错误信息
  472. return False
  473. # b) 验证a、b、Gx和Gy是区间[0, p−1]中的整数
  474. if not self.on_Fp(self.a, self.b, *self.G):
  475. self.error = 'a、b或G坐标值不是域Fp中的元素'
  476. return False
  477. # d) 验证(4a^3 + 27b^2) mod p != 0
  478. if (4 * self.a * self.a * self.a + 27 * self.b * self.b) % self.p == 0:
  479. self.error = '(4a^3 + 27b^2) mod p = 0'
  480. return False
  481. # e) 验证Gy^2 = Gx^3 + aGx + b (mod p)
  482. if not self.on_curve(self.G):
  483. self.error = 'G不在椭圆曲线上'
  484. return False
  485. # f) 验证n是素数,n > 2^191 且 n > 4p^1/2
  486. if not is_prime(self.n) or self.n <= 1 << 191 or self.n <= 4 * self.p ** 0.5:
  487. self.error = 'n不是素数或n不够大'
  488. return False
  489. # g) 验证[n]G = O
  490. if not self.is_zero(self.Jacb_multiply(self.n, self.G, False)):
  491. self.error = '[n]G不是无穷远点'
  492. return False
  493. # i) 验证抗MOV攻击条件和抗异常曲线攻击条件成立(A.4.2.1)
  494. B = 27 # MOV阈B
  495. t = 1
  496. for i in range(B):
  497. t = t * self.p % self.n
  498. if t == 1:
  499. self.error = '不满足抗MOV攻击条件'
  500. return False
  501. # 椭圆曲线的阶N=#E(Fp)计算太复杂,未实现A.4.2.2验证
  502. # Fp上的绝大多数椭圆曲线确实满足抗异常曲线攻击条件
  503. return True
  504. # 计算Z
  505. # SM2第2部分 5.5
  506. # ID为数字或字符串,P为公钥(不提供参数时返回自身Z值)
  507. def get_Z(self, ID=None, P=None):
  508. save = False
  509. if not P: # 不提供参数
  510. if hasattr(self, 'Z'): # 再次计算,返回曾计算好的自身Z值
  511. return self.Z
  512. else: # 首次计算自身Z值
  513. ID = self.ID
  514. P = self.pk
  515. save = True
  516. entlen = get_bit_num(ID)
  517. ENTL = to_byte(entlen, 2)
  518. Z = sm3(join_bytes([ENTL, ID, self.a, self.b, *self.G, *P]))
  519. if save: # 保存自身Z值
  520. self.Z = Z
  521. return Z
  522. # 数字签名
  523. # SM2第2部分 6.1
  524. # 输入:待签名的消息M、随机数k(不填则自动生成)、输出类型(默认bytes)、对M是否hash(默认是)
  525. # 输出:r, s(int类型)或拼接后的bytes
  526. def sign(self, M, k=None, outbytes=True, dohash=True):
  527. if dohash:
  528. M_ = join_bytes([self.get_Z(), M])
  529. e = to_int(sm3(M_))
  530. else:
  531. e = to_int(to_byte(M))
  532. while True:
  533. if not k:
  534. k = random.randint(1, self.n - 1)
  535. # x1, y1 = self.multiply(k, self.G)
  536. x1, y1 = self.Jacb_multiply(k, self.G)
  537. r = (e + x1) % self.n
  538. if r == 0 or r + k == self.n:
  539. k = 0
  540. continue
  541. # s = get_inverse(1 + self.sk, self.n) * (k - r * self.sk) % self.n
  542. s = self.d_1 * (k - r * self.sk) % self.n
  543. if s == 0:
  544. k = 0
  545. else:
  546. break
  547. if outbytes:
  548. return to_byte((r, s), self.keysize)
  549. else:
  550. return r, s
  551. # 数字签名验证
  552. # SM2第2部分 7.1
  553. # 输入:收到的消息M′及其数字签名(r′, s′)、签名者的身份标识IDA及公钥PA、对M是否hash(默认是)
  554. # 输出:True or False
  555. def verify(self, M, sig, IDA, PA, dohash=True):
  556. if isinstance(sig, bytes):
  557. r = to_int(sig[:self.keysize])
  558. s = to_int(sig[self.keysize:])
  559. else:
  560. r, s = sig
  561. if not 1 <= r <= self.n - 1:
  562. return False
  563. if not 1 <= s <= self.n - 1:
  564. return False
  565. if dohash:
  566. M_ = join_bytes([self.get_Z(IDA, PA), M])
  567. e = to_int(sm3(M_))
  568. else:
  569. e = to_int(to_byte(M))
  570. t = (r + s) % self.n
  571. if t == 0:
  572. return False
  573. sG = self.Jacb_multiply(s, self.G, False)
  574. tPA = self.Jacb_multiply(t, PA, False)
  575. x1, y1 = self.Jacb_to_affine(self.Jacb_add(sG, tPA))
  576. R = (e + x1) % self.n
  577. if R == r:
  578. return True
  579. else: # 避免Jacobian坐标下的等价点导致判断失败
  580. x1, y1 = self.add(self.Jacb_to_affine(sG), self.Jacb_to_affine(tPA))
  581. R = (e + x1) % self.n
  582. return R == r
  583. # A 发起协商
  584. # SM2第3部分 6.1 A1-A3
  585. # 返回rA、RA
  586. def agreement_initiate(self):
  587. return self.gen_keypair()
  588. # B 响应协商(option=True时计算选项部分)
  589. # SM2第3部分 6.1 B1-B9
  590. def agreement_response(self, RA, PA, IDA, option=False, rB=None, RB=None, klen=None):
  591. # 参数准备
  592. if not self.on_curve(RA):
  593. return False, 'RA不在椭圆曲线上'
  594. x1, y1 = RA
  595. w = math.ceil(math.ceil(math.log(self.n, 2)) / 2) - 1
  596. if not hasattr(self, 'sk'):
  597. self.confirm_keypair()
  598. h = 1 # SM2推荐曲线的余因子h=1
  599. ZA = self.get_Z(IDA, PA)
  600. ZB = self.get_Z()
  601. # B1-B7
  602. if not rB:
  603. rB, RB = self.gen_keypair()
  604. x2, y2 = RB
  605. x_2 = (1 << w) + (x2 & (1 << w) - 1)
  606. tB = (self.sk + x_2 * rB) % self.n
  607. x_1 = (1 << w) + (x1 & (1 << w) - 1)
  608. # V = self.multiply(h * tB, self.add(PA, self.multiply(x_1, RA)))
  609. V = self.Jacb_multiply(h * tB, self.Jacb_add(self.Jacb_multiply(x_1, RA, False), PA))
  610. if self.is_zero(V):
  611. return False, 'V是无穷远点'
  612. xV, yV = V
  613. if not klen:
  614. klen = KEY_LEN
  615. KB = KDF(join_bytes([xV, yV, ZA, ZB]), klen)
  616. if not option:
  617. return True, (RB, KB)
  618. # B8、B10(可选部分)
  619. tmp = join_bytes([yV, sm3(join_bytes([xV, ZA, ZB, x1, y1, x2, y2]))])
  620. SB = sm3(join_bytes([2, tmp]))
  621. S2 = sm3(join_bytes([3, tmp]))
  622. return True, (RB, KB, SB, S2)
  623. # A 协商确认
  624. # SM2第3部分 6.1 A4-A10
  625. def agreement_confirm(self, rA, RA, RB, PB, IDB, SB=None, option=False, klen=None):
  626. # 参数准备
  627. if not self.on_curve(RB):
  628. return False, 'RB不在椭圆曲线上'
  629. x1, y1, x2, y2 = *RA, *RB
  630. w = math.ceil(math.ceil(math.log(self.n, 2)) / 2) - 1
  631. if not hasattr(self, 'sk'):
  632. self.confirm_keypair()
  633. h = 1 # SM2推荐曲线的余因子h=1
  634. ZA = self.get_Z()
  635. ZB = self.get_Z(IDB, PB)
  636. # A4-A8
  637. x_1 = (1 << w) + (x1 & (1 << w) - 1)
  638. tA = (self.sk + x_1 * rA) % self.n
  639. x_2 = (1 << w) + (x2 & (1 << w) - 1)
  640. # U = self.multiply(h * tA, self.add(PB, self.multiply(x_2, RB)))
  641. U = self.Jacb_multiply(h * tA, self.Jacb_add(self.Jacb_multiply(x_2, RB, False), PB))
  642. if self.is_zero(U):
  643. return False, 'U是无穷远点'
  644. xU, yU = U
  645. if not klen:
  646. klen = KEY_LEN
  647. KA = KDF(join_bytes([xU, yU, ZA, ZB]), klen)
  648. if not option or not SB:
  649. return True, KA
  650. # A9-A10(可选部分)
  651. tmp = join_bytes([yU, sm3(join_bytes([xU, ZA, ZB, x1, y1, x2, y2]))])
  652. S1 = sm3(join_bytes([2, tmp]))
  653. if S1 != SB:
  654. return False, 'S1 != SB'
  655. SA = sm3(join_bytes([3, tmp]))
  656. return True, (KA, SA)
  657. # B 协商确认(可选部分)
  658. # SM2第3部分 6.1 B10
  659. def agreement_confirm2(self, S2, SA):
  660. if S2 != SA:
  661. return False, 'S2 != SA'
  662. return True, ''
  663. # 加密
  664. # SM2第4部分 6.1
  665. # 输入:待加密的消息M(bytes或str类型)、对方的公钥PB、随机数k(不填则自动生成)
  666. # 输出(True, bytes类型密文)或(False, 错误信息)
  667. def encrypt(self, M, PB, k=None):
  668. if self.is_zero(self.multiply(self.h, PB)): # S
  669. return False, 'S是无穷远点'
  670. M = to_byte(M)
  671. klen = get_bit_num(M)
  672. while True:
  673. if not k:
  674. k = random.randint(1, self.n - 1)
  675. # x2, y2 = self.multiply(k, PB)
  676. x2, y2 = self.Jacb_multiply(k, PB)
  677. t = to_int(KDF(join_bytes([x2, y2]), klen))
  678. if t == 0: # 若t为全0比特串则继续循环
  679. k = 0
  680. else:
  681. break
  682. # C1 = to_byte(self.multiply(k, self.G), self.keysize) # (x1, y1)
  683. C1 = to_byte(self.Jacb_multiply(k, self.G), self.keysize) # (x1, y1)
  684. C2 = to_byte(to_int(M) ^ t, klen >> 3)
  685. C3 = sm3(join_bytes([x2, M, y2]))
  686. return True, join_bytes([C1, C2, C3])
  687. # 解密
  688. # SM2第4部分 7.1
  689. # 输入:密文C(bytes类型)
  690. # 输出(True, bytes类型明文)或(False, 错误信息)
  691. def decrypt(self, C):
  692. x1 = to_int(C[:self.keysize])
  693. y1 = to_int(C[self.keysize:self.keysize << 1])
  694. C1 = (x1, y1)
  695. if not self.on_curve(C1):
  696. return False, 'C1不满足椭圆曲线方程'
  697. if self.is_zero(self.multiply(self.h, C1)): # S
  698. return False, 'S是无穷远点'
  699. # x2, y2 = self.multiply(self.sk, C1)
  700. x2, y2 = self.Jacb_multiply(self.sk, C1)
  701. klen = len(C) - (self.keysize << 1) - HASH_SIZE << 3
  702. t = to_int(KDF(join_bytes([x2, y2]), klen))
  703. if t == 0:
  704. return False, 't为全0比特串'
  705. C2 = C[self.keysize << 1:-HASH_SIZE]
  706. M = to_byte(to_int(C2) ^ t, klen >> 3)
  707. u = sm3(join_bytes([x2, M, y2]))
  708. C3 = C[-HASH_SIZE:]
  709. if u != C3:
  710. return False, 'u != C3'
  711. return True, M
  712. # 最简单的ECDH正确性测试
  713. def test_ECDH(verify=False):
  714. time_1 = get_cpu_time()
  715. sm2 = SM2(genkeypair=False)
  716. # A、B双方生成公、私钥
  717. dA, PA = sm2.gen_keypair()
  718. dB, PB = sm2.gen_keypair()
  719. # 验证ECC系统参数和公钥
  720. if verify:
  721. if not sm2.para_valid():
  722. print('椭圆曲线系统参数未通过验证:%s' % sm2.error)
  723. return
  724. if not sm2.pk_valid(PA):
  725. print('PA未通过验证:%s' % sm2.error)
  726. return
  727. if not sm2.pk_valid(PB):
  728. print('PB未通过验证:%s' % sm2.error)
  729. return
  730. # A将PA传给B,B将PB传给A
  731. # A、B双方计算密钥
  732. QA = sm2.Jacb_multiply(dA, PB)
  733. KA = KDF(to_byte(QA), KEY_LEN)
  734. QB = sm2.Jacb_multiply(dB, PA)
  735. KB = KDF(to_byte(QB), KEY_LEN)
  736. time_2 = get_cpu_time()
  737. print('ECDH密钥协商完毕,耗时%.2f ms' % ((time_2 - time_1) * 1000))
  738. print('KA == KB?: %s, value: 0x%s, len: %d' % (KA == KB, KA.hex(), len(KA) << 3))
  739. # SM2密钥协商测试
  740. def test_SM2_agreement(option=False):
  741. time_1 = get_cpu_time()
  742. # A、B双方初始化
  743. sm2_A = SM2(ID='Alice')
  744. sm2_B = SM2(ID='Bob')
  745. # A、B均掌握对方的公钥和ID
  746. PA, IDA = sm2_A.pk, sm2_A.ID
  747. PB, IDB = sm2_B.pk, sm2_B.ID
  748. # A 发起协商
  749. rA, RA = sm2_A.agreement_initiate()
  750. # A将RA发送给B
  751. # B 响应协商
  752. res, content = sm2_B.agreement_response(RA, PA, IDA, option)
  753. if not res:
  754. print('B报告协商错误:', content)
  755. return
  756. if option:
  757. RB, KB, SB, S2 = content
  758. else:
  759. RB, KB = content
  760. SB = None
  761. # B将RB、(选项SB)发送给A
  762. # A 协商确认
  763. res, content = sm2_A.agreement_confirm(rA, RA, RB, PB, IDB, SB, option)
  764. if not res:
  765. print('A报告协商错误:', content)
  766. return
  767. if option:
  768. KA, SA = content
  769. else:
  770. KA = content
  771. if option:
  772. # A将(选项SA)发送给B
  773. # B 协商确认
  774. res, content = sm2_B.agreement_confirm2(S2, SA)
  775. if not res:
  776. print('B报告协商错误:', content)
  777. return
  778. time_2 = get_cpu_time()
  779. print('SM2密钥协商完毕,耗时%.2f ms' % ((time_2 - time_1) * 1000))
  780. print('KA == KB?: %s, value: 0x%s, len: %d' % (KA == KB, KA.hex(), len(KA) << 3))
  781. # SM2示例中的椭圆曲线系统参数
  782. def demo_para():
  783. p = 0x8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3
  784. a = 0x787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498
  785. b = 0x63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A
  786. xG = 0x421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D
  787. yG = 0x0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2
  788. n = 0x8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7
  789. G = (xG, yG)
  790. h = 1
  791. return p, a, b, n, G, h
  792. # SM2数字签名与验证测试
  793. # SM2第2部分 A.1 A.2
  794. def test_signature():
  795. IDA = 'ALICE123@YAHOO.COM'
  796. M = 'message digest'
  797. dA = 0x128B2FA8BD433C6C068C8D803DFF79792A519A55171B1B650C23661D15897263
  798. xA = 0x0AE4C7798AA0F119471BEE11825BE46202BB79E2A5844495E97C04FF4DF2548A
  799. yA = 0x7C0240F88F1CD4E16352A73C17B7F16F07353E53A176D684A9FE0C6BB798E857
  800. PA = (xA, yA)
  801. k = 0x6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F
  802. # A、B双方初始化
  803. sm2_A = SM2(*demo_para(), IDA, dA, PA)
  804. sm2_B = SM2(*demo_para())
  805. time_1 = get_cpu_time()
  806. # A对消息M进行签名
  807. sig = sm2_A.sign(M, k)
  808. # A将消息M签名(r, s)发送给B
  809. # B对消息M签名进行验证
  810. res = sm2_B.verify(M, sig, IDA, PA)
  811. time_2 = get_cpu_time()
  812. print('SM2签名、验证完毕,耗时%.2f ms' % ((time_2 - time_1) * 1000))
  813. print('结果:%s,R值:%s' % (res, sig[:sm2_A.keysize].hex()))
  814. # 验证通过,输出的r值(40f1ec59f793d9f49e09dcef49130d4194f79fb1eed2caa55bacdb49c4e755d1)与SM2第2部分 A.2中的结果一致
  815. # SM2密钥协商测试2
  816. # SM2第3部分 A.1 A.2
  817. def test_SM2_agreement2(option=False):
  818. IDA = 'ALICE123@YAHOO.COM'
  819. IDB = 'BILL456@YAHOO.COM'
  820. dA = 0x6FCBA2EF9AE0AB902BC3BDE3FF915D44BA4CC78F88E2F8E7F8996D3B8CCEEDEE
  821. xA = 0x3099093BF3C137D8FCBBCDF4A2AE50F3B0F216C3122D79425FE03A45DBFE1655
  822. yA = 0x3DF79E8DAC1CF0ECBAA2F2B49D51A4B387F2EFAF482339086A27A8E05BAED98B
  823. PA = (xA, yA)
  824. dB = 0x5E35D7D3F3C54DBAC72E61819E730B019A84208CA3A35E4C2E353DFCCB2A3B53
  825. xB = 0x245493D446C38D8CC0F118374690E7DF633A8A4BFB3329B5ECE604B2B4F37F43
  826. yB = 0x53C0869F4B9E17773DE68FEC45E14904E0DEA45BF6CECF9918C85EA047C60A4C
  827. PB = (xB, yB)
  828. rA = 0x83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563
  829. x1 = 0x6CB5633816F4DD560B1DEC458310CBCC6856C09505324A6D23150C408F162BF0
  830. y1 = 0x0D6FCF62F1036C0A1B6DACCF57399223A65F7D7BF2D9637E5BBBEB857961BF1A
  831. RA = (x1, y1)
  832. rB = 0x33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80
  833. x2 = 0x1799B2A2C778295300D9A2325C686129B8F2B5337B3DCF4514E8BBC19D900EE5
  834. y2 = 0x54C9288C82733EFDF7808AE7F27D0E732F7C73A7D9AC98B7D8740A91D0DB3CF4
  835. RB = (x2, y2)
  836. time_1 = get_cpu_time()
  837. # A、B双方初始化
  838. sm2_A = SM2(*demo_para(), IDA, dA, PA)
  839. sm2_B = SM2(*demo_para(), IDB, dB, PB)
  840. # A 发起协商
  841. # A生成rA, RA,将RA发送给B
  842. # B 响应协商
  843. res, content = sm2_B.agreement_response(RA, PA, IDA, option, rB, RB)
  844. if not res:
  845. print('B报告协商错误:', content)
  846. return
  847. if option:
  848. RB, KB, SB, S2 = content
  849. else:
  850. RB, KB = content
  851. SB = None
  852. # B将RB、(选项SB)发送给A
  853. # A 协商确认
  854. res, content = sm2_A.agreement_confirm(rA, RA, RB, PB, IDB, SB, option)
  855. if not res:
  856. print('A报告协商错误:', content)
  857. return
  858. if option:
  859. KA, SA = content
  860. else:
  861. KA = content
  862. if option:
  863. # A将(选项SA)发送给B
  864. # B 协商确认
  865. res, content = sm2_B.agreement_confirm2(S2, SA)
  866. if not res:
  867. print('B报告协商错误:', content)
  868. return
  869. time_2 = get_cpu_time()
  870. print('SM2密钥协商完毕,耗时%.2f ms' % ((time_2 - time_1) * 1000))
  871. print('KA == KB?: %s, value: 0x%s, len: %d' % (KA == KB, KA.hex(), len(KA) << 3))
  872. # 协商成功,输出的密钥(55b0ac62a6b927ba23703832c853ded4)与SM2第3部分 A.2中的结果一致
  873. # SM2加解密测试
  874. # SM2第4部分 A.1 A.2
  875. def test_encryption():
  876. M = 'encryption standard'
  877. dB = 0x1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0
  878. xB = 0x435B39CCA8F3B508C1488AFC67BE491A0F7BA07E581A0E4849A5CF70628A7E0A
  879. yB = 0x75DDBA78F15FEECB4C7895E2C1CDF5FE01DEBB2CDBADF45399CCF77BBA076A42
  880. PB = (xB, yB)
  881. k = 0x4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F
  882. # A、B双方初始化
  883. sm2_A = SM2(*demo_para())
  884. sm2_B = SM2(*demo_para(), '', dB, PB)
  885. time_1 = get_cpu_time()
  886. # A用B的公钥对消息M进行加密
  887. res, C = sm2_A.encrypt(M, PB, k)
  888. if not res:
  889. print('A报告加密错误:', C)
  890. return
  891. # A将密文C发送给B
  892. # B用自己的私钥对密文C进行解密
  893. res, M2 = sm2_B.decrypt(C)
  894. if not res:
  895. print('B报告解密错误:', M2)
  896. return
  897. time_2 = get_cpu_time()
  898. print('SM2加解密完毕,耗时%.2f ms' % ((time_2 - time_1) * 1000))
  899. print('结果:%s,解密得:%s(%s)' % (res, M2.hex(), M2.decode()))
  900. # 加解密成功,解密后的16进制值(656e6372797074696f6e207374616e64617264)与SM2第4部分 A.2中的结果一致
  901. if __name__ == "__main__":
  902. test_ECDH()
  903. test_SM2_agreement(True)
  904. # 可复现SM2文档中的示例结果
  905. test_signature()
  906. test_SM2_agreement2(True)
  907. test_encryption()

“他们要打多久,就打多久,一直打到完全胜利!”

多用SM2,少用RSA,不用DH;多用SM3,少用SHA,不用MD5;多用SM4,少用AES,不用DES。支持国密,支持自主,不光是情怀,而是国密算法确实设计得好,易用,安全性高!

目前,对于国密算法的python实现,在代码开源、算法优化、稳定性方面还不及国外的成熟库,幸而我们的国家一直不乏有识之士踔厉奋发、笃行不怠、负重前行。

望大家共同努力,把网络信息安全牢牢掌握在自己手中,共勉!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/50057
推荐阅读
相关标签
  

闽ICP备14008679号