别再手动算参数了!用Python一步步复现SM2协同签名全过程(附完整代码)

别再手动算参数了!用Python一步步复现SM2协同签名全过程(附完整代码) 用Python实战SM2协同签名从数学公式到可运行代码的完整指南在密码学领域SM2作为我国自主研发的椭圆曲线公钥密码算法标准其协同签名机制因能实现多方安全计算而备受关注。但当你翻阅理论文档时是否曾被那些抽象的数学符号和冗长的参数计算步骤劝退本文将带你用Python一步步拆解SM2协同签名的每个环节把晦涩的公式转化为可执行的代码。1. 环境准备与基础工具实现SM2协同签名需要几个核心组件椭圆曲线运算库、大整数处理工具和哈希函数。我们选择gmssl库作为基础它原生支持SM2算法曲线参数。首先安装必要依赖pip install gmssl pycryptodome初始化SM2椭圆曲线参数基于GMT 0003.5-2012标准from gmssl import sm2, func import secrets # 标准SM2曲线参数 SM2_p 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF SM2_a 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC SM2_b 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93 SM2_n 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123 SM2_Gx 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7 SM2_Gy 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0注意实际开发中应使用标准库提供的曲线参数此处仅为演示原理。gmssl内部已内置这些参数。2. 密钥生成与参数交换协同签名的核心在于客户端和服务端各自持有部分私钥通过安全交互完成签名。我们先实现密钥对生成def generate_key_pair(): 生成SM2密钥对 private_key secrets.randbelow(SM2_n) public_key sm2.base_point_mult(private_key) return private_key, public_key # 客户端密钥 d1, P1 generate_key_pair() # 服务端密钥 d2, P2 generate_key_pair()公共公钥P的计算需要双方交换公钥def compute_shared_public_key(P1, P2): 计算协同签名公共公钥 P (d1*d2-1)*G # 等效于 P (P1 * d2) - G 或 (P2 * d1) - G temp_point sm2.point_mult(d1, P2) # P1 d1*G, 所以 P1*d2 d1*d2*G shared_pub sm2.point_sub(temp_point, (SM2_Gx, SM2_Gy)) return shared_pub P compute_shared_public_key(P1, P2)3. 签名过程分步实现3.1 客户端第一阶段计算客户端生成随机数K1并计算相关参数def client_phase1(d1, P2): K1 secrets.randbelow(SM2_n) R1 sm2.base_point_mult(K1) # R1 K1*G R1_ sm2.point_mult(K1, P2) # R1_ K1*P2 return K1, R1, R1_ K1, R1, R1_ client_phase1(d1, P2)服务端收到后需验证R1_ sm2.point_mult(d2, R1)因为R1_ K1*P2 K1*d2*G d2*R1 d2*K1*G K1*d2*G3.2 服务端响应计算服务端生成K2并计算响应参数def server_phase(d2, P1, R1, R1_): # 验证客户端参数 if not sm2.point_equal(R1_, sm2.point_mult(d2, R1)): raise ValueError(Client parameter verification failed) K2 secrets.randbelow(SM2_n) R2_ sm2.base_point_mult(K2) # R2_ K2*G R2 sm2.point_mult(K2, P1) # R2 K2*P1 return K2, R2_, R2 K2, R2_, R2 server_phase(d2, P1, R1, R1_)3.3 客户端签名计算客户端验证服务端参数并计算部分签名def client_sign(m, d1, K1, K2, R2_, R2): # 验证服务端参数 if not sm2.point_equal(R2, sm2.point_mult(d1, R2_)): raise ValueError(Server parameter verification failed) # 计算签名随机数K K (K1 K2 * d1) % SM2_n # 计算r (H(M) x1) mod n x1, _ sm2.base_point_mult(K) e int.from_bytes(func.sm3_hash(m.encode()), big) r (e x1) % SM2_n # 计算s_ (K1 r) / d1 mod n s_ (K1 r) * pow(d1, -1, SM2_n) % SM2_n return r, s_ message plaintext r, s_ client_sign(message, d1, K1, K2, R2_, R2)4. 服务端完成签名服务端收到s_后计算最终签名分量def server_complete_sign(d2, K2, s_, r): # 计算 t (s_ K2) / d2 mod n t (s_ K2) * pow(d2, -1, SM2_n) % SM2_n # 客户端最终计算 s (t - r) mod n s (t - r) % SM2_n return t, s t, s server_complete_sign(d2, K2, s_, r)现在我们已经得到完整签名(r, s)。验证签名的正确性def verify_signature(P, message, signature): sm2_crypt sm2.CryptSM2(private_key, public_keyP) return sm2_crypt.verify(signature, message) signature (r, s) print(Signature valid:, verify_signature(P, message, signature))5. 关键问题与调试技巧在实际实现过程中可能会遇到以下典型问题模逆运算失败SM2签名涉及多个模逆计算如pow(d1, -1, SM2_n)。当d1与n不互质时会抛出异常。解决方案def safe_inv(a, n): try: return pow(a, -1, n) except ValueError: # 极低概率事件可重新生成密钥 raise ValueError(No modular inverse exists, regenerate keys)参数验证失败协同签名过程中有两次关键验证服务端验证R1_ d2*R1客户端验证R2 d1*R2_验证失败通常意味着随机数生成不符合规范参数在传输过程中被篡改密钥对生成逻辑有误签名验证不通过如果最终签名验证失败可按以下步骤排查检查项验证方法可能原因公共公钥P比较P1d2-G与P2d1-G密钥交换错误随机数K检查KK1K2*d1 mod n参数传递错误哈希值e对比标准SM3实现消息编码不一致6. 性能优化实践当需要高频执行协同签名时可考虑以下优化手段预计算加速椭圆曲线点乘是最耗时的操作对固定参数可预先计算# 客户端预计算d1的逆元 d1_inv pow(d1, -1, SM2_n) # 服务端预计算d2的逆元 d2_inv pow(d2, -1, SM2_n)并行计算客户端和服务端的部分计算可并行执行from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor() as executor: # 客户端并行计算R1和R1_ future_R1 executor.submit(sm2.base_point_mult, K1) future_R1_ executor.submit(sm2.point_mult, K1, P2) R1, R1_ future_R1.result(), future_R1_.result()缓存机制对于长期合作的客户端-服务端对可缓存部分中间结果减少重复计算。