# gm-java
**Repository Path**: RuanKongkong/gm-java
## Basic Information
- **Project Name**: gm-java
- **Description**: 使用java实现国密算法
- **Primary Language**: Java
- **License**: GPL-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 116
- **Forks**: 46
- **Created**: 2022-06-23
- **Last Updated**: 2026-07-17
## Categories & Tags
**Categories**: security-dev
**Tags**: None
## README
# GM-JAVA
GM-JAVA 是一套用 JAVA 开发的支持国密算法的加解密工具包。
## 运行环境
- 本项目发布产物按 Java 8 目标版本构建,`pom.xml` 已显式配置 `maven-compiler-plugin` 的 `source=8` 与 `target=8`
- 作为依赖使用时,推荐运行环境为 JDK 11 / 17 / 21
- 若需要运行在 JDK 8,请尽量使用较新的 8u 版本,不建议使用过老的 JDK 8
### JDK 8 已知说明
- 极老的 JDK 8 运行时可能在 `bcprov-jdk18on` 的 JCE Provider 验签阶段失败,典型报错为 `JCE cannot authenticate the provider BC`
- 这类问题通常不是 `gm-java` 自身算法实现错误,而是旧 JDK 8 对 Provider 签名链的兼容性不足
- 如果你的运行环境必须停留在旧 JDK 8,建议优先升级到更新的 8u 版本;若无法升级,再考虑回退相关 Bouncy Castle 版本并单独验证
## 项目引入
- pom 引入(已上传中央仓库)
```xml
io.github.kongkongruan
gm-java
3.2.6
```
- 下载源码编译之后引入,或者从 Maven 中央仓库直接下载对应版本的 `gm-java-.jar` 引入
### Netty TLS 功能(可选依赖)
从 `3.2.2` 起,`gm-java` 核心算法模块不再强制依赖 `netty`。默认作为加密工具包引入时,Maven 不会传递 `netty-all`:
```xml
io.github.kongkongruan
gm-java
3.2.6
```
如果需要使用 `NettyTlsClient` / `NettyTlsServer` 进行 TLS 握手密钥协商,请在自身项目的 `pom.xml` 中显式引入 `netty-all`:
```xml
io.netty
netty-all
5.0.0.Alpha2
```
## 主要功能
### 密码算法
- 对称密码算法 SM4(ECB/CBC/CFB/OFB/CTR/GCM)
- 非对称密码算法 SM2(加解密/签名验签)
- Hash 算法 SM3
- 消息认证码 HMAC-SM3(直接调用 / JCA `Mac`)
- 基于 SM3 实现的随机数生成器(多线程加速)
### 证书
- 证书解析以及证书 SHA1 指纹计算
- SM2 证书生成
### 密钥协商
- 模拟 TLS 握手协议,通信双方协商会话密钥
## 整体功能基准测试
### test 目录下 com.yxj.gm 包 Benchmarking 类
## 快速使用
```java
String msg = "gm-java-1.0";
```
### SM2 密钥对生成
```java
KeyPair keyPair = SM2KeyPairGenerate.generateSM2KeyPair();
```
### SM2 加解密
```java
SM2Cipher sm2Cipher = new SM2Cipher();
byte[] mi = sm2Cipher.SM2CipherEncrypt(msg.getBytes(), keyPair.getPublic().getEncoded());
byte[] ming = sm2Cipher.SM2CipherDecrypt(mi, keyPair.getPrivate().getEncoded());
System.out.println("SM2解密结果:" + new String(ming));
```
### SM2 签名验签
```java
SM2Signature signature = new SM2Signature();
byte[] signature1 = signature.signature(msg.getBytes(), null, keyPair.getPrivate().getEncoded());
boolean b = signature.verify(msg.getBytes(), null, signature1, keyPair.getPublic().getEncoded());
System.out.println("SM2验签结果:" + b);
```
#### SM2 输入与性能配置
- `SM2Signature` 的签名值使用固定 64 字节裸格式 `r || s`,私钥和公钥分别使用固定 32 字节、64 字节编码。公钥还必须是规范编码的 SM2 曲线点。验签遇到空值、错误长度、非法公钥点、越界 `r/s` 或超过 8191 字节的 ID 时返回 `false`;签名参数不合法时抛出 `IllegalArgumentException`。
- SM2 签名和验签默认启用单条目的线程本地 ZA 缓存。同一工作线程反复使用相同 ID 和公钥时可省去 ZA 重算;缓存保存调用参数的内容快照,调用方修改原数组不会复用旧结果。
- 如需禁用 ZA 缓存,在 JVM 启动时添加 `-Dgm.sm2.zaCache=false`。该参数在 `SM2Signature` 类初始化时读取。
### 制作 SM2 证书
```java
// ca 证书密钥
KeyPair caKeyPair = SM2KeyPairGenerate.generateSM2KeyPair();
// 终端证书密钥
KeyPair equipKeyPair = SM2KeyPairGenerate.generateSM2KeyPair();
SM2CertGenerator sm2CertGenerator = new SM2CertGenerator();
String DN_CA = "CN=Digicert,OU=Digicert,O=Digicert,L=Linton,ST=Utah,C=US";
String DN_CHILD = "CN=DD,OU=DD,O=DD,L=Linton,ST=Utah,C=CN";
CertTest certTest = new CertTest();
byte[] rootCert = sm2CertGenerator.generatorCert(DN_CA, 365 * 10, DN_CA, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign), true, caKeyPair.getPrivate().getEncoded(), caKeyPair.getPublic().getEncoded(), false, 0);
try {
FileUtils.writeFile("D:/certtest/java-ca-3.cer", rootCert);
} catch (Exception e) {
throw new RuntimeException(e);
}
byte[] ownerCert = sm2CertGenerator.generatorCert(DN_CA, 365, DN_CHILD, new KeyUsage(KeyUsage.digitalSignature), false, caKeyPair.getPrivate().getEncoded(), equipKeyPair.getPublic().getEncoded(), false, 0);
try {
FileUtils.writeFile("D:/certtest/java-ownerCert-3.cer", ownerCert);
} catch (Exception e) {
throw new RuntimeException(e);
}
// 使用 HSM 签名制作 SM2 证书
int hsmSigPriIndex = 0;
rootCert = sm2CertGenerator.generatorCert(DN_CA, 365 * 10, DN_CA, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign), true, caKeyPair.getPrivate().getEncoded(), caKeyPair.getPublic().getEncoded(), true, hsmSigPriIndex);
```
### SM3 摘要计算
```java
SM3Digest sm3Digest = new SM3Digest();
sm3Digest.update(msg.getBytes());
byte[] md = sm3Digest.doFinal();
byte[] md2 = sm3Digest.doFinal(msg.getBytes());
sm3Digest.update("gm-java-".getBytes());
sm3Digest.update("1.0".getBytes());
byte[] md3 = sm3Digest.doFinal();
System.out.println(Hex.toHexString(md));
System.out.println(Hex.toHexString(md2));
System.out.println(Hex.toHexString(md3));
```
### HMAC-SM3(直接调用)
```java
byte[] key = "1234567890abcdef".getBytes();
SM3HMac sm3HMac = new SM3HMac(key);
sm3HMac.update(msg.getBytes());
byte[] tag = sm3HMac.doFinal();
System.out.println(Hex.toHexString(tag));
```
### HMAC-SM3(JCA)
```java
Security.addProvider(new XaProvider());
Mac mac = Mac.getInstance("HmacSM3", "XaProvider");
SecretKeySpec keySpec = new SecretKeySpec("1234567890abcdef".getBytes(), "HmacSM3");
mac.init(keySpec);
mac.update(msg.getBytes());
byte[] tag = mac.doFinal();
System.out.println(Hex.toHexString(tag));
```
如果源码目录下直接运行 JCA `Mac` 遇到 `JCE cannot authenticate the provider`,优先使用上面的直接调用方式;这是 JCE Provider 认证限制,不影响 `SM3HMac` 直接 API。
### 随机数生成(通过 SM3 实现)
```java
byte[] random = Random.RandomBySM3(16);
System.out.println(Hex.toHexString(random));
```
### SM4 加解密
```java
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
byte[] iv = new byte[16];
secureRandom.nextBytes(key);
secureRandom.nextBytes(iv);
// ECB 模式
SM4Cipher sm4CipherECB = new SM4Cipher(ModeEnum.ECB);
byte[] ecbmi = sm4CipherECB.cipherEncrypt(key, msg.getBytes(), null);
byte[] ecbming = sm4CipherECB.cipherDecrypt(key, ecbmi, iv);
System.out.println("ECB明文:" + new String(ecbming));
// CBC 模式
SM4Cipher sm4CipherCBC = new SM4Cipher(ModeEnum.CBC);
byte[] cbcmi = sm4CipherCBC.cipherEncrypt(key, msg.getBytes(), iv);
byte[] cbcming = sm4CipherCBC.cipherDecrypt(key, cbcmi, iv);
System.out.println("CBC明文:" + new String(cbcming));
// CTR 模式
SM4Cipher sm4CipherCTR = new SM4Cipher(ModeEnum.CTR);
byte[] ctrmi = sm4CipherCTR.cipherEncrypt(key, msg.getBytes(), iv);
byte[] ctrming = sm4CipherCTR.cipherDecrypt(key, ctrmi, iv);
System.out.println("CTR明文:" + new String(ctrming));
// GCM 模式
SM4Cipher sm4_gcm = new SM4Cipher();
AEADExecution aeadExecution = sm4_gcm.cipherEncryptGCM(key, msg, new byte[12], "aad".getBytes(), 16);
System.out.println("GCM密文:" + Hex.toHexString(aeadExecution.getCipherText()));
System.out.println("GCMtag:" + Hex.toHexString(aeadExecution.getTag()));
byte[] ming_gcm = sm4_gcm.cipherDecryptGCM(key, aeadExecution.getCipherText(), new byte[12], "aad".getBytes(), aeadExecution.getTag());
System.out.println("GCM明文:" + new String(ming_gcm));
```
#### 性能相关配置
- **内部多线程**:ECB、CTR、CBC 解密、GCM-GCTR 等可并行模式在处理 **≥256 个分组(4 KB)** 的数据时会自动使用内部线程池加速;分组数较小时自动退化为单线程,避免线程调度开销。
- **关闭内部并行**:业务层若希望自己管理线程池,或运行在线程敏感的容器/Serverless 环境,可添加 JVM 参数 `-Dgm.sm4.parallel=false`,强制所有模式走单线程路径;关闭后内部线程池不会被创建。
### 模拟 TLS 握手进行密钥协商(Netty)
#### 服务端(默认使用 4433 端口)
```java
NettyTlsServer nettyTlsServer = new NettyTlsServer(4432);
new Thread(() -> {
try {
nettyTlsServer.start();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
while (true) {
System.out.println("server sleep");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (nettyTlsServer.getRandom() != null) {
System.out.println("netty server random:" + Hex.toHexString(nettyTlsServer.getRandom()));
break;
}
}
nettyTlsServer.shutdown();
}).start();
```
#### 客户端
```java
NettyTlsClient nettyTlsClient = new NettyTlsClient("localhost", 4432);
new Thread(() -> {
try {
nettyTlsClient.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}).start();
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (nettyTlsClient.getRandom() != null) {
System.out.println("netty client random:" + Hex.toHexString(nettyTlsClient.getRandom()));
break;
}
}
nettyTlsClient.shutdown();
System.out.println(i.incrementAndGet() + "---------TLS握手测试通过(NETTY)---------");
}).start();
```
#### 服务端(使用私有服务端证书以及自定义端口)
```java
NettyTlsServer nettyTlsServer = new NettyTlsServer(4432, cert, pri);
```
#### 客户端
```java
NettyTlsClient nettyTlsClient = new NettyTlsClient("localhost", 4432);
```
#### 客户端(使用固定 sessionId 可以获取固定的 key,在 Server 中缓存,存在被窃听风险,自行选择是否开启,默认关闭)
```java
NettyTlsClient nettyTlsClient = new NettyTlsClient("localhost", 4432, true, Hex.decode("1234567812345678"));
```
### 模拟 TLS 握手进行密钥协商(Socket)
#### 服务端(默认使用 4433 端口)
```java
TlsServer tlsServer = new TlsServer();
tlsServer.setDEBUG(true);
tlsServer.start();
System.out.println("握手完成!");
System.out.println("服务端随机数:" + Hex.toHexString(tlsServer.getRandom()));
```
#### 客户端
```java
TlsClient tlsClient = new TlsClient("127.0.0.1");
tlsClient.setDEBUG(true);
tlsClient.start();
System.out.println("握手完成!");
System.out.println("客户端随机数:" + Hex.toHexString(tlsClient.getRandom()));
```
#### 服务端(使用私有服务端证书以及自定义端口)
```java
TlsServer tlsServer = new TlsServer(serverCert, serverCertPriKey, 447);
tlsServer.setDEBUG(true);
tlsServer.start();
System.out.println("握手完成!");
System.out.println("服务端随机数:" + Hex.toHexString(tlsServer.getRandom()));
```
#### 客户端
```java
TlsClient tlsClient = new TlsClient("127.0.0.1", 447);
tlsClient.setDEBUG(true);
tlsClient.start();
System.out.println("握手完成!");
System.out.println("客户端随机数:" + Hex.toHexString(tlsClient.getRandom()));
```
## 性能对比(vs Bouncy Castle / Hutool)
下表为同一台机器(Intel Core Ultra 7 265K,20 核)、同一 JDK(OpenJDK 25.0.1)下 gm-java、Bouncy Castle、Hutool 三方的实测对比,gm-java 在所有算法分类上均为最快。测试代码为 `src/test/java/com/yxj/gm/BenchmarkComparison.java`,完整原始报告见 `reports/benchmark-comparison-20260717-101730-JDK25.log`(对应提交 `4038f94`)。
对比口径:BC 的 SM2 使用 lightweight engine/signer 预初始化后复用,签名采用与 gm-java 相同的 64 字节裸编码;SM4 中 gm-java 的内部多线程为默认开启状态(ECB/CTR/CBC 解密等可并行模式在 ≥4 KB 数据时自动启用,可用 `-Dgm.sm4.parallel=false` 关闭)。
### SM2(单次操作平均耗时,越低越好)
| 操作 | gm-java | BC(复用) | Hutool | gm-java 领先 |
|------|--------:|----------:|-------:|--------------|
| 密钥生成 | 15.4 µs | 48.1 µs | 73.6 µs | BC 慢 178%,Hutool 慢 387% |
| 加密 | 78.6 µs | 157.3 µs | 155.2 µs | BC 慢 70%,Hutool 慢 92% |
| 解密 | 59.6 µs | 109.6 µs | 112.2 µs | BC 慢 67%,Hutool 慢 82% |
| 签名 | 15.8 µs | 47.6 µs | 85.7 µs | BC 慢 192%,Hutool 慢 432% |
| 验签 | 19.0 µs | 52.8 µs | 51.7 µs | BC 慢 146%,Hutool 慢 159% |
### SM3(单次哈希平均耗时,越低越好)
| 数据量 | gm-java | BC | Hutool | gm-java 领先 |
|--------|--------:|---:|-------:|--------------|
| 16 B | 0.235 µs | 0.238 µs | 0.520 µs | BC 慢 1.1%,Hutool 慢 117% |
| 1 KB | 3.3 µs | 3.3 µs | 3.5 µs | BC 慢 1.8%,Hutool 慢 8.4% |
| 64 KB | 194 µs | 200 µs | 200 µs | BC 慢 3.1%,Hutool 慢 3.4% |
| 1 MB | 3.07 ms | 3.15 ms | 3.17 ms | BC 慢 2.4%,Hutool 慢 3.3% |
### SM4(吞吐,越高越好;10 MB 数据,1 MB/20 MB 趋势一致)
| 模式 | gm-java | BC | Hutool | gm-java 领先 |
|------|--------:|---:|-------:|--------------|
| ECB 加密 | 1695 MB/s | 132 MB/s | 133 MB/s | 约 13 倍 |
| ECB 解密 | 1695 MB/s | 132 MB/s | 133 MB/s | 约 13 倍 |
| CBC 加密 | 194 MB/s | 118 MB/s | 118 MB/s | 约 1.6 倍 |
| CBC 解密 | 1667 MB/s | 120 MB/s | 121 MB/s | 约 14 倍 |
| CTR 加密 | 1961 MB/s | 114 MB/s | 119 MB/s | 约 17 倍 |
| CTR 解密 | 2041 MB/s | 114 MB/s | 119 MB/s | 约 18 倍 |
> SM2 的领先来自 native 域运算加速(Montgomery/热公钥 comb/wNAF/Jacobian 项目坐标验签)与线程本地缓存;SM4 的大倍数主要来自内部并行与 native 加速路径,CBC 加密为串行模式,体现的是单线程实现差距;SM3 三方同属一个量级,gm-java 各尺寸均小幅领先。不同机器/JDK 下绝对值会变化,建议以本机复测为准。
## 更新日志
[查看完整更新日志](CHANGELOG.md)