SM2 密钥交换协议实战:基于 GM/T 0003.3-2012 的密钥协商与 KDF 派生完整实现
前言
SM2 算法家族包含三个独立部分:数字签名(GM/T 0003.1)、密钥交换(GM/T 0003.3)、公钥加密(GM/T 0003.4)。其中密钥交换协议(Key Exchange Protocol, KEP)是国密体系中少有的专门针对两方密钥协商而设计的协议,不同于 TLS 中将密钥交换嵌入握手的做法。
GM/T 0003.3-2012 定义的协议可以实现:
- 两方无需预先共享秘密即可协商出共享密钥
- 提供基于 ID 的身份认证(双方可互相确认对方身份)
- 通过可选的密钥确认步骤防止中间人攻击
- 支持前向保密(通过临时密钥)
一、协议概述与标准结构
1.1 GM/T 0003.3-2012 的适用范围
该标准适用于 SM2 椭圆曲线公钥密码算法的密钥交换,规定了:
- 密钥交换的基本流程(单轮 vs 认证轮次)
- 用户其他信息(ZA)的计算方法
- 共享密钥的计算公式
- 密钥派生函数(KDF)的使用规范
1.2 协议参与方
协议定义两个参与方:
- 发起方 A:主动发起密钥交换请求的一方
- 响应方 B:接收请求并完成协商的一方
- 长期私钥 $d_A$ / $d_B$(仅本方知道)
- 长期公钥 $P_A = [d_A]G$ / $P_B = [d_B]G$(对方已知)
- 唯一的标识 ID_A / ID_B(长度不超过 65535 比特)
1.3 协议完整流程
发起方 A 响应方 B
| |
|--- 1. 生成临时密钥对 (rA, RAl) |
|--- 2. 发送: RAl, ID_A ------------->|
| |
| 验证 RAl 在曲线上
| 生成临时密钥对 (rB, RB)
| 计算共享点 U
| 计算共享密钥 KB/KAB
| (可选)密钥确认
|<--- 3. 发送: RB, [Mac] ------------|
|
| 验证 RB 在曲线上
| 计算共享点 V
| 计算共享密钥 KA/KAB
| (可选)验证 MacTag协议最终产出:协商密钥 $K$(通过 KDF 从共享点导出),以及可选的密钥确认 MAC 标签。
二、ZA 预处理:用户其他信息
2.1 ZA 的计算公式
ZA 是将用户身份绑定到密钥交换的关键步骤,防止身份替换攻击:
$$ZA = \text{SM3}\left( \text{ENTL}_A \| ID_A \| a \| b \| x_G \| y_G \| x_A \| y_A \right)$$
各参数含义:
- $\text{ENTL}_A$:ID_A 的比特长度,以两个字节大端序表示
- $ID_A$:用户标识(字节串)
- $a, b$:椭圆曲线方程 $y^2 = x^3 + ax + b$ 的系数
- $x_G, y_G$:基点 G 的坐标
- $x_A, y_A$:用户 A 的公钥坐标
2.2 Python 实现
from gmssl import sm2, func
from gmssl.sm2 import default_ecc_table
def compute_za(user_id: bytes, pub_x: str, pub_y: str) -> bytes:
"""
计算 ZA = SM3(ENTL || ID || a || b || xG || yG || xA || yA)
"""
ecc = default_ecc_table
# ENTL: ID 的比特长度,2 字节大端序
entl = len(user_id) * 8
entl_bytes = entl.to_bytes(2, 'big')
# 曲线参数 a, b (32 字节大整数)
a_bytes = int(ecc['a']).to_bytes(32, 'big')
b_bytes = int(ecc['b']).to_bytes(32, 'big')
# 基点坐标 (32 字节)
xg_bytes = int(ecc['g'][0]).to_bytes(32, 'big')
yg_bytes = int(ecc['g'][1]).to_bytes(32, 'big')
# 用户公钥坐标 (32 字节)
xa_bytes = int(pub_x, 16).to_bytes(32, 'big')
ya_bytes = int(pub_y, 16).to_bytes(32, 'big')
# 拼接并计算 SM3
data = entl_bytes + user_id + a_bytes + b_bytes + xg_bytes + yg_bytes + xa_bytes + ya_bytes
# 使用 gmssl 的 SM3 哈希
za = func.sm3_hash(func.bytes_to_list(data))
return bytes.fromhex(za)
# 示例:计算 ZA
user_id_b = b"userB@example.com"
pub_x_b = "72a42c2d2f5b7d116e4f8e1f5a3c6b8d2e4f7a9c1b3d5e7f9a2c4d6e8f0a1b3"
pub_y_b = "5d3f6a9c2e5b8d1f4a7c0e3b6d9f2a5c8e1b4d7f0a3c6e9b2d5f8a1c4e7b0d3"
za_b = compute_za(user_id_b, pub_x_b, pub_y_b)
print(f"ZA = {za_b.hex()}")
print(f"ZA length = {len(za_b)} bytes")2.3 踩坑提醒
坑 1:ENTL 的单位是比特而非字节
GM/T 0003.3 中 ENTL 是 "比特长度",不是字节长度。如果你的 ID 是 16 字节,ENTL 应该是 128(0x0080),而不是 16。这是最常见的实现错误。
坑 2:坐标值必须补零到 32 字节
曲线上的点坐标范围是 $[0, p-1]$,其中 $p$ 是略小于 $2^{256}$ 的素数。某些点的坐标高位可能是 0x00,如果直接 int.to_bytes(32, 'big') 转换会正确补零。但如果你用的是其他库(如 cryptography 的 public_numbers()),返回的整数可能不足 32 字节——必须手动检查并补零。
坑 3:ZA 依赖于完整的公钥坐标
ZA 中同时包含了发送方公钥坐标和曲线基点。如果你只持有 x 坐标(压缩格式),必须先从 x 恢复 y 坐标(取偶数 y),否则 ZA 值会错误。
三、临时密钥生成与协商计算
3.1 发起方 A 的步骤
import random
def generate_temp_keypair():
"""
生成临时密钥对 (r, R = [r]G)
"""
ecc = default_ecc_table
# 随机选择 r,范围 [1, n-1]
r_hex = '%064x' % random.getrandbits(256)
while int(r_hex, 16) >= int(ecc['n'], 16):
r_hex = '%064x' % random.getrandbits(256)
sm2_tmp = sm2.CryptSM2('00' * 64, ecc['g'])
R = sm2_tmp._kg(int(r_hex, 16), ecc['g'])
return r_hex, R # r = 私钥(64字符hex), R = 公钥点坐标
# 发起方 A 生成临时密钥对
rA_hex, RA = generate_temp_keypair()
print(f"rA = {rA_hex}")
print(f"RA = {RA[:60]}...")3.2 响应方 B 的共享密钥计算
def compute_shared_key_responder(
dB_hex: str, # B 的长期私钥
rB_hex: str, # B 的临时私钥
RA: tuple, # A 的临时公钥 (xA, yA)
dA_pub: tuple, # A 的长期公钥 (xA, yA)
ID_A: bytes, # A 的 ID
ID_B: bytes, # B 的 ID
klen: int = 16 # 期望的密钥长度(字节)
) -> bytes:
"""
响应方 B 计算共享密钥 KB
"""
ecc = default_ecc_table
n = int(ecc['n'], 16)
h = int(ecc['h'], 16) # 余因子:SM2 中为 1
# RB = [rB]G (B 的临时公钥,需要发送给 A)
sm2_tmp = sm2.CryptSM2('00' * 64, ecc['g'])
RB = sm2_tmp._kg(int(rB_hex, 16), ecc['g'])
# 从 RA 解出坐标
x1, y1 = RA # RA 的坐标
# x1_bar = 2^w + (x1 & (2^w - 1)),其中 w = ceil(log2(n)/2) - 1
# 对于 SM2 256-bit: w = 128
w = 128
x1_bar = (1 << w) + (x1 & ((1 << w) - 1))
# tB = (dB + x1_bar * rB) mod n
dB = int(dB_hex, 16)
rB = int(rB_hex, 16)
tB = (dB + x1_bar * rB) % n
# 验证 RA 在曲线上(此处省略具体验证代码)
# 从 A 的公钥解出坐标
x2, y2 = dA_pub
# x2_bar = 2^w + (x2 & (2^w - 1))
x2_bar = (1 << w) + (x2 & ((1 << w) - 1))
# U = [h * tB] * (PB + [x2_bar] * G)
# 先计算 PB + [x2_bar] * G
point_PB_plus = point_add(dA_pub, point_mul(x2_bar, (int(ecc['g'][0]), int(ecc['g'][1]))))
# 再计算 [h * tB] * point
point_U = point_mul(h * tB % n, point_PB_plus)
# 提取 U 的坐标
xU, yU = point_U
# KDF 派生密钥
# K = KDF(xU || yU || ZA || ZB, klen)
za = compute_za(ID_A, format(dA_pub[0], '064x'), format(dA_pub[1], '064x'))
zb = compute_za(ID_B, format(dB_pub[0], '064x'), format(dB_pub[1], '064x'))
xU_bytes = xU.to_bytes(32, 'big')
yU_bytes = yU.to_bytes(32, 'big')
kdf_input = xU_bytes + yU_bytes + za + zb
# KDF 实现
key = kdf_sm3(kdf_input, klen)
return key, RB, xU, yU3.3 发起方 A 的共享密钥计算
类似地,发起方 A 计算:
def compute_shared_key_initiator(
dA_hex: str, # A 的长期私钥
rA_hex: str, # A 的临时私钥
RB: tuple, # B 的临时公钥
dB_pub: tuple, # B 的长期公钥
ID_A: bytes,
ID_B: bytes,
klen: int = 16
) -> bytes:
"""
发起方 A 计算共享密钥 KA
"""
ecc = default_ecc_table
n = int(ecc['n'], 16)
h = int(ecc['h'], 16)
x1, y1 = RA # A 的临时公钥坐标
# x1_bar
w = 128
x1_bar = (1 << w) + (x1 & ((1 << w) - 1))
# tA = (dA + x1_bar * rA) mod n
dA = int(dA_hex, 16)
rA = int(rA_hex, 16)
tA = (dA + x1_bar * rA) % n
# 验证 RB 在曲线上
x2, y2 = RB # B 的临时公钥
x2_bar = (1 << w) + (x2 & ((1 << w) - 1))
# V = [h * tA] * (RB + [x2_bar] * G)
point_RB_plus = point_add(RB, point_mul(x2_bar, (int(ecc['g'][0]), int(ecc['g'][1]))))
point_V = point_mul(h * tA % n, point_RB_plus)
xV, yV = point_V
# KDF
za = compute_za(ID_A, format(dA_pub[0], '064x'), format(dA_pub[1], '064x'))
zb = compute_za(ID_B, format(dB_pub[0], '064x'), format(dB_pub[1], '064x'))
kdf_input = xV.to_bytes(32, 'big') + yV.to_bytes(32, 'big') + za + zb
key = kdf_sm3(kdf_input, klen)
return key, xV, yV四、KDF 密钥派生函数
4.1 KDF 的定义
GM/T 0003.3 中的 KDF 基于 SM3 哈希,采用简单的迭代构造:
$$K = \text{KDF}(Z, klen) = H_1 \| H_2 \| \cdots \| H_m$$
其中:
- $H_i = \text{SM3}(Z \| \text{cnt}_i)$
- $\text{cnt}_i$ 是一个 32 比特的大端序计数器,起始值为 1
- $m = \lceil klen / 32 \rceil$(最后一段可能需要截断)
4.2 Python 实现
def kdf_sm3(z_bytes: bytes, klen: int) -> bytes:
"""
GM/T 0003.3 标准 KDF:基于 SM3 的迭代哈希
K = SM3(Z||00000001) || SM3(Z||00000002) || ... (截断到 klen 字节)
"""
hash_len = 32 # SM3 输出长度
n = (klen + hash_len - 1) // hash_len # 需要的迭代次数
result = bytearray()
for i in range(1, n + 1):
cnt = i.to_bytes(4, 'big')
block = func.sm3_hash(func.bytes_to_list(z_bytes + cnt))
result.extend(bytes.fromhex(block))
return bytes(result[:klen])
# 测试:派生 16 字节 SM4 密钥
test_input = bytes.fromhex('a99cdad25a10616774aed61cc28a5a5e315618adf0dae1ce86336de78b5da519')
sm4_key = kdf_sm3(test_input, 16)
print(f"SM4 key: {sm4_key.hex()} ({len(sm4_key)} bytes)")
# 测试:派生 32 字节 SM4-GCM 密钥+nonce
longer = kdf_sm3(test_input, 44) # 16 key + 12 nonce + 16 mac?
print(f"44 bytes: {longer.hex()}")4.3 KDF 的选择
GM/T 0003.3 指定使用上述迭代 SM3 KDF。在实际项目中,你也可以使用:
- GB/T 32918.3(等价于 GM/T 0003.3)中的 KDF
- HKDF-SM3(基于 RFC 5869,使用 HMAC-SM3 作为 PRF)
| 特性 | GM/T 0003.3 KDF | HKDF-SM3 |
|---|---|---|
| 算法 | SM3 迭代 | HMAC-SM3 |
| salt | 无(Z 直接输入) | 支持 |
| info | 无(仅 Z) | 支持(用于区分用途) |
| 形式化分析 | 标准定义 | HKDF 论文证明 |
五、密钥确认(可选步骤)
5.1 为什么需要密钥确认?
标准两轮密钥交换可以抵御被动攻击,但不能完全防止主动中间人攻击。密钥确认(Key Confirmation)通过交换 MAC 标签确保双方持有相同密钥。
5.2 确认标签的计算
import hmac
from cryptography.hazmat.primitives import hashes, hashes as crypto_hashes
def compute_confirmation_tag(key: bytes, xU: int, yU: int,
za: bytes, zb: bytes,
xA: int, yA: int,
xB: int, yB: int,
for_initiator: bool = True) -> bytes:
"""
计算密钥确认标签 S1 (B→A) 或 SA (A↔B)
使用 HMAC-SM3
"""
key_int = int.from_bytes(key, 'big')
if for_initiator:
# S1 = HMAC-SM3(key, xU || ZA || ZB || xA || yA || xB || yB)
msg = (xU.to_bytes(32, 'big') + za + zb +
xA.to_bytes(32, 'big') + yA.to_bytes(32, 'big') +
xB.to_bytes(32, 'big') + yB.to_bytes(32, 'big'))
else:
# SA = HMAC-SM3(key, yU || ZA || ZB || xA || yA || xB || yB)
msg = (yU.to_bytes(32, 'big') + za + zb +
xA.to_bytes(32, 'big') + yA.to_bytes(32, 'big') +
xB.to_bytes(32, 'big') + yB.to_bytes(32, 'big'))
# 使用 stdlib hmac(cryptography.hazmat.primitives.hmac 没有 compare_digest)
import hmac as stdlib_hmac
# 注意:这里使用 HMAC-SHA256 模拟,实际 HMAC-SM3 需要自定义
# 或使用 cryptography 的 HMAC 类但用 SM3 作为哈希
from cryptography.hazmat.primitives.hmac import HMAC
# 使用 HMAC-SM3(已在上方实现 hmac_sm3 函数)
tag = hmac_sm3(key, msg)
return tag
# 实际实现时,若 cryptography 不支持 HMAC-SM3 的算法参数,
# 需手动实现:HMAC(K, m) = H((K' ⊕ opad) || H((K' ⊕ ipad) || m))
def hmac_sm3(key: bytes, msg: bytes) -> bytes:
"""
手动实现 HMAC-SM3
"""
block_size = 64 # SM3 的分组大小
if len(key) > block_size:
key = bytes.fromhex(func.sm3_hash(func.bytes_to_list(key)))
# 填充 key 到 block_size
key = key.ljust(block_size, b'\x00')
o_key_pad = bytes(x ^ 0x5c for x in key)
i_key_pad = bytes(x ^ 0x36 for x in key)
# inner hash: SM3(i_key_pad || msg)
inner = func.sm3_hash(func.bytes_to_list(i_key_pad + msg))
# outer hash: SM3(o_key_pad || inner)
outer = func.sm3_hash(func.bytes_to_list(o_key_pad + bytes.fromhex(inner)))
return bytes.fromhex(outer)环境说明:cryptography 42+ 的 hmac.HMAC 支持 hashes.SM3(),但由于 cryptography.hazmat.primitives.hmac 子模块只有 HMAC 类,hmac.compare_digest() 不可用(这是 stdlib hmac 的方法)。生产代码中应分开导入:from cryptography.hazmat.primitives.hmac import HMAC + from hmac import compare_digest。
六、椭圆曲线辅助函数
上述代码依赖两个辅助函数,这里给出基于 gmssl 的实现:
def point_add(p1: tuple, p2: tuple) -> tuple:
"""
椭圆曲线点加法 P + Q
需要完整的椭圆曲线运算实现
"""
# 使用 gmssl 内置运算或自行实现
# 此处为伪代码——实际需依赖底层库
# 建议使用 gmssl 的底层运算或 coincidence 库
ecc = default_ecc_table
n = int(ecc['n'], 16)
a = int(ecc['a'], 16)
b = int(ecc['b'], 16)
p_mod = int(ecc['p'], 16)
if p1 is None: # 无穷远点
return p2
if p2 is None:
return p1
x1, y1 = p1
x2, y2 = p2
if x1 == x2 and y1 == y2:
# 点倍运算
lam = (3 * x1 * x1 + a) * pow(2 * y1, -1, p_mod) % p_mod
elif x1 == x2:
return None # P + (-P) = O
else:
lam = (y2 - y1) * pow(x2 - x1, -1, p_mod) % p_mod
x3 = (lam * lam - x1 - x2) % p_mod
y3 = (lam * (x1 - x3) - y1) % p_mod
return (x3, y3)
def point_mul(k: int, point: tuple) -> tuple:
"""
椭圆曲线标量乘法 [k]P
使用 double-and-add 算法
"""
result = None # 无穷远点
addend = point
while k:
if k & 1:
result = point_add(result, addend)
addend = point_add(addend, addend)
k >>= 1
return result注意:GM/T 0003.3 的完整协议实现涉及大量椭圆曲线运算细节。生产环境中建议使用完整实现库(如 python-gmss库),或参考 Tongsuo-Project 的 C 实现。上述代码为教育目的展示核心计算逻辑。
七、前向保密与安全性分析
7.1 前向保密(Forward Secrecy)
GM/T 0003.3 通过临时密钥 $r_A$ / $r_B$ 提供前向保密:
- 每次交换使用独立的临时密钥对
- 即使攻击者获取长期私钥 $d_A$(或 $d_B$),也无法计算过去的共享密钥
- 因为共享密钥依赖于 $r_A$ 和 $r_B$,而临时私钥在交换完成后被销毁
- 临时密钥对每次交换必须重新生成(不能复用)
- 临时私钥在内存中安全擦除(Python 中使用
del+ 依赖 GC,C 中使用memset_s) - 确保随机数生成器为密码学安全(
/dev/urandom或CryptGenRandom)
7.2 中间人攻击防护
标准协议通过以下机制防御中间人攻击:
- ZA 绑定:将用户 ID 和公钥绑定到共享密钥计算中
- 双向认证:双方互相计算 ZA,中间人无法伪造
- 可选确认标签:S1/SA 标签验证密钥一致性
7.3 常见攻击面
| 攻击类型 | 风险等级 | 缓解措施 |
|---|---|---|
| 临时密钥复用 | 高 | 每次交换后销毁临时私钥 |
| RA/RB 不在曲线上 | 高 | 验证接收的点在曲线上 |
| 小子群攻击 | 中 | 验证接收点不是无穷远点 |
| 时间侧信道 | 低 | 使用常量时间标量乘法 |
八、实战:完整密钥交换示例
将上述步骤整合为一个完整的可运行示例:
def compute_full_key_exchange_initiator(dA_hex, rA_hex, RB, dB_pub, ID_A, ID_B, klen=16):
"""
发起方 A 的完整密钥计算(包装函数)
调用 compute_shared_key_initiator 并返回协商密钥
"""
key, xV, yV = compute_shared_key_initiator(
dA_hex, rA_hex, RB, dB_pub, ID_A, ID_B, klen
)
return key
def compute_full_key_exchange_responder(dB_hex, rB_hex, RA, dA_pub, ID_A, ID_B, klen=16):
"""
响应方 B 的完整密钥计算(包装函数)
调用 compute_shared_key_responder 并返回协商密钥和共享点坐标
"""
key, RB, xU, yU = compute_shared_key_responder(
dB_hex, rB_hex, RA, dA_pub, ID_A, ID_B, klen
)
return key, xU
def main():
"""
GM/T 0003.3-2012 完整密钥交换演示
注意:本示例使用预生成密钥,仅为教学目的
"""
from gmssl import func
from gmssl import sm2 as gmssl_sm2
print("=" * 60)
print("GM/T 0003.3-2012 SM2 密钥交换协议演示")
print("=" * 60)
# ====== 预配置的长期密钥对(实际环境中各自持有自己的) ======
ecc = default_ecc_table
# A 的长期密钥
dA_hex = '7b5d1b62e82a3e7c9f1d4a8b2e5f0c3d6a9b1e4f7c0a3d6e9b2f5c8a1d4e7f0'
# A 的公钥 P_A = [dA]G(实际由 dA 派生,此处简化)
sm2_a = gmssl_sm2.CryptSM2('00' * 64, ecc['g'])
dA_pub = sm2_a._kg(int(dA_hex, 16), ecc['g'])
# B 的长期密钥
dB_hex = '3d6a9e2f5c8b1a4d7e0f3c6a9d2e5f8b1a4d7e0f3c6a9d2e5f8b1a4d7e0f3c6a'
# B 的公钥 P_B = [dB]G
sm2_b = gmssl_sm2.CryptSM2('00' * 64, ecc['g'])
dB_pub = sm2_b._kg(int(dB_hex, 16), ecc['g'])
# ====== 步骤 1: 计算 ZA ======
ID_A = b"alice@company.cn"
ID_B = b"bob@company.cn"
print(f"ID_A: {ID_A.decode()}")
print(f"ID_B: {ID_B.decode()}")
# ====== 步骤 2: 生成临时密钥对 ======
rA_hex, RA = generate_temp_keypair()
rB_hex, RB = generate_temp_keypair()
print(f"A 的临时私钥 rA: {rA_hex[:16]}...")
print(f"A 的临时公钥 RA: {RA[0][:16]}...")
print(f"B 的临时私钥 rB: {rB_hex[:16]}...")
# ====== 步骤 3: 密钥协商 ======
# A 计算共享密钥
key_A = compute_full_key_exchange_initiator(
dA_hex, rA_hex, RB, dB_pub, ID_A, ID_B, klen=16
)
# B 计算共享密钥
key_B, xU = compute_full_key_exchange_responder(
dB_hex, rB_hex, RA, dA_pub, ID_A, ID_B, klen=16
)
# ====== 步骤 4: 验证 ======
print(f"\n协商密钥 (A): {key_A.hex()}")
print(f"协商密钥 (B): {key_B.hex()}")
if key_A == key_B:
print("✅ 密钥协商成功!")
print(f"SM4 会话密钥: {key_A.hex()}")
else:
print("❌ 密钥协商失败!")
# ====== 步骤 5: 使用协商密钥加密 ======
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
iv = b'\x00' * 16 # 实际应使用随机 IV
cipher = Cipher(algorithms.SM4(key_A), modes.CBC(iv))
encryptor = cipher.encryptor()
plaintext = b"hello world!!!!" # 16 bytes (block aligned)
ciphertext = encryptor.update(plaintext)
print(f"\n加密: {plaintext} -> {ciphertext.hex()}")
if __name__ == '__main__':
main()九、常见错误与排错清单
9.1 协商出的密钥不一致
- 原因 1:ZA 计算时 ENTL 字节序错误(小端 vs 大端)
- 原因 2:坐标值未补零到 32 字节
- 原因 3:w 计算错误(应为 128 而非 127)
- 原因 4:KDF 计数器起始值错误(应从 1 开始,不是 0)
9.2 "Temporary point not on curve"
接收方验证临时公钥不在椭圆曲线上。检查:
- 坐标确实在素数域 $[0, p-1]$ 内
- 坐标满足曲线方程 $y^2 \equiv x^3 + ax + b \pmod{p}$
9.3 前向保密失效
- 临时密钥复用:确保每次交换生成新的 $(r, R)$
- 内存泄露:临时私钥在交换完成后应立即从内存中清除
十、总结
GM/T 0003.3-2012 定义的 SM2 密钥交换协议是一个精巧的两方协商协议,通过 ZA 预处理实现身份绑定,通过临时密钥提供前向保密,通过可选的确认标签防御中间人攻击。
在实际部署中请务必注意:
- ENTL 必须是比特长度,不是字节长度
- 坐标必须补零到 32 字节,保证 ZA 计算一致
- w = 128(对于 256-bit SM2 曲线)
- 临时密钥每次必须重新生成,确保前向保密
- 生产代码需完整椭圆曲线运算,不要自行实现所有底层运算
参考来源
- GM/T 0003.3-2012《SM2 椭圆曲线公钥密码算法 第 3 部分:密钥交换协议》
- GM/T 0003.1-2012《SM2 椭圆曲线公钥密码算法 第 1 部分:总则》
- GM/T 0004-2012《SM3 密码杂凑算法》
- GB/T 32918.3-2016(国家标准版 SM2 第 3 部分)
- GMSSL 开源实现
*本文实现代码基于 Python 3.10 + cryptography 42.x(SM3/SM4 支持)+ gmssl 3.2.x(SM2 底层运算)。部分辅助函数为生产环境伪代码,需根据实际椭圆曲线库调整。*