一、国密算法简介
国密算法是中国密码管理局发布的密码算法标准,主要包括:
| 算法 | 类型 | 用途 | 对应国际算法 |
|---|---|---|---|
| SM2 | 非对称加密 | 签名、密钥交换、加密 | RSA、ECDSA |
| SM3 | 哈希算法 | 摘要计算、验证 | SHA-256 |
| SM4 | 对称加密 | 数据加密 | AES |
二、环境准备
Maven 依赖
XML
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<!-- Hutool 的 SM2/SM4 需要 Bouncy Castle 支持 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.78</version>
</dependency>
注册 Bouncy Castle 提供者
java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
// 在程序启动时注册
static {
Security.addProvider(new BouncyCastleProvider());
}
三、SM3 哈希算法
基本使用
java
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.digest.SM3;
public class SM3Demo {
public static void main(String[] args) {
String data = "Hello, 国密算法!";
// 方式1:直接计算 SM3 哈希
String hash = SmUtil.sm3(data);
System.out.println("SM3哈希: " + hash);
// 输出: 591e5b1b6e50f6d9b2f7d8c5a2b1e3f5a8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3
// 方式2:使用 SM3 对象(支持多次更新)
SM3 sm3 = SmUtil.sm3();
sm3.update(data.getBytes());
sm3.update("追加数据".getBytes());
byte[] digest = sm3.digest();
System.out.println("分段计算: " + cn.hutool.core.codec.HexUtil.encodeHexStr(digest));
// 方式3:验证数据完整性
String originalHash = SmUtil.sm3(originalData);
boolean isValid = originalHash.equals(computedHash);
}
}
带盐值的 SM3
java
public class SM3WithSalt {
public static void main(String[] args) {
String password = "myPassword123";
String salt = "randomSaltValue";
// 密码 + 盐值 一起哈希
String hashedPassword = SmUtil.sm3(password + salt);
System.out.println("加盐哈希: " + hashedPassword);
}
}
四、SM4 对称加密
生成密钥与基本加解密
java
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.hutool.core.codec.Base64;
public class SM4Demo {
public static void main(String[] args) {
String plainText = "这是一段需要加密的敏感数据";
// 方式1:自动生成随机密钥
SM4 sm4 = SmUtil.sm4();
byte[] key = sm4.getSecretKey().getEncoded();
System.out.println("生成的密钥(Hex): " + cn.hutool.core.codec.HexUtil.encodeHexStr(key));
// 加密
String encryptStr = sm4.encryptBase64(plainText);
System.out.println("加密结果(Base64): " + encryptStr);
// 解密
String decryptStr = sm4.decryptStr(encryptStr);
System.out.println("解密结果: " + decryptStr);
// 方式2:使用指定密钥(16字节 = 128位)
byte[] customKey = "1234567890123456".getBytes(); // 必须是16字节
SM4 sm4WithKey = new SM4(customKey);
String encrypted = sm4WithKey.encryptBase64(plainText);
String decrypted = sm4WithKey.decryptStr(encrypted);
System.out.println("自定义密钥解密: " + decrypted);
}
}
多种加解密模式
java
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
public class SM4ModesDemo {
public static void main(String[] args) {
byte[] key = "1234567890123456".getBytes();
byte[] iv = "1234567890123456".getBytes(); // 初始化向量,需要16字节
String data = "Hello SM4 with CBC mode!";
// ECB 模式(不需要 IV)
SymmetricCrypto sm4Ecb = new SymmetricCrypto("SM4/ECB/PKCS5Padding", key);
String encryptedEcb = sm4Ecb.encryptBase64(data);
String decryptedEcb = sm4Ecb.decryptStr(encryptedEcb);
// CBC 模式(需要 IV)
SymmetricCrypto sm4Cbc = new SymmetricCrypto("SM4/CBC/PKCS5Padding", key, iv);
String encryptedCbc = sm4Cbc.encryptBase64(data);
String decryptedCbc = sm4Cbc.decryptStr(encryptedCbc);
// GCM 模式(认证加密,推荐)
SymmetricCrypto sm4Gcm = new SymmetricCrypto("SM4/GCM/NoPadding", key);
String encryptedGcm = sm4Gcm.encryptBase64(data);
String decryptedGcm = sm4Gcm.decryptStr(encryptedGcm);
System.out.println("ECB解密: " + decryptedEcb);
System.out.println("CBC解密: " + decryptedCbc);
System.out.println("GCM解密: " + decryptedGcm);
}
}
五、SM2 非对称加密
生成密钥对与基本加解密
java
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.KeyUtil;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
public class SM2Demo {
public static void main(String[] args) {
String plainText = "使用SM2加密的机密信息";
// 方式1:自动生成密钥对
SM2 sm2 = SmUtil.sm2();
// 获取密钥
String privateKeyBase64 = sm2.getPrivateKeyBase64();
String publicKeyBase64 = sm2.getPublicKeyBase64();
System.out.println("私钥: " + privateKeyBase64);
System.out.println("公钥: " + publicKeyBase64);
// 公钥加密,私钥解密
String encryptStr = sm2.encryptBase64(plainText, KeyType.PublicKey);
System.out.println("加密结果: " + encryptStr);
String decryptStr = sm2.decryptStr(encryptStr, KeyType.PrivateKey);
System.out.println("解密结果: " + decryptStr);
// 方式2:使用已有密钥对
String privateKeyHex = "你的私钥Hex字符串";
String publicKeyHex = "你的公钥Hex字符串";
SM2 sm2WithKey = SmUtil.sm2(privateKeyHex, publicKeyHex);
String encrypted = sm2WithKey.encryptBase64(plainText, KeyType.PublicKey);
String decrypted = sm2WithKey.decryptStr(encrypted, KeyType.PrivateKey);
}
}
SM2 签名与验签
java
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.asymmetric.KeyType;
public class SM2SignDemo {
public static void main(String[] args) {
// 生成密钥对
SM2 sm2 = SmUtil.sm2();
String data = "需要签名的数据";
// 签名(使用私钥)
String sign = sm2.signBase64(data, null);
System.out.println("签名: " + sign);
// 验签(使用公钥)
boolean verified = sm2.verify(data, sign);
System.out.println("验签结果: " + verified);
// 带用户ID的签名(SM2标准支持)
String userId = "user123";
String signWithId = sm2.signBase64(data, userId.getBytes());
boolean verifiedWithId = sm2.verify(data, signWithId, userId.getBytes());
System.out.println("带用户ID验签: " + verifiedWithId);
}
}
密钥的存储与加载
java
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.KeyUtil;
import java.security.PrivateKey;
import java.security.PublicKey;
public class SM2KeyStoreDemo {
public static void main(String[] args) {
// 从Hex字符串加载
String priHex = "你的私钥Hex";
String pubHex = "你的公钥Hex";
SM2 sm2FromHex = SmUtil.sm2(priHex, pubHex);
// 从Base64字符串加载
String priBase64 = "你的私钥Base64";
String pubBase64 = "你的公钥Base64";
PrivateKey privateKey = KeyUtil.generatePrivateKey("SM2",
cn.hutool.core.codec.Base64.decode(priBase64));
PublicKey publicKey = KeyUtil.generatePublicKey("SM2",
cn.hutool.core.codec.Base64.decode(pubBase64));
SM2 sm2FromKeys = new SM2(privateKey, publicKey);
// 导出密钥为PEM格式
String privateKeyPem = KeyUtil.getPrivateKeyPem(privateKey);
String publicKeyPem = KeyUtil.getPublicKeyPem(publicKey);
System.out.println("私钥PEM:\n" + privateKeyPem);
System.out.println("公钥PEM:\n" + publicKeyPem);
}
}
六、综合示例:文件加密与签名
java
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.codec.Base64;
import java.io.File;
public class FileEncryptWithSM2SM4 {
// 使用 SM4 加密大文件,SM2 加密 SM4 密钥(混合加密)
public static void encryptFile(File inputFile, File outputFile, SM2 sm2) {
// 1. 生成随机的 SM4 密钥
SM4 sm4 = SmUtil.sm4();
byte[] sm4Key = sm4.getSecretKey().getEncoded();
// 2. 使用 SM2 公钥加密 SM4 密钥
String encryptedKey = sm2.encryptBase64(sm4Key, KeyType.PublicKey);
// 3. 使用 SM4 加密文件内容
byte[] fileBytes = FileUtil.readBytes(inputFile);
String encryptedData = sm4.encryptBase64(fileBytes);
// 4. 保存:加密后的密钥 + 加密后的数据
String result = encryptedKey + "\n" + encryptedData;
FileUtil.writeUtf8String(result, outputFile);
}
// 解密文件
public static void decryptFile(File inputFile, File outputFile, SM2 sm2) {
String content = FileUtil.readUtf8String(inputFile);
String[] parts = content.split("\n", 2);
// 1. SM2 私钥解密得到 SM4 密钥
byte[] sm4Key = sm2.decrypt(parts[0], KeyType.PrivateKey);
// 2. 使用 SM4 密钥解密数据
SM4 sm4 = new SM4(sm4Key);
byte[] decryptedData = sm4.decrypt(parts[1]);
// 3. 写入文件
FileUtil.writeBytes(decryptedData, outputFile);
}
public static void main(String[] args) {
// 初始化 SM2 密钥对
SM2 sm2 = SmUtil.sm2();
// 加密文件
File input = new File("plain.txt");
File encrypted = new File("encrypted.dat");
encryptFile(input, encrypted, sm2);
// 解密文件
File decrypted = new File("decrypted.txt");
decryptFile(encrypted, decrypted, sm2);
System.out.println("文件加解密完成!");
}
}
七、注意事项
-
密钥长度:SM4 密钥必须为 128 位(16字节)
-
IV 要求:CBC/GCM 等模式需要 16 字节的 IV
-
SM2 加密限制:单次加密数据长度有限制,建议混合加密
-
性能考虑:大文件应使用流式处理,避免一次性读入内存
-
BC 版本:确保 Bouncy Castle 版本与 JDK 兼容