我们一个政务审批系统做国密改造,把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个坑能帮到正在做或准备做国密改造的同学。如果踩过其他坑,欢迎在评论区补充。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