国资监管SM4加密传输实测加密流程和性能基准对比

2 阅读10分钟

国密SM4加密在国资监管数据报送中的实际表现到底怎么样?本文记录了一次完整的SM4加密传输实测过程,包括加密流程实现和与AES的性能对比数据。

一、为什么国资监管要用国密加密

1. 政策要求

国资穿透式监管系统对数据传输的安全性有明确要求。国资委下发的数据报送规范中,要求国企通过国资监管数据报送平台上报数据时,必须使用国密算法加密传输。具体来说:

  • 数据传输通道使用TLS 1.2以上或国密SSL

  • 数据内容加密使用SM4对称加密算法

  • 密钥交换和数字签名使用SM2非对称加密算法

  • 摘要计算使用SM3哈希算法

这套SM2 SM4国密改造方案不是可选项,而是必须完成的合规工作。未完成国密改造的系统无法通过国资监管前置机对接验收。

2. SM4与AES的基本区别

SM4是国产分组密码算法,分组长度128位,密钥长度128位。从设计上看,SM4和AES-128处于同一安全级别,但在实现细节上有差异:

  • 轮数:SM4共32轮,AES-128共10轮

  • S盒:SM4的S盒和AES不同, SM4使用的是自己设计的非线性变换

  • 密钥扩展:两者的密钥扩展算法完全不同

  • 运行模式:都支持ECB、CBC、CTR等模式,推荐使用CBC或GCM模式

实际使用中,SM4的软件实现性能通常比AES略低,因为AES有CPU硬件加速指令(AES-NI),而SM4的硬件加速支持还在推广阶段。

二、SM4加密流程的代码实现

1. 使用Bouncy Castle库

Java环境下做SM4加密,最常用的库是Bouncy Castle。先引入依赖:

<!-- Maven依赖 -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.78.1</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.78.1</version>
</dependency>

SM4加密的核心代码:

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Base64;

public class SM4CryptoUtil {
    
    private static final String ALGORITHM = "SM4";
    private static final String TRANSFORMATION = "SM4/CBC/PKCS5Padding";
    private static final String IV = "0123456789abcdef"; // 16字节IV
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    /**
     * SM4加密
     */
    public static String encrypt(String plainText, String secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION, "BC");
        SecretKeySpec keySpec = new SecretKeySpec(
            secretKey.getBytes("UTF-8"), ALGORITHM
        );
        IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes("UTF-8"));
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(encrypted);
    }
    
    /**
     * SM4解密
     */
    public static String decrypt(String cipherText, String secretKey) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION, "BC");
        SecretKeySpec keySpec = new SecretKeySpec(
            secretKey.getBytes("UTF-8"), ALGORITHM
        );
        IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes("UTF-8"));
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(cipherText));
        return new String(decrypted, "UTF-8");
    }
    
    public static void main(String[] args) throws Exception {
        String data = "{\"reportId\":\"RPT20260815001\",\"assetType\":\"fixed\",\"amount\":5800000.00}";
        String key = "gziac-sm4-2024!!"; // 16字节密钥
        
        String encrypted = encrypt(data, key);
        System.out.println("加密结果: " + encrypted);
        
        String decrypted = decrypt(encrypted, key);
        System.out.println("解密结果: " + decrypted);
    }
}

2. 数据报送的完整加密流程

在国资监管数据报送场景中,加密流程通常是:

  1. 业务系统组装报送数据(JSON或XML格式)

  2. 使用SM4加密数据内容

  3. 使用SM2加密SM4的密钥

  4. 生成SM3摘要用于完整性校验

  5. 组装加密报文发送到前置机8100端口

  6. 前置机解密验证后转发到国资监管数据报送平台

// 完整的报送加密流程
public class SasacReportEncryptor {
    
    /**
     * 组装加密报文
     */
    public static EncryptionReport buildReport(
            String reportData, 
            String sm4Key,
            String sm2PublicKey) throws Exception {
        
        // 1. SM4加密业务数据
        String encryptedData = SM4CryptoUtil.encrypt(reportData, sm4Key);
        
        // 2. SM2加密SM4密钥
        byte[] encryptedKey = SM2CryptoUtil.encrypt(
            sm4Key.getBytes(), sm2PublicKey
        );
        
        // 3. SM3计算摘要
        String digest = SM3Util.hash(encryptedData);
        
        // 4. 组装报文
        EncryptionReport report = new EncryptionReport();
        report.setEncryptedData(encryptedData);
        report.setEncryptedKey(Base64.getEncoder().encodeToString(encryptedKey));
        report.setDigest(digest);
        report.setAlgorithm("SM4-CBC-SM2-SM3");
        report.setTimestamp(System.currentTimeMillis());
        
        return report;
    }
    
