政务系统国密改造踩坑实录:SM2替换RSA后签名性能降了70%,怎么破?
国密改造第一条铁律:所有密码运算必须走硬件加密机(HSM),不要依赖CPU软件计算。HSM的SM2签名性能不仅不比RSA差,甚至更快。国密证书必须申请两张,签名证书和加密证书分开。且Web服务器必须支持GMTLS协议(不是普通的TLS)。Nginx/Tengine需要编译国密模块。国密改造不只是换算法,还要检查加密模式是否合规。ECB模式在等保测评中会被直接打回,建议统一用SM4-GCM模式(加密
我们一个政务审批系统做国密改造,把RSA-2048全部换成SM2,上线第一天签名验签接口延迟从20ms飙升到340ms,直接把领导搞懵了。后来花了两个月,踩了一堆坑才搞定。这篇文章把7个最痛的坑整理出来,每个坑都附解决方案,做国密改造的可以直接抄。
背景:为什么要做国密改造?
2020年1月1日《中华人民共和国密码法》正式施行,其中明确要求:
关键信息基础设施和等保三级以上系统,应当使用商用密码进行保护。
2022年发布的《“十四五"数字经济发展规划》进一步要求政务系统"三同步”——密码保障系统与主体工程同步规划、同步建设、同步运行。
对我们来说,这不是选择题。系统要过等保三级 + 密评,就必须做国密改造。
改造范围:
- 数字签名:RSA-2048 → SM2
- 数据加密:AES-256 → SM4
- 哈希摘要:SHA-256 → SM3
- SSL/TLS证书:国际证书 → 国密双证书
- 硬件加密设备:国际HSM → 国密认证加密机
说起来简单,做起来全是坑。
坑1:SM2签名性能断崖式下降
现象
替换签名算法后,性能测试数据:
| 算法 | 签名耗时 | 验签耗时 | 吞吐量(TPS) |
|---|---|---|---|
| RSA-2048 | 1.2ms | 0.05ms | ~800 |
| SM2(软件实现) | 8.5ms | 3.2ms | ~120 |
| SM2(HSM硬件加速) | 0.3ms | 0.08ms | ~3000 |

SM2软件签名比RSA慢了7倍,这就是我们上线第一天延迟飙升的原因。
根因
SM2基于椭圆曲线密码学,签名运算涉及大量的椭圆曲线点乘运算,纯CPU软件计算确实比RSA慢。但RSA验签非常快(利用公钥指数小),而SM2的验签也要做椭圆曲线运算,所以SM2的验签也比RSA慢很多。
解决方案
方案一:硬件加密机加速(推荐)
// ❌ 软件实现SM2签名
SM2 sm2 = new SM2(privateKey);
byte[] signature = sm2.sign(data); // 8.5ms,太慢
// ✅ 通过HSM硬件加速签名
HSMClient hsm = HSMClient.connect("hsm://10.0.1.200:9000");
SM2SignRequest req = new SM2SignRequest()
.setKeyAlias("sign_key_001")
.setData(data)
.setHashAlgorithm("SM3");
byte[] signature = hsm.sign(req); // 0.3ms,快28倍
方案二:签名结果缓存
对不要求实时签名的场景(如批量文件签名),可以预签名后缓存:
// 对审批文件预签名,缓存签名结果
@Cacheable(value = "docSignature", key = "#docId", unless = "#result == null")
public byte[] getDocumentSignature(String docId) {
Document doc = documentRepository.findById(docId);
return hsmService.sm2Sign(doc.getContentHash());
}
方案三:异步签名
// 将签名操作丢到线程池异步执行
@Async("signExecutor")
public CompletableFuture<byte[]> signAsync(byte[] data) {
return CompletableFuture.completedFuture(hsmService.sm2Sign(data));
}
经验总结
国密改造第一条铁律:所有密码运算必须走硬件加密机(HSM),不要依赖CPU软件计算。 HSM的SM2签名性能不仅不比RSA差,甚至更快。
坑2:国密双证书体系搞混了
现象
国密SSL/TLS和传统的RSA证书不一样,它用的是双证书体系——签名证书 + 加密证书。我们一开始只申请了一张证书,结果客户端怎么都握手不成功。
国密双证书 vs 国际单证书
| 对比项 | 国际证书(RSA) | 国密证书(SM2) |
|---|---|---|
| 证书数量 | 1张 | 2张(签名证书 + 加密证书) |
| 签名证书 | 同一张证书既签名又加密 | 仅用于数字签名 |
| 加密证书 | — | 仅用于密钥交换/数据加密 |
| 密钥对 | 1对 | 2对(签名密钥对 + 加密密钥对) |
| 协议 | TLS 1.2/1.3 | GMT 0024 / TLCP |

双证书配置示例(Nginx)
# 国密双证书配置
server {
listen 443 ssl;
server_name approval.gov.cn;
# 签名证书(用于签名验证)
ssl_certificate /etc/ssl/gm/sign.crt;
ssl_certificate_key /etc/ssl/gm/sign.key;
# 加密证书(用于密钥交换)
ssl_enc_certificate /etc/ssl/gm/enc.crt;
ssl_enc_certificate_key /etc/ssl/gm/enc.key;
# 强制使用国密套件
ssl_protocols GMTLS;
ssl_ciphers ECC-SM2-SM4-GCM-SM3:ECC-SM2-SM4-CBC-SM3;
}
经验总结
国密证书必须申请两张,签名证书和加密证书分开。 且Web服务器必须支持GMTLS协议(不是普通的TLS)。Nginx/Tengine需要编译国密模块。
坑3:SM4 ECB模式存在安全隐患
现象
改造时发现,原有系统用的AES-256 ECB模式直接替换成SM4 ECB模式后,等保测评不通过——测评师指出ECB模式无法隐藏数据模式,相同明文会产生相同密文。
ECB vs GCM/CBC 模式对比
| 模式 | 安全性 | 是否需要IV | 并行加密 | 适用场景 |
|---|---|---|---|---|
| ECB | ❌ 不安全 | 不需要 | ✅ | ❌ 不推荐使用 |
| CBC | ✅ 安全 | 需要 | ❌ | 传统加密场景 |
| GCM | ✅ 安全 | 需要 | ✅ | 现代推荐方案(自带认证) |
| CTR | ✅ 安全 | 需要 | ✅ | 流式加密 |
SM4-GCM加密实现
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import javax.crypto.SecretKey;
import javax.crypto.KeyGenerator;
import java.security.SecureRandom;
public class SM4GCMUtil {
// SM4密钥长度:128位 = 16字节
private static final int KEY_SIZE = 128;
// GCM认证标签长度:128位
private static final int GCM_TAG_LENGTH = 128;
// IV长度:96位(推荐)
private static final int IV_LENGTH = 12;
/**
* SM4-GCM加密
* @param plaintext 明文
* @param key SM4密钥(16字节)
* @return IV + 密文 + 认证标签
*/
public static byte[] encrypt(byte[] plaintext, byte[] key) throws Exception {
// 生成随机IV
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
GCMBlockCipher gcm = new GCMBlockCipher(new SM4Engine());
AEADParameters params = new AEADParameters(
new KeyParameter(key),
GCM_TAG_LENGTH,
iv,
null // 无附加认证数据
);
gcm.init(true, params);
byte[] ciphertext = new byte[gcm.getOutputSize(plaintext.length)];
int offset = gcm.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
gcm.doFinal(ciphertext, offset);
// 返回 IV + 密文(GCM已包含认证标签)
byte[] result = new byte[IV_LENGTH + ciphertext.length];
System.arraycopy(iv, 0, result, 0, IV_LENGTH);
System.arraycopy(ciphertext, 0, result, IV_LENGTH, ciphertext.length);
return result;
}
/**
* SM4-GCM解密
*/
public static byte[] decrypt(byte[] encrypted, byte[] key) throws Exception {
// 提取IV
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(encrypted, 0, iv, 0, IV_LENGTH);
// 提取密文
byte[] ciphertext = new byte[encrypted.length - IV_LENGTH];
System.arraycopy(encrypted, IV_LENGTH, ciphertext, 0, ciphertext.length);
GCMBlockCipher gcm = new GCMBlockCipher(new SM4Engine());
AEADParameters params = new AEADParameters(
new KeyParameter(key),
GCM_TAG_LENGTH,
iv,
null
);
gcm.init(false, params);
byte[] plaintext = new byte[gcm.getOutputSize(ciphertext.length)];
int offset = gcm.processBytes(ciphertext, 0, ciphertext.length, plaintext, 0);
gcm.doFinal(plaintext, offset);
return plaintext;
}
}
经验总结
国密改造不只是换算法,还要检查加密模式是否合规。 ECB模式在等保测评中会被直接打回,建议统一用SM4-GCM模式(加密+认证一步到位)。
坑4:密钥管理架构没想清楚就动手
现象
改造初期,我们在每个业务系统里都单独管理SM2密钥对和SM4密钥。结果:
- 50个系统 = 50套密钥管理逻辑
- 密钥轮换需要逐个系统登录后台手动操作
- 密钥的生成/存储/备份策略各不相同
- 安全审计时拿不出完整的密钥使用报告
正确的密钥管理架构

┌─────────────────────────────────────────────────────┐
│ 密钥管理平台(KSP) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 密钥生成 │ │ 密钥存储 │ │ 密钥生命周期管理 │ │
│ │ (HSM生成) │ │ (HSM保护) │ │ (轮换/归档/销毁) │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 策略引擎 │ │ 审计引擎 │ │ 多租户隔离 │ │
│ │ (RBAC) │ │ (SYSLOG) │ │ (租户密钥隔离) │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└──────────────────────┬──────────────────────────────┘
│ 统一API / SDK
┌───────────┼───────────┐
│ │ │
┌──────┴───┐ ┌────┴────┐ ┌───┴──────┐
│ 审批系统 │ │ 发票系统 │ │ 证照系统 │
└──────────┘ └─────────┘ └──────────┘
核心原则:
- 所有密钥由HSM统一生成,不从业务系统本地生成
- 根密钥永远不出HSM,业务系统只能通过API做密码运算
- 密钥生命周期由平台统一管理,业务系统不感知轮换过程
通过API调用密钥运算
// ❌ 错误:业务系统本地生成密钥
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair keyPair = kpg.generateKeyPair(); // 密钥在应用内存中,可以被dump
// ✅ 正确:通过密钥管理平台API做签名
KspClient ksp = KspClient.connect("https://ksp.internal:8443");
KspSignRequest req = new KspSignRequest()
.setKeyId("sm2_sign_key_001") // 密钥在HSM中,私钥永不出加密机
.setAlgorithm("SM2")
.setHashAlgorithm("SM3")
.setData(dataToSign);
KspSignResponse resp = ksp.sign(req);
byte[] signature = resp.getSignature();
经验总结
国密改造不是"把算法从RSA换成SM2"就完事了,密钥管理架构才是底层支撑。 没有统一的密钥管理平台,改造得越多后续运维越痛苦。
坑5:PKI证书链信任问题
现象
国密CA签发的证书,在浏览器中显示"不安全",因为根证书没有被信任。
国密PKI证书链结构
国密根CA证书(自签名)
└── 中间CA证书(签名证书签发)
└── 终端实体证书(服务器证书)
与RSA证书链的差异:
- 国密证书使用SM2作为签名算法,SM3作为摘要算法
- 根证书必须是自签名的SM2证书
- 浏览器默认不信任自建CA,需要手动导入根证书
解决方案
服务端:配置完整证书链
# 将证书链合并为一个文件(顺序:服务器证书 → 中间CA证书)
# cat server.crt intermediate-ca.crt > fullchain.crt
ssl_certificate /etc/ssl/gm/fullchain.crt;
客户端/浏览器:导入根证书
# Linux系统信任根证书
cp gm_root_ca.crt /usr/local/share/ca-certificates/
update-ca-certificates
# Java应用信任根证书
keytool -importcert -alias gm-root-ca \
-file gm_root_ca.crt \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit
经验总结
国密证书链部署时,一定要配置完整的证书链(服务器证书+中间CA),并确保所有客户端都导入了根证书。 否则就是疯狂踩"证书不受信"的坑。
坑6:等保测评/密评时拿不出审计日志
现象
密评时测评师要求查看"近6个月的密钥使用审计记录",我们拿不出——因为改造时只关注了功能实现,没有做审计日志。
密评审计要求(GM/T 0051)
| 审计项 | 具体要求 |
|---|---|
| 密钥生成 | 记录生成时间、算法、密钥长度、操作人 |
| 密钥使用 | 记录每次加密/解密/签名/验签的时间、密钥ID、操作人 |
| 密钥轮换 | 记录旧密钥归档时间、新密钥激活时间 |
| 密钥销毁 | 记录销毁时间、销毁方式(安全擦除/物理销毁) |
| 异常操作 | 记录所有失败的密码运算、越权访问 |
通过密钥管理平台实现自动审计
# 密钥管理平台审计配置
audit:
enabled: true
log_level: ALL # 记录所有密码运算
output:
- type: SYSLOG
host: 10.0.1.50
port: 514
protocol: TCP
format: CEF # Common Event Format,对接SIEM
- type: FILE
path: /var/log/ksp/audit.log
rotation:
max_size: 500MB
max_files: 30 # 保留30个文件
compress: true
alert_rules:
- event: KEY_ACCESS_DENIED
level: P0 # 立即告警
- event: KEY_EXPORT_ATTEMPT # 尝试导出密钥
level: P0
- event: SIGNATURE_VERIFY_FAILED
level: P2 # 记录即可
经验总结
密评拿不出审计日志 = 改造白做。 审计能力必须在架构设计阶段就规划好,不是事后补的。
坑7:新旧系统并行期间的兼容性问题
现象
改造采用"新老并行"策略,但有些请求走到新系统(SM2),有些还是老系统(RSA),导致签名验证失败——因为两个系统互相不认识对方的签名。
过渡期兼容方案
方案一:双算法签名(推荐过渡方案)
// 过渡期:同时用RSA和SM2签名,验证方按需选择
public class DualAlgorithmSigner {
public SignatureResult sign(byte[] data) {
byte[] rsaSignature = rsaSigner.sign(data);
byte[] sm2Signature = sm2Signer.sign(data);
return new SignatureResult()
.setRsaSignature(rsaSignature)
.setSm2Signature(sm2Signature)
.setSm2Preferred(true); // 标记优先使用SM2
}
}
// 验签方:优先尝试SM2,失败后回退到RSA
public boolean verify(byte[] data, SignatureResult sig) {
if (sig.isSm2Preferred()) {
try {
return sm2Verifier.verify(data, sig.getSm2Signature());
} catch (Exception e) {
log.warn("SM2验签失败,回退到RSA", e);
}
}
return rsaVerifier.verify(data, sig.getRsaSignature());
}
方案二:网关层协议转换
┌──────────┐ RSA请求 ┌──────────────┐ SM2请求 ┌──────────┐
│ 老系统 │ ──────────────→ │ 协议转换网关 │ ──────────────→ │ 新系统 │
│ (RSA) │ ←────────────── │ (自动适配) │ ←────────────── │ (SM2) │
└──────────┘ RSA响应 └──────────────┘ SM2响应 └──────────┘
方案三:分阶段灰度切换
第1周:内部测试系统100%切换SM2
第2-3周:5%生产流量切换SM2,95%走RSA
第4-5周:30%流量走SM2
第6-7周:100%流量走SM2
第8周:下线RSA签名逻辑
经验总结
不要搞"大爆炸"式的切换。 国密改造建议采用"灰度切换 + 双算法并行"的过渡策略,给充分的时间发现和修复兼容性问题。
国密改造项目清单
最后附一份通用的改造清单,方便大家对照:
| 序号 | 改造项 | 关键要点 |
|---|---|---|
| 1 | 密码算法替换 | RSA→SM2, AES→SM4, SHA→SM3 |
| 2 | SSL/TLS证书 | 申请国密双证书(签名+加密),部署GMTLS |
| 3 | 硬件加密设备 | 部署商密认证的HSM,所有密码运算走HSM |
| 4 | 密钥管理平台 | 统一密钥生成/存储/轮换/审计 |
| 5 | PKI证书体系 | 根CA/中间CA/终端证书链完整部署 |
| 6 | 审计日志 | 所有密码运算可追溯,对接SYSLOG/SIEM |
| 7 | 应用改造 | SDK替换、签名逻辑修改、加密模式切换 |
| 8 | 性能测试 | 确保HSM加速后性能达标 |
| 9 | 兼容性测试 | 新老系统并行期间的互操作性 |
| 10 | 等保/密评 | 按GM/T 0051逐项对照自查 |
写在最后
国密改造这件事,技术难度其实不算特别高,真正难的是全局规划——密钥管理架构、证书体系、审计能力、性能保障、新旧兼容,这些才是决定项目成败的关键。
如果让我重新来一次,我会先把密钥管理平台和HSM部署好,再逐个系统做算法替换。基础打好,后面就是体力活;基础没打好,后面全是救火。
希望这7个坑能帮到正在做或准备做国密改造的同学。如果踩过其他坑,欢迎在评论区补充。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)