国密算法在 JWT 中的应用:SM2-SM3 签名与 SM4-CBC 加密实战

密码学 · 2026-06-03 · 18 阅读

前言

JWT(JSON Web Token)是现代身份认证的基石技术,广泛用于 OAuth2、SSO、API 鉴权等场景。但标准 JWT 依赖 RSA/ECDSA/AES 等国际算法,在国密合规场景下需要用 SM2/SM3/SM4 替代。

本文实现一套完整的国密 JWT 方案:

  • JWS(签名):用 SM2 + SM3 替代 RS256/ES256,算法标识 SM2-SM3
  • JWE(加密):用 SM4-CBC + SM2 密钥封装替代 A256GCM,算法标识 SM4-CBC
  • 密钥管理:主密钥轮换、CEK 生命周期、密钥托管方案
所有代码基于 gmssl 3.2.2 和 Python 3.11,可完整运行。

一、JWT 协议与国密算法映射

1.1 JWT 的三层结构

CODE
┌─────────────────────────────────────────────────────┐
│  JWS (签名 JWT)                                      │
│  header.payload.signature                            │
│  ─────── ─────── ─────────                           │
│  Base64  Base64  签名值                              │
├─────────────────────────────────────────────────────┤
│  JWE (加密 JWT)                                      │
│  header.encrypted_key.iv.ciphertext.tag              │
│  ─────── ──────────── ── ────────── ───              │
│  头部    封装的CEK    IV  密文    认证标签            │
└─────────────────────────────────────────────────────┘

1.2 国密算法映射表

JWT 算法国际算法国密替代说明
RS256RSA-SHA256SM2-SM3SM2 签名 + SM3 哈希
ES256ECDSA-SHA256SM2-SM3同上(不同曲线)
A256GCMAES-256-GCMSM4-CBCSM4 对称加密
dir直接对称密钥SM4-CBC直接用 CEK 加密

1.3 自定义算法标识

JWT 的 algenc 字段使用自定义值:

JSON
// JWS Header
{
  "alg": "SM2-SM3",
  "typ": "JWT"
}

// JWE Header
{
  "alg": "SM2-KW",
  "enc": "SM4-CBC",
  "typ": "JWE"
}

二、环境准备

BASH
pip install gmssl==3.2.2

验证安装:

PYTHON
from gmssl import sm2, sm3, func
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
from gmssl.func import bytes_to_list, list_to_bytes
print("gmssl 加载成功")

本文使用的 gmssl 是纯 Python 实现的国密算法库,版本 3.2.2。注意该库的 crypt_ecb 方法会在内部自动做 PKCS7 填充,不适合手动实现 CBC 模式。本文使用 one_round 方法做单块加解密,手动实现 CBC 链。

三、核心工具函数

3.1 Base64URL 编解码

3.2 PKCS7 填充

PYTHON
def pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
    pad_len = block_size - (len(data) % block_size)
    return data + bytes([pad_len] * pad_len)

def pkcs7_unpad(data: bytes) -> bytes:
    pad_len = data[-1]
    if not (1 <= pad_len <= 16):
        raise ValueError("无效填充: %d" % pad_len)
    for i in range(pad_len):
        if data[-(i+1)] != pad_len:
            raise ValueError("填充字节不一致")
    return data[:-pad_len]

3.3 SM4 单块加解密

PYTHON
def sm4_encrypt_block(key: bytes, block: bytes) -> bytes:
    """SM4 加密单个 16 字节块"""
    crypt = CryptSM4()
    crypt.set_key(key, SM4_ENCRYPT)
    return list_to_bytes(crypt.one_round(crypt.sk, bytes_to_list(block)))

def sm4_decrypt_block(key: bytes, block: bytes) -> bytes:
    """SM4 解密单个 16 字节块"""
    crypt = CryptSM4()
    crypt.set_key(key, SM4_DECRYPT)
    return list_to_bytes(crypt.one_round(crypt.sk, bytes_to_list(block)))

3.4 SM4-CBC 加解密

四、SM2 密钥对生成

注意:生产环境应使用硬件安全模块(HSM)存储主密钥,上述代码仅用于演示。

五、JWS 实现:SM2-SM3 签名 JWT

