Java开发者必知的国密SM2/SM3/SM4:10分钟上手国产加密算法(附完整代码)
·

前言:为什么Java开发者需要了解国密?
三个事实:
- 密评合规要求:等保三级及以上系统,密码应用必须通过商用密码应用安全性评估(密评),其中明确要求使用国密算法
- 信创替代加速:金融、政务、能源、交通等关键行业正在大规模推进国密改造
- JDK默认不支持:Java标准库
java.security不内置国密算法,需要引入 BouncyCastle 或国产密码库
简单说:你可能不一定现在要用,但你一定会遇到。
本文用最小化代码示例,讲清楚SM2(非对称加密)、SM3(哈希)、SM4(对称加密)这三种最常用的国密算法怎么在Java里调用。
一、三种算法一句话定位
| 算法 | 类型 | 对标国际算法 | 一句话定位 |
|---|---|---|---|
| SM2 | 非对称加密 | RSA/ECC | 公私钥加解密、数字签名 |
| SM3 | 哈希摘要 | SHA-256 | 数据完整性校验、密码哈希 |
| SM4 | 对称加密 | AES | 大量数据加解密(快,密钥要保密) |
SM2(非对称) SM4(对称)
┌─────────┐ ┌─────────┐
│ 公钥加密 │ │ 同一密钥 │
│ 私钥解密 │ │ 加解密 │
└─────────┘ └─────────┘
│ │
├── 慢,适合加密密钥 ──→ ←── 快,适合加密数据
│ │
└────── 配合使用 ──────────┘
(数字信封模式)
SM3(哈希)
┌─────────┐
│ 任意长度 │
│ → 256位 │ 用于:密码存储、文件校验、签名摘要
│ 不可逆 │
└─────────┘
二、环境准备
Maven依赖(二选一):
<!-- 方式1:BouncyCastle(推荐,兼容性最好)-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>
<!-- 方式2:hutool-crypto(封装更简单,适合快速开发)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.28</version>
</dependency>
本文使用 Hutool 做演示(代码量最少),文末附BouncyCastle原生写法。
三、SM4 对称加密:最常用、最容易上手
SM4是分组对称加密算法,密钥长度128位(16字节),分组长度128位。
3.1 最简单的加解密
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
public class Sm4Demo {
public static void main(String[] args) {
// 1. 生成SM4密钥(或使用已有的16字节密钥)
byte[] key = SmUtil.sm4().getSecretKey().getEncoded();
// 2. 创建SM4加密器(默认ECB模式,PKCS5Padding填充)
SymmetricCrypto sm4 = SmUtil.sm4(key);
// 3. 加密
String plainText = "这是待加密的业务数据";
String cipherText = sm4.encryptHex(plainText);
System.out.println("密文: " + cipherText);
// 输出: 密文: a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4...
// 4. 解密
String decrypted = sm4.decryptStr(cipherText);
System.out.println("解密: " + decrypted);
// 输出: 解密: 这是待加密的业务数据
}
}
3.2 指定CBC模式(更安全,推荐生产使用)
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
public class Sm4CbcDemo {
public static void main(String[] args) {
// 准备密钥和IV向量(各16字节)
byte[] key = "0123456789abcdef".getBytes(); // 16字节密钥
byte[] iv = "fedcba9876543210".getBytes(); // 16字节IV
// 创建SM4-CBC-PKCS5Padding实例
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv);
String plainText = "CBC模式加密的业务数据";
String cipherText = sm4.encryptBase64(plainText);
System.out.println("CBC密文(Base64): " + cipherText);
String decrypted = sm4.decryptStr(cipherText);
System.out.println("解密结果: " + decrypted);
}
}
3.3 实战:加密数据库敏感字段
@Service
public class UserPrivacyService {
// 密钥从密钥管理平台获取,不要硬编码!
@Value("${sm4.key}")
private String sm4Key;
private final SM4 sm4;
@PostConstruct
public void init() {
byte[] keyBytes = HexUtil.decodeHex(sm4Key);
this.sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding,
keyBytes, "random_iv_16byte".getBytes());
}
// 手机号加密存入数据库
public String encryptPhone(String phone) {
return sm4.encryptBase64(phone);
}
// 手机号解密
public String decryptPhone(String encryptedPhone) {
return sm4.decryptStr(encryptedPhone);
}
// 使用示例
public void saveUser(User user) {
user.setPhone(encryptPhone(user.getPhone())); // 加密手机号
user.setIdCard(encryptPhone(user.getIdCard())); // 加密身份证
// 加密后存入数据库
userRepository.save(user);
}
}
四、SM3 哈希算法:密码存储和完整性校验
SM3输出256位(32字节)哈希值,不可逆。
4.1 基本使用
import cn.hutool.crypto.SmUtil;
public class Sm3Demo {
public static void main(String[] args) {
// 计算SM3哈希
String data = "hello 国密算法";
String hash = SmUtil.sm3(data);
System.out.println("SM3哈希: " + hash);
// 输出: 64位十六进制字符串
// 对文件计算SM3(文件完整性校验)
String fileHash = SmUtil.sm3(new File("/path/to/file.pdf"));
System.out.println("文件SM3: " + fileHash);
}
}
4.2 实战:用户密码安全存储
@Service
public class PasswordService {
// 密码加盐后SM3哈希存储
public String hashPassword(String rawPassword, String salt) {
// SM3(password + salt)
String combined = rawPassword + salt;
return SmUtil.sm3(combined);
}
// 用户注册
public void register(String username, String rawPassword) {
String salt = RandomUtil.randomString(16); // 生成16位随机盐
String hashedPassword = hashPassword(rawPassword, salt);
User user = new User();
user.setUsername(username);
user.setPassword(hashedPassword);
user.setSalt(salt); // 盐值也存起来
userRepository.save(user);
}
// 用户登录验证
public boolean verifyPassword(String username, String rawPassword) {
User user = userRepository.findByUsername(username);
String hashedInput = hashPassword(rawPassword, user.getSalt());
return hashedInput.equals(user.getPassword());
}
}
4.3 SM3-HMAC:带密钥的哈希(防篡改)
public class Sm3HmacDemo {
public static void main(String[] args) {
String data = "转账金额:5000元";
String secretKey = "shared-secret-key";
// 计算HMAC-SM3
String hmac = SmUtil.hmacSm3(secretKey.getBytes()).digestHex(data);
System.out.println("HMAC-SM3: " + hmac);
// 应用场景:API签名验证
// 客户端发送:data + hmac(data, secretKey)
// 服务端用相同secretKey计算hmac,比对是否一致
// 不一致 → 数据被篡改
}
}
五、SM2 非对称加密:公私钥加解密和数字签名
SM2基于椭圆曲线密码(ECC),256位密钥,安全性相当于RSA-3072,但密钥更短、运算更快。
5.1 生成SM2密钥对
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
public class Sm2Demo {
public static void main(String[] args) {
// 生成密钥对
SM2 sm2 = SmUtil.sm2();
// 获取公私钥(Base64格式)
String privateKey = sm2.getPrivateKeyBase64();
String publicKey = sm2.getPublicKeyBase64();
System.out.println("私钥: " + privateKey);
System.out.println("公钥: " + publicKey);
// 使用自定义密钥对
SM2 customSm2 = SmUtil.sm2(privateKey, publicKey);
}
}
5.2 公钥加密、私钥解密
public class Sm2EncryptDemo {
public static void main(String[] args) {
SM2 sm2 = SmUtil.sm2();
// 公钥加密
String plainText = "用公钥加密的敏感数据";
String cipherText = sm2.encryptBase64(plainText, KeyType.PublicKey);
System.out.println("SM2密文: " + cipherText);
// 私钥解密
String decrypted = sm2.decryptStr(cipherText, KeyType.PrivateKey);
System.out.println("解密: " + decrypted);
}
}
5.3 数字签名:私钥签名、公钥验签
public class Sm2SignDemo {
public static void main(String[] args) {
// 模拟场景:A用私钥对合同签名,B用A的公钥验证签名
SM2 sm2 = SmUtil.sm2();
String contract = "合同内容:甲方向乙方支付100万元";
// A:用私钥签名(sign)
byte[] sign = sm2.sign(contract.getBytes());
String signBase64 = Base64.encode(sign);
System.out.println("签名: " + signBase64);
// B:用A的公钥验证签名(verify)
boolean verified = sm2.verify(contract.getBytes(), sign);
System.out.println("验签结果: " + verified); // true
// 如果合同被篡改
String tamperedContract = "合同内容:甲方向乙方支付1000万元";
boolean tampered = sm2.verify(tamperedContract.getBytes(), sign);
System.out.println("篡改后验签: " + tampered); // false
}
}
5.4 实战:SM2+SM4数字信封(最佳实践)
业界标准的"数字信封"模式:用SM4加密数据(快),用SM2加密SM4密钥(安全)。
public class DigitalEnvelopeDemo {
/**
* 发送方:SM4加密数据 + SM2加密SM4密钥
*/
public Envelope encrypt(String plainText, String receiverPublicKey) {
// 1. 生成随机SM4密钥(一次性)
byte[] sm4Key = RandomUtil.randomBytes(16);
// 2. 用SM4加密数据
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, sm4Key, sm4Key);
String encryptedData = sm4.encryptBase64(plainText);
// 3. 用接收方SM2公钥加密SM4密钥
SM2 sm2 = SmUtil.sm2(null, receiverPublicKey);
String encryptedKey = sm2.encryptBase64(sm4Key, KeyType.PublicKey);
// 4. 组装信封
return new Envelope(encryptedData, encryptedKey);
}
/**
* 接收方:SM2解密SM4密钥 → SM4解密数据
*/
public String decrypt(Envelope envelope, String receiverPrivateKey) {
// 1. 用SM2私钥解密出SM4密钥
SM2 sm2 = SmUtil.sm2(receiverPrivateKey, null);
byte[] sm4Key = sm2.decrypt(envelope.getEncryptedKey(), KeyType.PrivateKey);
// 2. 用SM4密钥解密数据
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, sm4Key, sm4Key);
return sm4.decryptStr(envelope.getEncryptedData());
}
// 信封数据结构
@Data
@AllArgsConstructor
public static class Envelope {
private String encryptedData; // SM4加密的数据
private String encryptedKey; // SM2加密的SM4密钥
}
}
六、BouncyCastle原生写法(进阶参考)
Hutool底层用的就是BouncyCastle。如果你不想引入Hutool,可以直接用BC:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class BcSm4RawDemo {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static byte[] sm4Encrypt(byte[] key, byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(data);
}
public static byte[] sm4Decrypt(byte[] key, byte[] cipherData) throws Exception {
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(cipherData);
}
}
七、国密算法选型速查表
| 场景 | 算法 | 模式 |
|---|---|---|
| 数据库字段加密(大批量) | SM4 | CBC + PKCS5Padding |
| 配置文件/小数据加密 | SM2 | 公钥加密、私钥解密 |
| 用户密码存储 | SM3 | 加盐后哈希 |
| 文件完整性校验 | SM3 | 直接哈希 |
| API签名防篡改 | SM3-HMAC | 带密钥哈希 |
| 电子合同/数字签名 | SM2 | 私钥签名、公钥验签 |
| 大文件传输 | SM4+SM2 | 数字信封模式 |
总结
国密三件套的使用逻辑很清晰:
- SM4 处理数据:日常加密的主力,快且简单
- SM2 保护密钥:在不可信环境中安全地传递SM4密钥
- SM3 验证完整性:哈希、签名摘要、密码存储
入门只需要记住三个Hutool方法调用:SmUtil.sm4()、SmUtil.sm3()、SmUtil.sm2()。
如果你现在的项目还没用国密,建议从SM4加密数据库敏感字段开始——成本最低、见效最快、密评检查时也能交差。
💬 话题讨论:你们项目用了国密算法吗?用SM4做数据库加密还是SM2做数字签名?欢迎评论区交流,聊聊你的实践经验。
更多推荐


所有评论(0)