国密算法实战——SM2/SM3/SM4 在国产系统中的应用

开门见山:一秒钟的选型结论

非对称用 SM2(曲线固定为 sm2p256v1,绝不能换其他椭圆曲线),签名前置哈希必须按 GM/T 0003 规范计算 Z 值(不是简单 SM3(消息)),加密密文顺序统一用 C1C3C2(即 04 || X || Y || SM3摘要 || 密文);哈希用 SM3(256 位输出),HMAC 用 HMAC-SM3;对称用 SM4,新系统一律 SM4-GCM 或 SM4-CBC + HMAC-SM3(绝不用裸 ECB);协议层国产系统走 TLCP 双证书(签名证书+加密证书),跨网络与 TLS 互通走 RFC 8998 定义的 TLS 1.3 套件(TLS_SM4_GCM_SM3);工具链选 GmSSL 或 Tongsuo(铜锁)做底层,Java 用 BouncyCastle 1.68+,Node 用 sm-crypto,绝不用网上流传的"纯 JavaScript 国密实现"。

如果你赶时间,把上面这段话贴到方案评审文档里就够了。如果你接着往下读,这篇文章会回答一个更本质的问题:为什么 2026 年了,国密改造项目里"签名验证失败"和"加密结果无法互通"仍然是最常见的两大翻车现场?为什么同一份"国密实现",在不同库之间互通的成功率不到 50%? 答案几乎从来不在算法本身------SM2/SM3/SM4 的算法设计都是经过严格评审的------而在规范细节的工程实现上:Z 值怎么算、密文顺序怎么拼、曲线 OID 怎么标、双证书怎么配、密码套件怎么协商,每一处"看起来无所谓"的偏差,都可能让两套"都符合国密"的系统无法互通。

一、先看清地图:国密算法家族与"三件套"的定位

1.1 完整的国密算法谱系

"国密"是国家密码管理局发布的**商用密码(SM,即"商密")**算法系列。核心是五个算法家族,覆盖现代密码学的三大类目:

算法 类别 国家标准 对标的国际算法 典型用途
SM1 分组密码(不公开,仅芯片实现) - AES-128 智能卡、轨道交通门禁
SM2 椭圆曲线公钥密码 GB/T 32918-2016(5 部分) RSA / ECDSA / ECDH 数字签名、密钥交换、公钥加密
SM3 密码杂凑 GB/T 32905-2016 SHA-256 摘要、HMAC、签名前置哈希
SM4 分组密码 GB/T 32907-2016 AES-128 传输与存储加密
SM9 标识密码(IBC) GB/T 38635-2020 无直接对标 以身份为公钥的签名与加密
ZUC(祖冲之) 序列密码 GB/T 33133 系列 Snow 3G / AES-CTR 5G/4G 通信加密

一句话记忆SM2 管非对称、SM3 管摘要、SM4 管对称、SM9 管身份即公钥、ZUC 管流密码 。日常开发中 90% 的国密改造只涉及 SM2/SM3/SM4 三件套 ,所以这三件我们展开讲,SM9 和 ZUC 讲清定位即可。

SM1 的特殊性 :SM1 算法本身不公开 ,只以加密芯片 的形式提供(如 HSM、加密卡、智能卡)。这意味着软件层面无法直接调用 SM1------如果项目要求 SM1,意味着必须采购密码卡/密码机等硬件。这是密评现场最常被搞错的点之一:方案里写"用 SM1 加密",落地时必须先理解 SM1 不是软件算法。

1.2 为什么是"三件套"?工程定位的清晰映射

把 SM2/SM3/SM4 放到国际算法的坐标系里看,定位非常清晰:

能力 国际方案 国密方案 性能对比
数字签名 RSA-2048 / ECDSA P-256 SM2(256 位曲线) SM2 签名速度 > RSA-2048,密钥短一半
密钥交换 ECDH P-256 SM2 密钥交换 等价
公钥加密 RSA-OAEP SM2 公钥加密 密文比 RSA 短一半
摘要 SHA-256 SM3(256 位输出) 性能接近
HMAC HMAC-SHA256 HMAC-SM3 等价
对称加密 AES-128-GCM SM4-GCM 软实现 SM4 慢于 AES(无 AES-NI 等价指令)

一个重要的性能背景 :AES 在 Intel/ARM 上有 SIMD 指令集加速(AES-NI、Crypto Extensions),SM4 在通用 CPU 上长期只有纯软件实现。近年来 ARM 的 SM4 Crypto Extensions 和 Intel 的 AVX2 优化版本开始出现(如 Linux 内核的 crypto-sm4-arm64-ce-gcm 模块),但软实现 SM4 比硬加速 AES 慢一个数量级 是常态。这意味着:如果你改造的是高吞吐场景(如 CDN、网关),必须考虑硬件密码卡或 GPU 加速

1.3 三大标准与合规强制

国密算法的落地不是"技术选型",而是合规强制。三条硬性法规把国密变成必选项:

  1. 《密码法》(2020 年施行):明确要求关键信息基础设施使用商用密码开展安全防护;
  2. 《商用密码管理条例》:规范商用密码检测认证体系;
  3. GB/T 39786-2021《信息系统密码应用基本要求》:密评(商用密码应用安全性评估)的核心依据,对等保三级及以上系统强制要求。

密评 vs 等保的关系 :等保是"整体网络安全评估"(公安主导),密评是"密码应用专项评估"(密码管理部门主导)。对于使用商用密码的等保三级及以上系统,两项评估都要做 ,密评会重点检查:传输通道是否用 SM 算法、存储是否用 SM4、摘要是否用 SM3、签名是否用 SM2。