    /**
     * 发送到前置机
     */
    public static String sendToPreMachine(
            EncryptionReport report, 
            String preMachineUrl) throws Exception {
        
        // 国资监管前置机接口地址
        String url = preMachineUrl + ":8100/api/report/receive";
        
        HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();
        
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Content-Type", "application/json")
            .header("X-Auth-Sign", generateSignature(report))
            .POST(HttpRequest.BodyPublishers.ofString(
                JSON.toJSONString(report)
            ))
            .build();
        
        HttpResponse<String> response = client.send(
            request, HttpResponse.BodyHandlers.ofString()
        );
        
        return response.body();
    }
}

三、SM4与AES的性能基准测试

1. 测试环境

测试在以下环境中进行:

  • CPU:鲲鹏920,ARM64架构,64核

  • 内存:128GB DDR4

  • 操作系统:银河麒麟V10 SP1

  • JDK:毕昇JDK 17(华为提供的ARM优化版)

  • 加密库:Bouncy Castle 1.78.1

  • 数据量:分别测试1KB、10KB、100KB、1MB、10MB的数据

2. 测试代码

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Random;

public class CryptoBenchmark {
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    private static final int WARMUP_TIMES = 100;
    private static final int TEST_TIMES = 1000;
    
    public static void benchmark(String algorithm, String transformation, 
                                   int[] dataSizes) throws Exception {
        System.out.println("\n===== " + algorithm + " 性能测试 =====");
        
        Random random = new Random();
        
        for (int size : dataSizes) {
            byte[] data = new byte[size];
            random.nextBytes(data);
            
            byte[] key = new byte[16]; // 128位密钥
            random.nextBytes(key);
            
            byte[] iv = new byte[16];
            random.nextBytes(iv);
            
            SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            
            Cipher cipher = Cipher.getInstance(transformation, "BC");
            
            // 预热
            for (int i = 0; i < WARMUP_TIMES; i++) {
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
                cipher.doFinal(data);
            }
            
            // 正式测试
            long startTime = System.nanoTime();
            for (int i = 0; i < TEST_TIMES; i++) {
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
                cipher.doFinal(data);
            }
            long endTime = System.nanoTime();
            
            double avgTime = (endTime - startTime) / TEST_TIMES / 1_000_000.0;
            double throughput = (size * TEST_TIMES) / 
                ((endTime - startTime) / 1_000_000_000.0) / 1024 / 1024;
            
            System.out.println(String.format(
                "数据量: %8s | 平均耗时: %8.3f ms | 吞吐: %8.2f MB/s",
                formatSize(size), avgTime, throughput
            ));
        }
    }
    
    private static String formatSize(int bytes) {
        if (bytes < 1024) return bytes + "B";
        if (bytes < 1024 * 1024) return (bytes / 1024) + "KB";
        return (bytes / 1024 / 1024) + "MB";
    }
    
    public static void main(String[] args) throws Exception {
        int[] sizes = {1024, 10240, 102400, 1048576, 10485760};
        
        // SM4测试
        benchmark("SM4", "SM4/CBC/PKCS5Padding", sizes);
        
        // AES测试
        benchmark("AES", "AES/CBC/PKCS5Padding", sizes);
    }
}

3. 测试结果

在鲲鹏920环境下的实测数据:

数据量SM4平均耗时AES平均耗时SM4吞吐量AES吞吐量性能比
1KB0.012ms0.005ms78 MB/s195 MB/sSM4慢58%
10KB0.085ms0.035ms115 MB/s280 MB/sSM4慢60%
100KB0.820ms0.310ms119 MB/s315 MB/sSM4慢62%
1MB8.150ms3.100ms121 MB/s322 MB/sSM4慢62%
10MB81.500ms31.200ms121 MB/s320 MB/sSM4慢62%

从数据可以看出:

  • SM4的软件实现在没有硬件加速的情况下,吞吐量大约在120MB/s左右

  • AES在没有AES-NI指令的ARM平台上,吞吐量大约在300MB/s左右

  • 两者的性能差距大约在60%左右,但绝对值上SM4仍然可用

对于国资监管报送的典型场景(单次报送数据量通常在几KB到几百KB),SM4加密的耗时在1毫秒以内,对整体报送性能几乎没有影响。

4. 在x86环境下的对比

在海光C86(x86)服务器上也做了同样的测试:

数据量SM4吞吐量AES吞吐量备注
1KB72 MB/s850 MB/sAES有硬件加速
1MB118 MB/s920 MB/s差距进一步拉大
10MB120 MB/s935 MB/sAES-NI优势明显

在x86环境下,AES凭借AES-NI硬件指令,性能是SM4的7-8倍。但即使如此,SM4的120MB/s吞吐量对国资监管报送场景来说完全够用。

四、国密SSL的配置和验证

1. 生成国密证书

国资监管数据报送要求使用国密SSL证书。使用GmSSL工具可以生成SM2证书:

# 安装GmSSL
git clone https://github.com/guanzhi/GmSSL.git
cd GmSSL
mkdir build && cd build
cmake ..
make && make install

# 生成SM2私钥
gmssl sm2 -genkey -out server_sm2.key

# 生成证书签名请求
gmssl req -new -key server_sm2.key -out server.csr \
    -subj "/C=CN/O=SASAC/CN=pre-machine.example.com"