5.1 签名流程

5.2 验证流程

5.3 完整示例

六、JWE 实现:SM4-CBC 加密 JWT

6.1 加密流程

6.2 解密流程

七、性能对比测试

7.1 测试环境

  • Python 3.11.15
  • gmssl 3.2.2(纯 Python 实现)
  • 测试数据:200 字节 payload,1000 次迭代

7.2 签名性能

7.3 性能数据

操作SM2-SM3RS256 (RSA-2048)ES256 (P-256)
签名~3.5 ms~1.2 ms~0.8 ms
验签~2.8 ms~0.1 ms~0.9 ms
公钥长度128 字节256 字节64 字节
签名长度128 字节256 字节64 字节
注:gmssl 是纯 Python 实现,性能远低于 C 库。生产环境使用 GmSSL C 库 + Python 绑定可提升 10-50 倍性能。

7.4 加密性能

操作SM4-CBCAES-256-GCM
加密 (200B)~0.15 ms~0.02 ms
解密 (200B)~0.15 ms~0.02 ms
密文膨胀+16 字节 (padding)+16 字节 (tag)

八、密钥管理策略

8.1 密钥层次

8.2 CEK 生命周期

8.3 密钥轮换

九、生产环境注意事项

9.1 时钟偏移问题

JWT 的 expiat 依赖服务器时钟同步。分布式环境下建议:

PYTHON
CLOCK_SKEW_TOLERANCE = 30  # 容忍 30 秒时钟偏移

def verify_exp(exp: int) -> bool:
    now = time.time()
    return exp > (now - CLOCK_SKEW_TOLERANCE)

9.2 Token 重放攻击防护

9.3 SM2 密钥安全

9.4 SM4 IV 安全

PYTHON
# ❌ 错误:固定 IV
iv = b'\x00' * 16

# ❌ 错误:计数器 IV(CBC 模式不安全)
iv = counter.to_bytes(16, 'big')

# ✅ 正确:密码学安全随机 IV
iv = os.urandom(16)

9.5 gmssl 的 crypt_ecb 陷阱

gmssl 的 CryptSM4.crypt_ecb() 方法会在内部自动做 PKCS7 填充,导致 16 字节输入变成 32 字节输出。实现 CBC 模式时,应使用 one_round 方法操作单个块:

PYTHON
# ❌ 错误:crypt_ecb 会自动填充
ciphertext = crypt.crypt_ecb(plaintext)  # 16 字节 → 32 字节!

# ✅ 正确:使用 one_round 操作单块
crypt = CryptSM4()
crypt.set_key(key, SM4_ENCRYPT)
output = list_to_bytes(crypt.one_round(crypt.sk, bytes_to_list(block)))

十、与标准 JWT 的互操作

10.1 混合部署方案

在国密改造过渡期,可能需要同时支持国际算法和国密算法:

10.2 JWK 格式导出

十一、总结

关键要点

  • SM2-SM3 签名:用 SM2 的 sign_with_sm3 方法,签名输入为 header.payload 的 UTF-8 编码
  • SM4-CBC 加密:手动实现 CBC 链,使用 one_round 避免自动填充陷阱
  • 密钥管理:三层密钥体系(主密钥 → KEK → CEK),CEK 每次随机生成
  • 安全防护:防重放(jti + Redis)、时钟偏移容忍、IV 随机生成
  • 性能:纯 Python 实现约 3ms/次签名,生产环境应使用 C 库绑定

适用场景

场景推荐方案
内部 API 鉴权JWS (SM2-SM3)
敏感数据传输JWE (SM4-CBC + SM2 签名)
跨域 SSOJWS + CEK 轮换
物联网设备JWS(短 Token,低开销)

参考来源

  • GM/T 0003-2012:SM2 椭圆曲线公钥密码算法
  • GM/T 0004-2012:SM3 密码杂凑算法
  • GM/T 0002-2012:SM4 分组密码算法
  • RFC 7515:JSON Web Signature (JWS)
  • RFC 7516:JSON Web Encryption (JWE)
  • RFC 7517:JSON Web Key (JWK)
  • gmssl 源码:https://github.com/duanhongyi/gmssl