一个高频整改单场景 :密评机构出单------"系统传输通道未采用商用密码算法保护,数据存储加密未使用 SM4,摘要算法应替换为 SM3"。真到动手时,大多数工程师的第一个困惑是:"国密算法到底有哪几个?SM2 和 RSA 什么关系?SM4 用什么模式?TLCP 双证书又是什么?"------这正是本文要逐一拆解的。

二、SM2:国产椭圆曲线的"一鱼三吃"

2.1 数学直觉与曲线参数

SM2 是基于椭圆曲线密码(ECC)的公钥算法,使用国密局指定的 256 位素域曲线 sm2p256v1 。它的安全性来自椭圆曲线离散对数问题(ECDLP)------已知私钥 d 和基点 G 算公钥 P = dG 是一次乘法,而已知 P、G 反推 d 在计算上不可行。

关键工程事实SM2 的曲线参数是固定的,由 GB/T 32918.5-2017 严格规定,RFC 8998 给出了完整参数:

text 复制代码
p = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b = 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
n = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy = BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0

RFC 8998 的明文规定 :"与其他基于 ECC 的公钥算法不同,SM2 不得选择其他椭圆曲线 "。这是一个非常关键的合规红线------你不能用 P-256 曲线跑"SM2 算法",那不叫 SM2,那是普通 ECDSA,密评时会被判不合规。

国密 OID :所有国密算法在 ASN.1 体系下的 OID 都挂在 1.2.156.10197(iso(1) member-body(2) cn(156) gm(10197))这个中国注册根下:

  • id-sm2-sign = 1.2.156.10197.1.301(SM2 签名)
  • sm3WithSM2 = 1.2.156.10197.1.501(SM2 with SM3 签名)
  • id-sm2-keyAgreement = 1.2.156.10197.1.303(SM2 密钥协商)

常见翻车现场 :国密证书验签失败的第一原因就是签名算法 OID 错配 ------证书里 SignatureAlgorithm 字段必须为 1.2.156.10197.1.501(SM2 with SM3),如果写成 sha256WithRSAEncryption 或 ECDSA 的 OID,标准密码库会直接判"未知算法"或"签名不匹配"。

2.2 三种用途之一:SM2 数字签名与 Z 值的"陷阱"

SM2 签名看起来像 ECDSA,但预处理完全不同 。这是国密改造中最大的一个翻车点

很多人以为 SM2 签名 = SM3(消息) → 签名 ,这是错的 。GM/T 0003.2 规定的完整流程是:

Step 1:计算 Z 值(身份绑定)

text 复制代码
Z = SM3( ENTL || IDA || a || b || Gx || Gy || xA || yA )
其中:
- ENTL:用户 IDA 的比特长度(2 字节大端)
- IDA:用户身份标识字符串(默认值 "1234567812345678",即 ASCII 0x31323334353637383132333435363738)
- a, b:SM2 曲线参数
- Gx, Gy:基点 G 坐标
- xA, yA:签名方公钥坐标

Step 2:计算真正的摘要

text 复制代码
e = SM3( Z || M )

Step 3:标准 ECDSA-like 签名运算

text 复制代码
随机选 k ∈ [1, n-1]
(x1, y1) = [k]·G
r = (e + x1) mod n          若 r=0 或 r+k=n,重选 k
s = ((1 + dA)^(-1) · (k − r·dA)) mod n    若 s=0,重选 k
签名 = (r, s)

Z 值的存在意义 :它把签名者身份 绑进了签名。没有 Z 值,攻击者可以构造一张"替换公钥"的攻击------保留签名,把证书里的公钥换成自己的,验签仍然通过(这是 ECDSA 的经典 key substitution attack)。Z 值通过把公钥坐标混入哈希 ,让签名"自证"公钥身份。

工程陷阱清单

  1. ID 不匹配 ------签名端用 "1234567812345678",验签端用空字符串或另一个 ID,必然验签失败。IDA 必须作为协议的一部分显式传递,或者双方硬编码同一默认值;
  2. Z 值没算 ------某些简化实现直接对 SM3(M) 签名,这类签名与标准 SM2 不兼容 。GmSSL、BouncyCastle、tjfoc/gmsm 等标准库都会自动算 Z 值,自己实现时最容易漏
  3. ENTL 字节序 ------必须是大端 2 字节,错写成小端必然失败;
  4. 签名格式 ------SM2 签名有**纯 R||S 拼接(64 字节)**和 **DER 编码(变长,通常 70-72 字节)**两种格式,两端必须约定同一种。Java BouncyCastle 默认输出 DER,某些前端 JS 库输出裸 64 字节,互通时报"签名不是 ASN.1 编码"是常见错误。

2.3 三种用途之二:SM2 公钥加密与 C1C2C3 / C1C3C2

SM2 加密结构(GB/T 32918.4):

text 复制代码
密文 = C1 || C2 || C3  (老顺序)或  C1 || C3 || C2  (新顺序,标准推荐)
C1:椭圆曲线随机点(04 || x || y,共 65 字节未压缩)
C3:SM3 摘要(32 字节,用于完整性校验)
C2:与明文等长的密文

C1C2C3 vs C1C3C2 是另一个经典翻车点 。GB/T 32918.4-2016 标准顺序是 C1||C3||C2 ,但早期很多实现(包括 BouncyCastle 老版本)默认 C1||C2||C3两端约定不一致时,解密会"成功但拿到垃圾明文",或直接报摘要校验失败

密文格式还有第三层坑 :BouncyCastle 输出的密文会在 C1 前加一个 04 头(表示未压缩点),而 sm-crypto 等 JS 库的输出不带 04 头跨语言互通时必须先做长度判断补头或去头

GM/T 0003 的 ASN.1 描述

text 复制代码
SM2Cipher ::= SEQUENCE {
    XCoordinate   INTEGER,     -- C1 的 x
    YCoordinate   INTEGER,     -- C1 的 y
    HASH          OCTET STRING SIZE(32),  -- C3
    CipherText    OCTET STRING            -- C2
}

