)
用Python从零实现ECDSA签名与验签数学原理与代码实战含SM2对比椭圆曲线密码学ECC已成为现代加密体系的基石从比特币交易到HTTPS安全连接背后都离不开ECDSA签名算法的支撑。但大多数开发者仅停留在调用API的层面对曲线上的点运算如何转化为数字签名缺乏直观理解。本文将用Python带你从零实现一个简化版的ECDSA系统通过代码揭示数学原理并对比分析国密SM2标准的实现差异。1. 椭圆曲线数学基础的可视化实现椭圆曲线并非真正的椭圆而是由韦尔斯特拉斯方程定义的二元三次曲线。在密码学中我们主要使用素数域上的简化方程y² x³ ax b mod p。理解曲线上的点运算规则是掌握ECDSA的前提。1.1 用Python绘制椭圆曲线我们先实现一个可视化工具直观展示曲线形态import matplotlib.pyplot as plt import numpy as np def plot_elliptic_curve(a, b, p): 绘制素数域上的椭圆曲线 x np.arange(0, p) y_squared (x**3 a*x b) % p y [] for ys in y_squared: roots [i for i in range(p) if (i*i) % p ys] y.extend([(x_val, root) for root in roots for x_val in [x[ys y_squared]]]) x_vals, y_vals zip(*y) if y else ([], []) plt.scatter(x_vals, y_vals, s5) plt.title(fElliptic Curve y² x³ {a}x {b} mod {p}) plt.grid() plt.show() # 示例绘制secp256k1曲线比特币使用 plot_elliptic_curve(a0, b7, p17)运行这段代码你会看到离散点构成的对称图形——这就是密码学中实际使用的椭圆曲线形态。与连续曲线不同有限域上的点呈现离散分布特性。1.2 点加法与倍点的几何解释椭圆曲线上的点构成阿贝尔群其核心运算是点加法。我们通过动画演示来理解其几何意义点加(PQ)连接两点P和Q的直线与曲线相交于第三点R取R关于x轴的对称点即为PQ倍点(2P)当PQ时使用P点的切线确定R用Python实现点加运算def point_add(p, q, a, p_mod): 实现椭圆曲线上的点加运算 if p (0, 0): return q if q (0, 0): return p if p[0] q[0] and p[1] ! q[1]: return (0, 0) # 无穷远点 if p ! q: # 点加运算 lam (q[1] - p[1]) * pow(q[0] - p[0], -1, p_mod) % p_mod else: # 倍点运算 lam (3*p[0]*p[0] a) * pow(2*p[1], -1, p_mod) % p_mod x (lam*lam - p[0] - q[0]) % p_mod y (lam*(p[0] - x) - p[1]) % p_mod return (x, y)注意实际密码系统使用扩展形式存储无穷远点这里用(0,0)简化表示1.3 标量乘法与密钥生成私钥本质上是一个大整数d而公钥则是基点G的d倍点。实现高效的标量乘法对性能至关重要def scalar_mult(k, point, a, p_mod): 通过double-and-add算法实现标量乘法 result (0, 0) # 无穷远点 current point while k 0: if k % 2 1: result point_add(result, current, a, p_mod) current point_add(current, current, a, p_mod) # 倍点 k k // 2 return result通过可视化演示可以直观看到标量乘法如何将私钥标量映射到曲线上的公钥点G (15, 13) # 示例基点 priv_key 6 pub_key scalar_mult(priv_key, G, a0, p_mod17) print(fPublic key: {pub_key}) # 输出: (5, 8)2. ECDSA签名算法的完整实现理解了曲线运算后我们可以着手实现ECDSA签名系统。标准ECDSA包含三个核心步骤密钥生成、签名和验证。2.1 密钥对生成使用secp256k1曲线参数实现密钥生成# secp256k1曲线参数比特币使用 P 2**256 - 2**32 - 977 A, B 0, 7 Gx 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 Gy 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 G (Gx, Gy) N 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # 基点阶数 def generate_keypair(): 生成ECDSA密钥对 private_key secrets.randbelow(N-1) 1 public_key scalar_mult(private_key, G, A, P) return private_key, public_key2.2 签名过程实现ECDSA签名需要将消息哈希与私钥结合计算def ecdsa_sign(private_key, message): ECDSA签名实现 z int.from_bytes(sha256(message).digest(), big) # 消息哈希 k secrets.randbelow(N-1) 1 # 临时密钥 r_point scalar_mult(k, G, A, P) r r_point[0] % N s (pow(k, -1, N) * (z r * private_key)) % N return (r, s)签名过程中的关键点k必须随机重复使用k会导致私钥泄露索尼PS3曾因此被破解哈希处理实际使用中应对消息先进行标准化哈希处理2.3 验签过程实现验证者需要检查签名是否由对应私钥生成def ecdsa_verify(public_key, message, signature): ECDSA验签实现 r, s signature if not (1 r N and 1 s N): return False z int.from_bytes(sha256(message).digest(), big) s_inv pow(s, -1, N) u1 (z * s_inv) % N u2 (r * s_inv) % N point1 scalar_mult(u1, G, A, P) point2 scalar_mult(u2, public_key, A, P) total point_add(point1, point2, A, P) return total[0] % N r % N验签成功的数学原理在于u1*G u2*Q (z/s)*G (r/s)*dG ((z r*d)/s)*G kG3. 国密SM2签名算法的实现与对比SM2是我国自主设计的椭圆曲线密码标准与ECDSA在算法结构和安全性设计上存在显著差异。3.1 SM2签名算法核心区别特性ECDSASM2哈希处理直接哈希消息包含用户ID和公钥的Z值签名方程s k⁻¹(z r·d) mod ns ((1 d)⁻¹)·(k - r·d) mod n安全性设计随机数k敏感添加冗余设计3.2 SM2签名实现关键代码def sm2_sign(private_key, public_key, message, user_idb1234567812345678): SM2签名实现 # 计算Z值 entl len(user_id) * 8 z sha256(bytes([entl 8, entl 0xFF]) user_id bytes.fromhex(f{A:064x}{B:064x}{Gx:064x}{Gy:064x}) bytes.fromhex(f{public_key[0]:064x}{public_key[1]:064x})).digest() m_hat sha256(z message).digest() e int.from_bytes(m_hat, big) while True: k secrets.randbelow(N) x1, _ scalar_mult(k, G, A, P) r (e x1) % N if r 0 or r k N: continue s (pow(1 private_key, -1, N) * (k - r * private_key)) % N if s ! 0: return (r, s)3.3 SM2验签实现def sm2_verify(public_key, message, signature, user_idb1234567812345678): SM2验签实现 r, s signature if not (1 r N and 1 s N): return False # 计算Z值同签名过程 entl len(user_id) * 8 z sha256(bytes([entl 8, entl 0xFF]) user_id bytes.fromhex(f{A:064x}{B:064x}{Gx:064x}{Gy:064x}) bytes.fromhex(f{public_key[0]:064x}{public_key[1]:064x})).digest() m_hat sha256(z message).digest() e int.from_bytes(m_hat, big) t (r s) % N if t 0: return False point1 scalar_mult(s, G, A, P) point2 scalar_mult(t, public_key, A, P) total point_add(point1, point2, A, P) return (e total[0]) % N r4. 性能优化与安全实践在实际应用中我们需要考虑性能和安全的平衡。以下是关键优化技术4.1 预计算加速标量乘法class Point: 优化点表示并支持预计算 def __init__(self, x, y, curve): self.x x self.y y self.curve curve self._precompute None def precompute(self, window_size8): 预计算窗口优化表 self._precompute [None] * (2**window_size) self._precompute[0] (0, 0) # 无穷远点 self._precompute[1] (self.x, self.y) for i in range(2, 2**window_size): self._precompute[i] point_add( self._precompute[i-1], self._precompute[1], self.curve.a, self.curve.p ) def fast_scalar_mult(self, k): 使用预计算表的窗口算法 if not self._precompute: self.precompute() result (0, 0) window_size 8 mask (1 window_size) - 1 while k 0: window k mask if window ! 0: result point_add(result, self._precompute[window], self.curve.a, self.curve.p) k k window_size if k 0: for _ in range(window_size): result point_add(result, result, self.curve.a, self.curve.p) return result4.2 侧信道攻击防护定时攻击防护确保运算时间不随私钥变化随机化标量使用随机掩码保护密钥安全内存管理及时清除敏感数据def secure_scalar_mult(k, point, curve): 抗侧信道攻击的标量乘法 # 添加随机掩码 mask secrets.randbelow(curve.n) k_masked (k mask * curve.n) % (curve.n * 2) # 使用固定时间算法 result (0, 0) dummy (0, 0) current point for i in range(curve.n.bit_length() 1): bit (k_masked i) 1 result point_add(result, current if bit else dummy, curve.a, curve.p) current point_add(current, current, curve.a, curve.p) dummy point_add(dummy, dummy, curve.a, curve.p) return result5. 实际应用中的选择建议在项目中选择ECDSA还是SM2时需要考虑以下因素技术考量兼容性国际项目优先ECDSA国内政务系统需用SM2性能SM2签名速度略慢但验签更快安全性SM2设计更保守抗攻击冗余更高实施建议金融级应用建议使用硬件安全模块(HSM)移动端考虑使用P-256曲线平衡安全与性能长期存储的数据应使用更长的密钥如P-384# 密码学安全比较表 security_levels { RSA-2048: 112, ECDSA-P256: 128, SM2-P256: 128, RSA-3072: 128, ECDSA-P384: 192, SM2-P384: 192 }在区块链项目中比特币使用secp256k1曲线而以太坊2.0已开始支持SM2作为可选方案。实际开发中推荐使用成熟的密码学库如OpenSSL或Bouncy Castle而非自行实现核心算法。