深入解析:【应用密码学】实验八 数字签名——SM2
一、实验要求与目的
1.编程练习:将公钥密码实验的RSA加密改为RSA签名,在公钥密码试验基础上,实现DSS和SM2。
2.会用现成的签名算法进行数字签名和验签操作。此处用SM2。
二、实验内容与步骤记录(只记录关键步骤与结果,可截图,但注意排版与图片大小)
本实验基于国密SM2椭圆曲线密码算法,分别实现了加密与解密功能以及数字签名与验证功能,借助自编程的SM3哈希函数和有限域椭圆曲线上的主要运算操作,完整再现了SM2的基本过程。
在密码学中,普通加密和数字签名虽然同样基于公钥密码体系,但二者关注的安全目标不同。加密的核心是保障消息的机密性,防止信息被窃取,通常使用接收方的公钥对明文进行加密,密文只有持有对应私钥的接收方才能解密。相反,数字签名的目的是验证消息的真实性和完整性,防止数据被篡改或伪造,过程是发送方利用自己的私钥对消息生成签名,接收方借助发送方的公钥进行签名验证。简而言之,普通加密关注“谁能读懂”信息,而数字签名关注“这是谁发的、内容是否被篡改”。
在原有实验代码中,完成的是SM2的加密流程。先随机生成一个整数k,计算椭圆曲线上的点C1=kG;然后根据接收方公钥P计算共享点kP并提取其坐标,结合KDF(密钥派生函数)生成密钥流t。启用t对明文进行异或运行生成密文数据C2,同时利用SM3哈希计算校验值C3,确保数据在传输过程中的完整性。加密结果最终以C1、C3和C2的组合形式输出。整个加密过程保证了消息的隐私性,且通过哈希校验防止密文篡改。
而在本次修改后的实验中,基于SM2实现了数字签名与验证作用。签名过程中,首先对待签名消息进行SM3哈希处理,生成消息摘要e。之后选取随机数k,计算kG点的横坐标x1,并由此生成签名分量r=(e+x1) mod n,其中n为基点G的阶。接下来通过发送方私钥d计算s=((1+d)^(-1) * (k - r*d)) mod n,最终输出签名对(r, s)。在验证过程中,接收方再次对消息哈希得到e,计算t=(r+s) mod n,并根据公钥P计算sG+tP对应的椭圆曲线点,再提取横坐标x1',结合e计算R=(e+x1') mod n。若最终R与r相等,则说明签名合法,消息未被篡改且来源可信。
相较于加密过程,签名过程不需密钥派生函数KDF,但对随机数k的要求更高,必须保证每次签名采用不同的k,以避免私钥泄露。此外,签名的生成与验证均基于椭圆曲线点的加法与标量乘法运算,底层依然依赖有限域上的数论运算。值得注意的是,签名算法中哈希操作是在消息发送前进行的,确保签名对象具有固定长度,而加密算法中的哈希是用于校验密文的完整性。
整体来看,原有加密代码关键是保护信息的机密性,在消息传递过程中防止内容泄露;而新增的签名代码则侧重于保护消息的真实性和完整性,使接收方能够确认消息确实来自于发送方且未被篡改。两者虽然同属于公钥密码体系,但使用的密钥、运算步骤以及最终实现的安全目标存在明显区别。经过本实验,能够更加直观地理解SM2在不同安全需求下的应用方式,并体会椭圆曲线密码体制在安全性与运算效率方面的优势。