工程建议

  • 新系统统一用 C1C3C2 + 04 头(这是标准推荐,也是 GmSSL/Tongsuo 等主流库的默认);
  • 跨语言交互时显式声明密文顺序------比如在 API 文档里写明"密文格式为 04||X(32bytes)||Y(32bytes)||SM3(32bytes)||Ciphertext";
  • 不要试图"试错"------错误的顺序解出来的是错位字节,可能偶然通过摘要校验(极小概率),但本质上是错误实现。

2.4 三种用途之三:SM2 密钥交换

SM2 密钥交换(GB/T 32918.3)类似 ECDH,但加入了双方 ID 和临时密钥的复杂运算,比普通 ECDH 多了双方身份验证 ------可以看作"带认证的 ECDH"。在 TLCP 协议中,这是 ECDHE-SM2-WITH-SM4-SM3 套件的基础。

实际工程中 ,SM2 密钥交换很少直接在应用层使用,主要由 TLCP/国密 TLS 协议栈在握手阶段自动处理。应用开发者不需要手写密钥交换,只需要正确配置证书。

2.5 SM2 Python 实战代码

Python 生态的国密支持有三个主流库:gmssl(纯 Python 实现)tjfoc/gmsm(Go)pyca/cryptography + 第三方扩展。下面用 gmssl 演示:

python 复制代码
# pip install gmssl
from gmssl import sm2, func
# ============ 1. 密钥生成 ============
# 生产环境:私钥应来自 HSM 或 CSPRNG,不要用 func.random_hex 这种简化方式
private_key = func.random_hex(64)   # 32 字节 = 64 个十六进制字符
# 公钥是 04 || X(64) || Y(64) 共 130 个十六进制字符
public_key = "04" + "9EF573019D9A03B16B0BE44FC8A5B4E8E098F56034C97B312282DD0B4810AFC3" \
                  + "CC759673ED0FC9B9DC7E6FA38F0E2B121E02654BF37EA6B63FAF2A0D6013EADF"
# ============ 2. SM2 加密(C1C3C2 模式)============
crypt_sm2 = sm2.CryptSM2(public_key=public_key, private_key=private_key)
plaintext = b"Transfer 100 to Alice"
ciphertext = crypt_sm2.encrypt(plaintext)   # 默认为 C1C3C2 模式
decrypted = crypt_sm2.decrypt(ciphertext)
assert decrypted == plaintext
# ============ 3. SM2 签名(自动计算 Z 值)============
random_hex = func.random_hex(32)   # 签名所需的随机数 k
sig = crypt_sm2.sign(plaintext, random_hex)
# sig 是 64 字节裸格式(r || s),如需 DER 需手动封装
verified = crypt_sm2.verify(sig, plaintext)
assert verified
# ============ 4. 指定 ID 的签名(与 GmSSL 命令行互通)============
# gmssl 命令行的默认 ID 是 "1234567812345678"
custom_id = "1234567812345678"
# gmssl 3.x 的 CryptSM2 支持 asn1 验签(用于国密证书场景)

GmSSL 命令行实战

bash 复制代码
# ============ SM2 密钥生成 ============
gmssl sm2keygen -pass 1234 -out sm2.pem -pubout sm2pub.pem
# ============ SM2 签名(自动算 Z 值,默认 ID = 1234567812345678)============
echo hello | gmssl sm2sign -key sm2.pem -pass 1234 -out sm2.sig -id 1234567812345678
# ============ SM2 验签 ============
echo hello | gmssl sm2verify -pubkey sm2pub.pem -sig sm2.sig -id 1234567812345678
# ============ SM2 加密解密 ============
echo hello | gmssl sm2encrypt -pubkey sm2pub.pem -out sm2.der
gmssl sm2decrypt -key sm2.pem -pass 1234 -in sm2.der

关键工程点

  • GmSSL 命令行的 -id 参数对应 GM/T 0003 的用户 IDA,必须与对端约定一致
  • GmSSL 输出的签名是裸 R||S 格式,如果对接国密证书的 X.509 验签,需要 DER 封装 (gmssl 有 sm2-verify-cert 类命令);
  • 生产环境的 SM2 私钥应放密码卡/密码钥匙 (通过 SKF/SDF 接口访问),不要让私钥落盘

三、SM3:国产摘要算法的"直接替换型"

3.1 SM3 的算法特点

SM3(GB/T 32905-2016)输出 256 位哈希,功能上对标 SHA-256 ,但内部压缩函数、消息扩展逻辑完全独立自研 ------这意味着 SM3 哈希和 SHA-256 哈希输出不同,不能互相校验

SM3 的工程要点

  • 输出 256 位(32 字节),与 SHA-256 一致;
  • 没有长度扩展攻击 ------SM3 的 Merkle--Damgård 结构与 SHA-256 类似,但不建议 直接用"SM3(key || message)"这种 naive MAC(仍然存在长度扩展风险),必须用 HMAC-SM3
  • HMAC-SM3 是标准用法,输出 32 字节;
  • PBKDF2-SM3 可以用于密码哈希,但密码存储更推荐 Argon2id(国密场景下 SM3 加盐 + 多次迭代是合规最低线)。

3.2 SM3 在签名和 Hmac 中的实战

python 复制代码
from gmssl import sm3, func
# ============ SM3 摘要 ============
data = b"Hello, 国密"
sm3_hash = sm3.sm3_hash(func.bytes_to_list(data))
print(f"SM3: {sm3_hash}")   # 64 个十六进制字符 = 256 位
# ============ HMAC-SM3 ============
from gmssl.sm3 import sm3_hash
import hmac
def hmac_sm3(key: bytes, msg: bytes) -> str:
    """手动实现 HMAC-SM3(gmssl 库未直接提供)"""
    block_size = 64   # SM3 block size
    if len(key) > block_size:
        key = bytes.fromhex(sm3.sm3_hash(func.bytes_to_list(key)))
    key = key.ljust(block_size, b'\x00')
    ipad = bytes(b ^ 0x36 for b in key)
    opad = bytes(b ^ 0x5c for b in key)
    inner = sm3.sm3_hash(func.bytes_to_list(ipad + msg))
    return sm3.sm3_hash(func.bytes_to_list(opad + bytes.fromhex(inner)))

