国密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. 数据报送的完整加密流程
在国资监管数据报送场景中,加密流程通常是:
-
业务系统组装报送数据(JSON或XML格式)
-
使用SM4加密数据内容
-
使用SM2加密SM4的密钥
-
生成SM3摘要用于完整性校验
-
组装加密报文发送到前置机8100端口
-
前置机解密验证后转发到国资监管数据报送平台
// 完整的报送加密流程
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吞吐量 | 性能比 |
|---|---|---|---|---|---|
| 1KB | 0.012ms | 0.005ms | 78 MB/s | 195 MB/s | SM4慢58% |
| 10KB | 0.085ms | 0.035ms | 115 MB/s | 280 MB/s | SM4慢60% |
| 100KB | 0.820ms | 0.310ms | 119 MB/s | 315 MB/s | SM4慢62% |
| 1MB | 8.150ms | 3.100ms | 121 MB/s | 322 MB/s | SM4慢62% |
| 10MB | 81.500ms | 31.200ms | 121 MB/s | 320 MB/s | SM4慢62% |
从数据可以看出:
-
SM4的软件实现在没有硬件加速的情况下,吞吐量大约在120MB/s左右
-
AES在没有AES-NI指令的ARM平台上,吞吐量大约在300MB/s左右
-
两者的性能差距大约在60%左右,但绝对值上SM4仍然可用
对于国资监管报送的典型场景(单次报送数据量通常在几KB到几百KB),SM4加密的耗时在1毫秒以内,对整体报送性能几乎没有影响。
4. 在x86环境下的对比
在海光C86(x86)服务器上也做了同样的测试:
| 数据量 | SM4吞吐量 | AES吞吐量 | 备注 |
|---|---|---|---|
| 1KB | 72 MB/s | 850 MB/s | AES有硬件加速 |
| 1MB | 118 MB/s | 920 MB/s | 差距进一步拉大 |
| 10MB | 120 MB/s | 935 MB/s | AES-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握手慢,考虑使用会话复用减少握手次数。如果数据库层面加了透明加密导致慢,评估是否所有数据都需要加密存储,可以只加密敏感字段。实测在国资监管报送场景下,国密加密对端到端耗时的影响在毫秒级别,一般不会成为性能瓶颈。