三、源代码记录(关键代码需备注)
import structimport random # ---------------- SM3 哈希函数 ---------------- #IV = [ 0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E]T_j = [0x79CC4519] * 16 + [0x7A879D8A] * 48 def _left_rotate(x, n): n = n % 32 return ((x > (32 - n))) & 0xFFFFFFFF def _FF_j(x, y, z, j): return x ^ y ^ z if j bytes: l = len(message) * 8 message += b'\x80' while (len(message) * 8 + 64) % 512 != 0: message += b'\x00' message += struct.pack('>Q', l) return message def _message_extension(B: bytes): W = [int.from_bytes(B[i:i+4], 'big') for i in range(0, 64, 4)] for i in range(16, 68): W.append(_P1(W[i-16] ^ W[i-9] ^ _left_rotate(W[i-3], 15)) ^ _left_rotate(W[i-13], 7) ^ W[i-6]) W_ = [W[i] ^ W[i+4] for i in range(64)] return W, W_ def _CF(V_i, B_i): A, B, C, D, E, F, G, H = V_i W, W_ = _message_extension(B_i) for j in range(64): SS1 = _left_rotate((_left_rotate(A, 12) + E + _left_rotate(T_j[j], j)) & 0xFFFFFFFF, 7) SS2 = SS1 ^ _left_rotate(A, 12) TT1 = (_FF_j(A, B, C, j) + D + SS2 + W_[j]) & 0xFFFFFFFF TT2 = (_GG_j(E, F, G, j) + H + SS1 + W[j]) & 0xFFFFFFFF D = C C = _left_rotate(B, 9) B = A A = TT1 H = G G = _left_rotate(F, 19) F = E E = _P0(TT2) return [ A ^ V_i[0], B ^ V_i[1], C ^ V_i[2], D ^ V_i[3], E ^ V_i[4], F ^ V_i[5], G ^ V_i[6], H ^ V_i[7] ] def sm3_hash(msg: bytes) -> bytes: msg = _padding(msg) V = IV.copy() for i in range(0, len(msg), 64): B_i = msg[i:i+64] V = _CF(V, B_i) return b''.join(v.to_bytes(4, 'big') for v in V) # ---------------- 椭圆曲线操作 ---------------- #def point_addition(p, a, P, Q): if P == (0, 0): return Q if Q == (0, 0): return P if P == Q: if P[1] == 0: return (0, 0) numerator = (3 * P[0]**2 + a) % p denominator = (2 * P[1]) % p else: if P[0] == Q[0] and (P[1] + Q[1]) % p == 0: return (0, 0) numerator = (Q[1] - P[1]) % p denominator = (Q[0] - P[0]) % p lam = (numerator * pow(denominator, p - 2, p)) % p x3 = (lam**2 - P[0] - Q[0]) % p y3 = (lam * (P[0] - x3) - P[1]) % p return (x3, y3) def point_multiplication(p, a, P, k): result = (0, 0) temp = P while k > 0: if k & 1: result = point_addition(p, a, result, temp) temp = point_addition(p, a, temp, temp) k >>= 1 return result def point_order(p, a, G): Q = G for i in range(1, 2*p): if Q == (0, 0): return i Q = point_addition(p, a, Q, G) return None def findsolution(p, a, b): """找到椭圆曲线 y^2 = x^3 + ax + b 在有限域 F_p 上的所有点""" s = [] cnt = 0 for i in range(p): z = (i**3 + a * i + b) % p if pow(z, (p - 1) // 2, p) == 1: y1 = pow(z, (p + 1) // 4, p) s.append((i, y1)) cnt += 1 y2 = p - y1 if y1 != y2: s.append((i, y2)) cnt += 1 s.append((0, 0)) # 添加无穷远点 cnt += 1 s.sort() return s, cnt # ---------------- SM2 签名与验证 ---------------- #def bytes_to_int(b: bytes) -> int: return int.from_bytes(b, 'big') def sm2_sign(p, a, G, n, d, message: str, k: int): M = message.encode('utf-8') e = bytes_to_int(sm3_hash(M)) % n kG = point_multiplication(p, a, G, k) x1, _ = kG r = (e + x1) % n if r == 0 or r + k == n: raise ValueError("Invalid r value, please choose another k") d_inv = pow(1 + d, -1, n) s = (d_inv * (k - r * d)) % n if s == 0: raise ValueError("Invalid s value, please choose another k") return (r, s) def sm2_verify(p, a, G, n, public_key, message: str, signature): r, s = signature M = message.encode('utf-8') e = bytes_to_int(sm3_hash(M)) % n if not (1 <= r <= n - 1) or not (1 <= s <= n - 1): return False t = (r + s) % n if t == 0: return False sG = point_multiplication(p, a, G, s) tP = point_multiplication(p, a, public_key, t) point = point_addition(p, a, sG, tP) x1, _ = point R = (e + x1) % n return R == r # ---------------- 示例 ---------------- #if __name__ == "__main__": p = 751 a = -1 b = 188 points, cnt = findsolution(p, a, b) print(f"在有限域 F_{p} 上的点共有 {cnt} 个") print("前十个点:", points[:10]) G = points[5] # 任选一个点,比如第6个点 n = point_order(p, a, G) # 求阶 print(f"选取的基点 G = {G}") print(f"G 点的阶 n = {n}") d = random.randint(1, n-1) # 私钥 d P = point_multiplication(p, a, G, d) # 公钥 P msg = "Hello SM2 Signature!" k = random.SystemRandom().randint(1, n-1) # 真随机生成 k print("明文:", msg) print("使用随机数 k =", k) print("私钥 d =", d) print("公钥 P =", P) signature = sm2_sign(p, a, G, n, d, msg, k) print("签名 (r, s):", signature) valid = sm2_verify(p, a, G, n, P, msg, signature) print("验证结果:", "通过" if valid else "失败")
四、实验思考
1. SM2加密和SM2签名虽然都基于椭圆曲线运算,为什么在加密中需要KDF函数,而签名中不需要?
答:KDF在加密过程中用于将椭圆曲线点的坐标信息转化为足够长度的密钥流。由于SM2加密采用的是将共享点kP的坐标作为种子,经过KDF生成与明文长度相同的伪随机数流,进而实现对明文的加密。这种做法能实用避免直接应用曲线点坐标的部分信息,提升加密密钥的不可预测性和安全性。相对地,SM2签名过程中,椭圆曲线运算只用于生成签名值的中间量,并不涉及对明文数据的加密处理,因此不应该生成伪随机密钥流。签名的本质是通过消息摘要哈希与曲线运算绑定身份认证关系,整个过程只依赖哈希函数和核心椭圆曲线运算即可完成。因此,KDF不必要的,仅在加密场景中用于生成密钥流,确保加密数据的安全性。就是在签名场景下
浙公网安备 33010602011771号