GmSSL 命令行

bash 复制代码
# SM3 摘要
echo -n abc | gmssl sm3
# HMAC-SM3
echo -n abc | gmssl sm3hmac -key 11223344556677881122334455667788

3.3 SM3 的"特殊用法":配合 SM2 签名的 Z 值哈希

如第二节所述,SM2 签名的预处理会用 SM3 计算 Z 值 ------这是 SM3 在国密生态里最特殊的用法。如果你的系统自己做 SM2 签名验签,必须实现完整的 Z 值计算 ,不能用简单的 SM3(消息) 替代。

OpenHarmony 的 HVB(Hypervisor Verified Boot)源码给出了一个完整的 Z 值计算实现示例:

c 复制代码
// Z 值计算(OpenHarmony hvb_sm2.c 简化)
void sm2_compute_z(uint8_t *z_out, 
                   const uint8_t *user_id, size_t id_len,
                   const sm2_pubkey_t *pubkey) {
    hvb_sm3_init(&ctx);
    // 1. ID 长度(2 字节大端)
    uint16_t entl = id_len * 8;
    hvb_sm3_update(&ctx, (uint8_t[]){(entl >> 8) & 0xff, entl & 0xff}, 2);
    // 2. ID
    hvb_sm3_update(&ctx, user_id, id_len);
    // 3. 曲线参数 a, b
    hvb_sm3_update(&ctx, curve_a, 32);
    hvb_sm3_update(&ctx, curve_b, 32);
    // 4. 基点 G 坐标
    hvb_sm3_update(&ctx, curve_gx, 32);
    hvb_sm3_update(&ctx, curve_gy, 32);
    // 5. 公钥坐标
    hvb_sm3_update(&ctx, pubkey->x, 32);
    hvb_sm3_update(&ctx, pubkey->y, 32);
    hvb_sm3_final(&ctx, z_out);   // 32 字节 Z 值
}
// 真正的签名摘要
void sm2_compute_digest(uint8_t *e_out, 
                        const uint8_t *z_value, 
                        const uint8_t *msg, size_t msg_len) {
    hvb_sm3_init(&ctx);
    hvb_sm3_update(&ctx, z_value, 32);   // Z 值在前
    hvb_sm3_update(&ctx, msg, msg_len);
    hvb_sm3_final(&ctx, e_out);   // e = SM3(Z || M)
}

四、SM4:国产对称算法与"模式选择"

4.1 SM4 的结构

SM4(GB/T 32907-2016)是 128 位分组、128 位密钥的分组密码,采用 32 轮 Feistel 变体结构密钥长度和分组长度都固定为 128 位 ------这一点与 AES 不同(AES 支持 128/192/256 位密钥),SM4 只有 128 位密钥

安全强度评估 :SM4 的 128 位安全强度在 2026 年仍被认为是充分的(与 AES-128 等价),但低于 AES-256。对于"需要对抗未来量子计算"的场景,SM4 与 AES 一样都不抗量子。

4.2 模式选择:绝不要裸用 ECB

与 AES 一样,SM4 的安全性几乎完全取决于工作模式的选择 。这是国密改造中第二个大坑------很多密评整改方案只说"用 SM4 加密",不指定模式,最后代码写成了 ECB

模式 安全性 推荐度 备注
SM4-ECB ❌ 极差(块模式泄露) 禁止 仅 16 字节内密钥包装可勉强用
SM4-CBC ⚠️ 中(需配合 HMAC) 可用 必须外挂 HMAC-SM3(EtM)
SM4-CFB ⚠️ 中 少用 流模式
SM4-OFB ⚠️ 中 少用 流模式
SM4-CTR ✅ 好(需配合 HMAC) 推荐 并行、无 padding
SM4-GCM 最佳(AEAD) 强烈推荐 内置认证,国密 TLS 主推

GCM 模式 是 SM4 的现代推荐------RFC 8998 定义的 TLS 1.3 国密套件就用 SM4-GCM ,Linux 内核也已加入 CRYPTO_SM4_ARM64_CE_GCM 加速模块。新系统一律 SM4-GCM

GCM 的 Nonce 规则与 AES-GCM 完全一致

  • Nonce 12 字节,同一密钥下绝不可复用
  • Nonce 重用会导致机密性泄露(密钥流相同)+ 完整性被伪造(认证密钥 H 被恢复)
  • 分布式系统多副本场景用 Nonce 前缀(实例 ID + 本地计数器)或换用 SM4-CTR + HMAC-SM3。

4.3 SM4 实战代码

GmSSL 命令行

bash 复制代码
# SM4-CBC 加密
KEY=11223344556677881122334455667788
IV=11223344556677881122334455667788
echo hello | gmssl sm4 -cbc -encrypt -key $KEY -iv $IV -out sm4.cbc
gmssl sm4 -cbc -decrypt -key $KEY -iv $IV -in sm4.cbc
# SM4-CTR(推荐,无 padding)
echo hello | gmssl sm4 -ctr -encrypt -key $KEY -iv $IV -out sm4.ctr

Python(gmssl 库)