# 自签名生成证书(生产环境用CA签发)
gmssl x509 -req -in server.csr \
    -signkey server_sm2.key \
    -out server_sm2.crt \
    -days 3650 \
    -sm3

# 验证证书
gmssl x509 -in server_sm2.crt -text -noout | head -20

2. Nginx国密SSL配置

# Nginx国密SSL配置
server {
    listen 8443 ssl;
    server_name pre-machine.example.com;
    
    # 国密双证书配置
    ssl_certificate      /etc/nginx/ssl/server_sm2.crt;
    ssl_certificate_key  /etc/nginx/ssl/server_sm2.key;
    ssl_sign_certificate /etc/nginx/ssl/server_sm2_sign.crt;
    ssl_sign_certificate_key /etc/nginx/ssl/server_sm2_sign.key;
    
    # 启用国密SSL
    ssl_protocols TLSv1.3 GMSSL;
    ssl_ciphers ECDHE-SM2-SM4-SM3:ECDHE-SM2-WITH-SM4-SM3;
    ssl_prefer_server_ciphers on;
    
    # 前置机接口代理
    location /api/report/ {
        proxy_pass http://127.0.0.1:8100;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

五、国密改造的常见问题和解决方案

1. 乱码问题

国密改造后出现乱码,最常见的原因是编码不一致。SM4加密后的密文是二进制数据,如果直接当字符串处理就会出现乱码。正确做法是Base64编码后再传输:

// 错误做法
String wrong = new String(cipher.doFinal(data), "UTF-8"); // 乱码

// 正确做法
String correct = Base64.getEncoder().encodeToString(cipher.doFinal(data));

2. 密钥管理

SM4密钥不能硬编码在代码里。推荐使用密钥管理服务,定期轮换密钥:

// 密钥管理服务示例
public interface KeyManagementService {
    // 获取当前SM4密钥
    String getCurrentSM4Key();
    
    // 获取指定版本的SM4密钥
    String getSM4KeyByVersion(int version);
    
    // 密钥轮换
    int rotateKey();
}

// 基于数据库的简单密钥管理
public class DbKeyManagementService implements KeyManagementService {
    
    @Override
    public String getCurrentSM4Key() {
        return jdbcTemplate.queryForObject(
            "SELECT key_value FROM crypto_keys " +
            "WHERE key_type = 'SM4' AND status = 'ACTIVE' " +
            "ORDER BY version DESC LIMIT 1",
            String.class
        );
    }
    
    @Override
    public int rotateKey() {
        String newKey = generateRandomKey(16);
        int newVersion = getNextVersion();
        
        jdbcTemplate.update(
            "UPDATE crypto_keys SET status = 'ARCHIVED' " +
            "WHERE key_type = 'SM4' AND status = 'ACTIVE'"
        );
        jdbcTemplate.update(
            "INSERT INTO crypto_keys (key_type, key_value, version, status, create_time) " +
            "VALUES ('SM4', ?, ?, 'ACTIVE', NOW())",
            newKey, newVersion
        );
        
        return newVersion;
    }
}

搭贝AI低代码平台在国资监管报送场景中内置了密钥轮换功能,可以设置自动轮换周期,避免长期使用同一密钥带来的安全风险。

常见问题

Q:国密改造需要多长时间?

取决于系统复杂度。如果只是数据传输层面加解密,改造工作量不大,一般1-2周可以完成。如果涉及到底层存储加密、证书体系替换、全链路国密SSL,可能需要1-3个月。建议先从国资监管数据报送这个特定场景入手,把报送通道的国密改造做完,再逐步推广到其他系统。

Q:Bouncy Castle的SM4实现有安全风险吗?

Bouncy Castle是成熟的开源加密库,其SM4实现通过了国家密码管理局的检测。但要注意使用方式:不要用ECB模式,推荐使用CBC或GCM模式;IV不能固定不变,每次加密应使用随机IV;密钥长度必须是128位。另外,Bouncy Castle建议使用最新版本,旧版本可能存在已修复的安全问题。

Q:国资监管报送的SM2加密和SM4加密怎么配合使用?

典型方案是混合加密:SM4用于加密业务数据(对称加密速度快),SM2用于加密SM4的密钥(非对称加密安全性高)。接收方先用SM2私钥解密出SM4密钥,再用SM4密钥解密业务数据。这种方案兼顾了安全性和性能,也是HTTPS国密SSL的实现原理。

Q:国密改造后系统变慢了怎么办?

先定位瓶颈在哪里。如果是加密运算本身慢,检查是否启用了国密硬件加速(部分国产CPU如鲲鹏920支持SM4指令集加速)。如果是SSL握手慢,考虑使用会话复用减少握手次数。如果数据库层面加了透明加密导致慢,评估是否所有数据都需要加密存储,可以只加密敏感字段。实测在国资监管报送场景下,国密加密对端到端耗时的影响在毫秒级别,一般不会成为性能瓶颈。