python 复制代码
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
key = bytes.fromhex("0123456789abcdef0123456789abcdef")  # 16 字节
iv  = bytes.fromhex("11223344556677881122334455667788")
# ============ SM4-CBC ============
crypt = CryptSM4()
crypt.set_key(key, SM4_ENCRYPT)
plaintext = b"Hello, 国密 SM4"
ciphertext = crypt.crypt_cbc(iv, plaintext)   # 自动 PKCS7 padding
crypt_dec = CryptSM4()
crypt_dec.set_key(key, SM4_DECRYPT)
decrypted = crypt_dec.crypt_cbc(iv, ciphertext)
assert decrypted == plaintext
# ============ SM4-ECB(仅用于密钥包装,禁止用于业务数据)============
ecb_crypt = CryptSM4()
ecb_crypt.set_key(key, SM4_ENCRYPT)
ecb_ct = ecb_crypt.crypt_ecb(plaintext)

Java(BouncyCastle)

java 复制代码
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;
// 一次性注册 BC Provider
static {
    Security.addProvider(new BouncyCastleProvider());
}
// ============ SM4-GCM(推荐)============
public static byte[] sm4GcmEncrypt(byte[] key, byte[] plaintext, byte[] aad) {
    try {
        byte[] nonce = new byte[12];
        new SecureRandom().nextBytes(nonce);   // 每次加密生成新 Nonce
        
        Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC");
        SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce);   // 128 位 tag
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
        if (aad != null) cipher.updateAAD(aad);
        
        byte[] ciphertext = cipher.doFinal(plaintext);
        
        // 输出格式:nonce || ciphertext+tag
        byte[] out = new byte[12 + ciphertext.length];
        System.arraycopy(nonce, 0, out, 0, 12);
        System.arraycopy(ciphertext, 0, out, 12, ciphertext.length);
        return out;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

BouncyCastle 版本注意事项

  • 必须用 1.60+ 版本,1.57 及更早版本存在 SM2 密文格式问题和 C1C2C3 固定 bug;
  • 版本冲突 是 Java 国密改造最常见的报错来源------Hutool 的 SmUtil 依赖 BouncyCastle,如果项目里其他依赖也带 BC,会报 NoSuchMethodError: org.bouncycastle.crypto.engines.SM2Enginemvn dependency:tree 排查,统一版本

五、TLCP 与 RFC 8998:国密 TLS 的两条路线

5.1 国密 SSL 的演变:GM/T 0024 → TLCP

国密传输层安全协议经历了两个阶段:

阶段 1:GM/T 0024-2014《SSL VPN 技术规范》 ------2014 年发布的行业标准,定义了国密 SSL 协议的基本框架(协议版本号 0x0101),双证书体系,密码套件如 ECC_SM4_CBC_SM3。这个标准已在 2023 年被新版 GM/T 0024-2023 替代。

阶段 2:GB/T 38636-2020《传输层密码协议 TLCP》 ------2020 年发布的国家标准,是国密 SSL 的标准化升级版协议版本号仍为 0x0101 ,与 GM/T 0024 兼容。

工程上不用纠结两者的区别,都统称为"国密 SSL"或"TLCP"即可。

5.2 双证书体系:TLCP 与 TLS 的根本区别

TLCP 与 TLS 最核心的差异不是算法,而是证书模型

维度 标准 TLS TLCP(国密)
证书数量 1 张(签名+加密共用) 2 张(签名证书 + 加密证书)
密钥用途 一张密钥对承担身份认证和密钥交换 严格隔离:签名密钥不加密,加密密钥不签名
协议版本 TLS 1.2 (0x0303) / TLS 1.3 (0x0304) TLCP v1.1 (0x0101)
前向安全 TLS 1.3 原生支持 静态 ECC-SM2 不支持,ECDHE-SM2 支持
浏览器支持 全部主流浏览器 仅国产浏览器(奇安信、360、密信等)
算法 RSA/ECDSA/AES/SHA SM2/SM4/SM3

为什么拆双证书? 主要是安全隔离 + 合规要求

  • 签名证书 :证明"我是谁",其私钥用于生成签名(如 SM2-with-SM3 签名),私钥必须不出卡(HSM/UKey 中保护);
  • 加密证书 :用于密钥交换,其私钥用于解密临时密钥,可以在服务器上保护级别稍低
  • 即使加密私钥因长期使用泄露,攻击者也无法冒充服务器(因为签名私钥独立且保护更严格);
  • 这是金融、政务强制要求------GM/T 0006、GB/T 35275 都要求签名/加密密钥用途隔离。

5.3 双证书的 Nginx 配置实战

Nginx 不原生支持 TLCP,需要用**国密版 OpenSSL(Tongsuo/铜锁、GmSSL、openHiTLS)**重新编译,或使用国密网关。

nginx 复制代码
# Tongsuo/BabaSSL 编译启用 NTLS
./config enable-ntls --prefix=/path/to/tongsuo
make -j && make install
# Nginx 配置(国密双证书 + 国际证书双栈)
server {
    listen 443 ssl;
    server_name example.com;
    
    # ============ 国际证书(TLS 1.2/1.3 用)============
    ssl_certificate /path/to/rsa.fullchain.crt;
    ssl_certificate_key /path/to/rsa.key;
    
    # ============ 国密签名证书(TLCP 身份认证用)============
    ssl_sign_certificate /path/to/server_sign.crt;
    ssl_sign_certificate_key /path/to/server_sign.key;
    
    # ============ 国密加密证书(TLCP 密钥交换用)============
    ssl_enc_certificate /path/to/server_enc.crt;
    ssl_enc_certificate_key /path/to/server_enc.key;
    
    # ============ 协议与套件 ============
    ssl_protocols TLSv1.2 TLSv1.3 TLCPv1.1;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:TLS_AES_128_GCM_SHA256:\
                ECDHE-SM2-WITH-SM4-SM3:ECC-SM2-WITH-SM4-SM3:\
                TLCP_ECC_SM4_GCM_SM3:TLCP_ECC_SM4_CBC_SM3;
}

双栈部署的价值同一台服务器同时服务国际浏览器(走 TLS+RSA)和国密客户端(走 TLCP+SM2),这是 2026 年大多数政企系统的标准做法。

5.4 RFC 8998:把国密塞进 TLS 1.3

RFC 8998 (2021 年 3 月发布)定义了如何在标准 TLS 1.3 框架下使用国密算法,密码套件为:

text 复制代码
TLS_SM4_GCM_SM3   = { 0x13, 0xC6 }   ← IANA 分配的 TLS 1.3 套件编号
TLS_SM4_CCM_SM3   = { 0x13, 0xC7 }

RFC 8998 与 TLCP 的本质区别

  • RFC 8998 是 TLS 1.3 的国密扩展,单证书体系,客户端可以用任何支持 TLS 1.3 + SM 套件的实现(如 BoringSSL with SM patch);
  • TLCP 是国密自有协议,双证书体系,需要国密专用栈(Tongsuo/GmSSL);
  • 从合规角度密评只认 TLCP 双证书------"TLS 套国密算法"不等于 TLCP,无法满足密评要求;
  • 从兼容角度:RFC 8998 让国密可以进入国际生态,蚂蚁集团等大厂已在 BabaSSL(Tongsuo 前身)里支持。

一个值得关注的进展 :IETF 还有 SM2-MLKEM 混合后量子密钥交换草案(draft-yang-tls-hybrid-sm2-mlkem),把 SM2 和 ML-KEM768 组合作为 TLS 1.3 的 named group,为后量子时代的国密迁移做准备。

5.5 s_server / s_client 实战验证

bash 复制代码
# ============ 服务端(TLCP 双证书)============
openssl s_server -accept 127.0.0.1:4433 \
    -enc_cert test/certs/sm2/server_enc.crt \
    -enc_key test/certs/sm2/server_enc.key \
    -sign_cert test/certs/sm2/server_sign.crt \
    -sign_key test/certs/sm2/server_sign.key \
    -enable_ntls
# ============ 客户端(ECC-SM2 静态套件)============
openssl s_client -connect 127.0.0.1:4433 \
    -cipher ECC-SM2-WITH-SM4-SM3 \
    -enable_ntls -ntls
# ============ 客户端(ECDHE-SM2 前向安全套件,双向认证)============
openssl s_client -connect 127.0.0.1:4433 \
    -cipher ECDHE-SM2-WITH-SM4-SM3 \
    -sign_cert test/certs/sm2/client_sign.crt \
    -sign_key test/certs/sm2/client_sign.key \
    -enc_cert test/certs/sm2/client_enc.crt \
    -enc_key test/certs/sm2/client_enc.key \
    -enable_ntls -ntls

Wireshark 抓包识别:TLCP 的版本号是 0x0101,与国际 TLS 的 0x0303/0x0304 完全不同,抓包时一眼可分。

六、工具链对比:GmSSL vs Tongsuo vs BouncyCastle vs sm-crypto

6.1 五大主流工具链

工具链 语言 维护方 适用场景 关键特性
GmSSL 3.x C(多语言绑定) 北京大学关志团队 嵌入式、C/C++、服务器 完整支持 SM2/3/4/9/ZUC、TLCP、SKF/SDF 硬件接口
Tongsuo(铜锁) C OpenAtom 基金会 OpenSSL 替换、Nginx、TLS OpenSSL 兼容、TLCP 原生支持、RFC 8998
BouncyCastle Java/.NET BC 社区 Java 后端、Android JCE Provider,需 1.60+
sm-crypto JavaScript 国内社区 浏览器、Node.js 前端加密签名
tjfoc/gmsm Go 江苏天融 Go 服务、云原生 与 Go crypto 集成良好

GmSSL 3.0 的关键改进

  • 支持 X86/ARM/RISC-V 汇编级优化(ENABLE_SM3_AVX2ENABLE_SM4_AESNI);
  • 移除不安全算法,仅保留国密和主流国际算法;
  • 支持 SKF/SDF 接口的硬件密码模块(PCI-E 密码卡、USB 密码钥匙);
  • 提供国密 Nginx、国密浏览器、自助 CA 服务等子项目。
    Tongsuo(铜锁)的关键定位
  • OpenSSL 的国内分支,由开放原子开源基金会孵化;
  • 原生支持 TLCP 协议(NTLS),是国密改造 Nginx/Apache 的首选;
  • 同时支持 RFC 8998(TLS 1.3 国密套件);
  • PostgreSQL 国密改造的官方推荐库。

6.2 OpenSSL 主线与国密支持

OpenSSL 主线从 1.1.1 开始加入了基础的 SM2/SM3/SM4 算法支持openssl sm2 命令、SM4-GCM 模式等),但不支持 TLCP 协议工程上的选择

  • 只需算法,不需协议 → OpenSSL 主线足够;
  • 需要 TLCP/双证书 → 必须用 Tongsuo 或 GmSSL;
  • 需要 RFC 8998(TLS 1.3 国密套件) → Tongsuo 或 patched BoringSSL。

6.3 工具链互通性"惨案"

同一份"国密实现"在不同库之间互通的成功率不到 50% 是工程现实。常见的互通性问题:

互通场景 常见问题 原因
sm-crypto(JS)↔ BouncyCastle(Java) 密文开头 04 头差异 JS 库不带 04 头,BC 默认带
sm-crypto ↔ GmSSL 签名格式(裸 vs DER) JS 输出裸 64 字节,GmSSL 可 DER
老版 BC ↔ 新版 BC C1C2C3 vs C1C3C2 1.60 前默认 C1C2C3
国密证书在不同 CA 间 OID 不一致 部分证书用错误的 SM2 OID
SM2 签名 ↔ 标准库 Z 值计算差异 自实现漏算 Z 值或 ID 不一致
GmSSL ↔ Tongsuo TLCP 套件编号差异 0xE0 开头的国密套件编号约定

一个真实案例 :某政务系统用 Node.js 的 sm-crypto 做签名,Java 后端用 BouncyCastle 验签,签名一直失败。排查发现是 sm-crypto 默认 ID 是空字符串 ,而 Java 端默认 ID 是 "1234567812345678"------两端 ID 不一致导致 Z 值不同,签名永远对不上 。修正方案:在 API 文档中明确约定 ID 必须为 "1234567812345678",并强制两端显式传入。

另一案例 :国密 HTTPS 网关访问失败,抓包发现服务器只发了签名证书、没发加密证书。TLCP 双证书必须两张一起发,单发一张客户端会报 "no certificate"------这是初学者最常见的坑。

6.4 接口加密的典型场景(政务/金融 API)

国密接口加密的标准组合是 SM2 + SM4 + HMAC-SM3 三件套:

text 复制代码
客户端请求流程:
1. 从服务端获取 SM2 公钥
2. 客户端生成一次性 SM4 对称密钥(128 位)
3. 用 SM4 加密业务 JSON body → encryptedBody
4. 用 SM2 公钥加密 SM4 密钥 → ciphertextBlob
5. 生成 HMAC-SM3 密钥(与 SM4 密钥独立)
6. 分别计算 ciphertextBlob 和 encryptedBody 的 HMAC-SM3
7. 用 SM2 公钥加密 HMAC 密钥 → encryptedHashKey
8. 发送 { ciphertextBlob, encryptedBody, encryptedHashKey, 
          ciphertextBlobHash, encryptedBodyHash }
服务端响应流程:
1. 用 SM2 私钥解密 SM4 密钥和 HMAC 密钥
2. 验证 HMAC → 防篡改
3. 用 SM4 解密 body
4. 响应也用 SM4 加密 + HMAC-SM3 校验

这个组合的安全意义

  • SM2 解决"对称密钥怎么安全传给对方"(非对称封装);
  • SM4 解决"业务数据怎么高效加密"(对称加密);
  • HMAC-SM3 解决"报文是否被篡改"(完整性);
  • 这是典型的"混合加密"模式,与国际场景的 RSA+AES-GCM 思路完全一致。

七、密评合规:从整改单到落地

7.1 密评的核心检查项

依据 GB/T 39786-2021,密评从物理和环境安全、网络和通信安全、设备和计算安全、应用和数据安全 四个层面检查密码应用。对大多数业务系统,重点在后面三层

层面 检查项 常见不合规
网络和通信 传输通道是否用商用密码(TLCP/TLS+SM) 用 HTTP 或标准 TLS 被判不合规
设备和计算 系统登录、远程管理是否用国密 SSH 密码登录、RDP 明文
应用和数据 应用层加密、签名、存储加密是否用国密 用 MD5/SHA-1/AES 而非 SM 算法
密钥管理 密钥生成/存储/使用是否符合规范 密钥硬编码、密钥无 KMS 管理
"高风险不合规项" (如使用已破解算法、密钥硬编码)会导致直接不通过,需立即整改;**"中风险"**允许整改期内完成。

7.2 密评整改的"三步走"实战

Step 1:差距分析

用密评机构的差距分析工具,对现有系统逐项打分。典型差距项

  • 传输层用 HTTP/标准 TLS(无国密);
  • 数据库字段加密用 AES 而非 SM4;
  • 摘要用 MD5/SHA-256 而非 SM3;
  • 签名用 RSA 而非 SM2;
  • 密钥存于配置文件(未用 KMS/HSM)。
    Step 2:算法替换与协议升级
    按"传输 → 存储 → 摘要 → 签名 "的顺序逐步替换。传输层改造是工作量最大的
  • 公网 HTTPS → 部署国密 Nginx(Tongsuo)+ 双证书;
  • 内部服务间通信 → 改造为 mTLS + SM2 双向认证;
  • 数据库 TLS → PostgreSQL/MySQL 用国密 TLS 插件(如 Tongsuo 编译版)。
    Step 3:密钥管理升级
    密钥不能硬编码,必须用密码机(HSM)+ KMS
  • SM2 私钥(签名)→ 不出卡,在 HSM/密码卡中生成和使用;
  • SM4 密钥(数据加密)→ KMS 统一管理,按信封加密模式分发;
  • 国密专用密码机(三未信安、江南天安、卫士通等厂商)提供 SKF/SDF/PKCS#11 接口。

7.3 与硬件密码机的集成

密码卡/密码机(HSM)的接口是国密落地的最后一公里,三种主流接口:

接口 全称 典型设备
SDF 设备管理接口 PCI-E 密码卡、服务器密码机
SKF 智能密码钥匙接口 USB-Key、TF 卡
PKCS#11 通用密码令牌接口 跨平台通用
GmSSL 集成示例
bash 复制代码
# 查询密码卡信息
gmssl sdfinfo -lib /path/to/sdf/library.so
# 用密码卡里的密钥做签名(私钥不离开 HSM)
gmssl sdfsign -lib /path/to/sdf/library.so -key 1 -pass password \
    -in message.txt -out signature.bin
# 用密码卡做 SM3 摘要
gmssl sdfdigest -lib /path/to/sdf/library.so -key 1 -in data.txt
# 用密码卡做混合加密
gmssl sdfencrypt -lib /path/to/sdf/library.so ...

工程上的关键原则

  • 签名私钥必须不出卡------HSM 内部生成,外部只能拿到公钥和签名结果;
  • 加密私钥可以放服务器软件层(TLCP 双证书的设计本意);
  • 对称密钥通过 KMS 的信封加密模式分发,每次业务加密生成 DEK、KMS 加密 DEK 后立即从内存擦除。

八、FAQ:最常见的 5 个问题

Q1:SM2 和 RSA 到底选哪个?如果项目没有强制国密要求,还要用 SM2 吗?

合规强制是第一决定因素 。如果项目涉及政务、金融、关键信息基础设施、等保三级及以上系统,必须用 SM2 (密评硬性要求)。如果没有合规强制,新系统用 ECDSA P-256 或 Ed25519 更合适 ------生态成熟、库支持广泛、性能与 SM2 相当。SM2 的安全性与 ECDSA P-256 等价 ,技术上并不更"安全"。

Q2:SM4-GCM 和 SM4-CBC 该怎么选?CBC 是不是必须外挂 HMAC?

新系统一律 SM4-GCM 。GCM 是 AEAD 模式,加密+认证一体,与 AES-GCM 的安全模型完全一致。如果必须用 CBC (如某些老协议强制),必须外挂 HMAC-SM3(Encrypt-then-MAC 模式),否则就是 CBC Padding Oracle 攻击的活靶子------国密和 AES 在模式选择上的原理完全一样

Q3:为什么我做的 SM2 签名,和另一端验签总是失败?

三个最可能的原因(按概率排序):

  1. ID 不一致------签名端用 "1234567812345678",验签端用空字符串或另一个值,Z 值不同,签名永远对不上;
  2. 签名格式不一致------一方输出裸 R||S(64 字节),另一方期待 DER 编码(70-72 字节),格式解析失败;
  3. Z 值实现不完整 ------自实现漏算 Z 值,直接 SM3(M) 签名,与标准 SM2 不兼容。
    排查方法 :用 GmSSL 命令行做基准------gmssl sm2sign -id 1234567812345678 生成签名,再用你的实现去验这个签名;如果 GmSSL 签名你能验、你签的 GmSSL 不能验,说明你的签名端有问题;反之亦然。

Q4:SM2 和国密证书能进系统信任库吗?

国密证书需要国密信任锚 。Chrome/Firefox/Edge 不支持国密证书 (会报"未知 CA"),只有国产浏览器 (奇安信、360 安全浏览器、密信、GmSSL 浏览器)内置国密根证书。工程方案

  • 内部系统 → 把国密根证书导入企业内部 trust store;
  • 对公网服务 → 部署国密网关 + 双栈(国密浏览器走国密,国际浏览器走 TLS+RSA);
  • 证书透明度 → 国密证书也建议加入 CT 日志,便于监控。

Q5:怎么检测我的系统是否符合密评要求?

工具化检查:

  • 协议扫描testssl.sh 加国密支持版本,检查 TLCP/国密套件协商;
  • 代码审计 :全局搜索 MD5SHA1AES/ECBRSA 等关键词,定位所有国际算法使用点;
  • 密钥审计:检查所有硬编码密钥、密钥存储位置、是否用了 KMS/HSM;
  • 工具库审计mvn dependency:tree(Java)排查 BouncyCastle 版本;
  • 对接官方密评机构做差距分析(如中国信息安全测评中心授权的密评机构)。

九、结语:国密的本质,是自主可控的密码工程

写到这里,你可能已经看到本文反复出现的主题------

国密算法本身不是"更安全"的算法,而是"自主可控"的算法 。SM2 的安全强度与 ECDSA P-256 等价,SM4 的安全强度与 AES-128 等价,SM3 的安全强度与 SHA-256 等价------从纯密码学角度,国密并没有超越国际算法。它的真正价值在于:

  1. 合规强制------《密码法》、密评、等保 2.0 把国密变成政务、金融、关键信息基础设施的硬性要求;
  2. 供应链自主------不依赖美国 NIST 算法标准,不受国外技术规则制约;
  3. 生态完整------从算法、协议、硬件、CA 到密评体系形成完整闭环。

但这套生态的工程成熟度与国际生态仍有差距 :软实现 SM4 比硬加速 AES 慢一个数量级、不同工具链互通性问题频发、TLCP 的浏览器支持仅限国产浏览器、文档和示例远不及 OpenSSL/RFC 体系丰富。国密改造的本质是"在合规约束下,把一套相对年轻的生态跑通" ------这需要的是扎实的工程能力,而不是对"国密更安全"的迷思。

密码学工程的第一铁律不要相信"用了国密就合规" 。算法选对了,还要看密文顺序对不对、Z 值算没算、曲线参数对不对、双证书发全没发、密钥管没管好------每一处细节的偏差,都可能让密评不通过,也可能让两套"都符合国密"的系统无法互通。

国密的工程难度,不在算法,在细节。

相关推荐
aiot189189352181 小时前
核芯物联蓝牙AOA高精度定位生态合作案例分享金库定位踩坑实录
大数据·运维·网络·人工智能·蓝牙aoa
风合星语2 小时前
2026 机器人行业观察(一):现在入局机器人还来得及吗?——机会、门槛与技术人的切入点
网络·人工智能·机器人
jimmyleeee2 小时前
大模型安全之十一:拆解现代 AI 系统的“骨架”:一份安全架构的完整蓝图
人工智能·安全·安全架构
zander2582 小时前
LeetCode 128. 最长连续序列
数据结构·算法
linx2952 小时前
单元四 · 对称认知·上:内存与指针
c语言·开发语言·数据结构·嵌入式硬件·算法
m0_459872922 小时前
网络基础
网络
Eason_LYC2 小时前
你天天用的AI工具,藏着无需登录的高危后门 CVE-2025-3248
网络安全·渗透测试·漏洞复现·白帽子·langflow·远程代码执行·cve-2025-3248
奋进的电子工程师2 小时前
助力汽车软件出海合规,构筑数字世界的软件安全基座
网络·安全·汽车
隐擎fox2 小时前
跨越传输层防线:深入 TCP/IP 协议栈指纹(p0f)原理与 Python 原始套接字检测实战
python·网络协议·tcp/ip·网络安全·dns