密码学算法分类指南[
国密算法全解析
Java实现国密算法 SM2 /SM3 /SM4加解密(基于 BouncyCastle)
环境要求:
JDK 17Maven 3BouncyCastle(1.78 )
先上个结论, SM2 是三种算法中唯一真正适用于数字签名的算法。
一、加解密与签名验签的区别
贴代码之前,先说一下加解密与签名验签的区别:
加解密:用于保密通信,使用公钥加密,私钥解密。签名验签:用于身份验证和数据完整性校验, 强调真实性、完整性以及不可否认性。使用私钥签名,公钥验签。
举个栗子:
1. 加解密示例
A 要给 B 寄一份重要的文件快递,但怕被别人偷看,便使用了一个只能用 B 钥匙打开的快递箱。 如图:

B 提前公开了开箱钥匙(公钥);A 把文件放进快递箱里,并用这把钥匙锁住(公钥加密);A 寄出这个快递箱(密文);B 收到后,用私钥打开;只有 B 能看到内容,别人即使拦截也无法打开。
2. 签名验签示例
A 寄文件快递时,在包裹外贴上了专属的封条(签名)。B 收到包裹后,使用 A 的公开印章验签,判断这个包括是否是 A 寄出的,内容是否被修改过。

A 写好信件后,用自己专属的签名封条(私钥)封住包裹;B 接收包裹, 从公开平台获取A 的印章样式(公钥);B 比对签名是否一致;如果验证通过,B 可以确定包裹是 A 发出的,内容也没被改过。
二、 Maven 依赖配置
在 中添加如下依赖:
pom.xml
<!-- 1.78 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>
三. 签名工具类
工具类说明:
工具类基于 Bouncy Castle 实现;SM2 用于非对称签名验签;SM3 用于哈希校验(非数字签名);SM4 用于对称加密,可用于实现“加密认证”。
Bouncy Castle 中,SM4Engine 默认使用 ECB 模式 。类 、
SmBaseUtils 和密钥对生成在上一篇文章中,这里就不贴了。
SmKeyGenUtils
代码如下:
import org.bouncycastle.crypto.digests.SM3Digest;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
/***
* 国密算法签名工具类.
*
* @author shijiangyong
* @date 2025/11/5 10:23
**/
public class SmAlgorithmSignUtils extends SmBaseUtils{
/**
* SM2 签名(非对称,基于公钥/私钥).
*
* @param privateKey key
* @param valueToDigest 数据
* @return 结果
*/
public static String sm2Sign(final String privateKey, final String valueToDigest) {
try {
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("SM3withSM2", "BC");
signature.initSign(key);
signature.update(valueToDigest.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
} catch (Exception e) {
throw new RuntimeException("SM2 sign error", e);
}
}
/**
* SM2 验签.
*
* @param publicKey publicKey
* @param value 验签数据
* @param signatureStr 签名值
* @return 结果
*/
public static boolean sm2Verify(final String publicKey, final String value, final String signatureStr) {
try {
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("SM3withSM2", "BC");
signature.initVerify(key);
signature.update(value.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.getDecoder().decode(signatureStr));
} catch (Exception e) {
throw new RuntimeException("SM2 verify error", e);
}
}
/**
* sm3签名.sm3签名不等于数字签名,非标准签名算法。
* 哈希+密钥的方式(类似简化版的 HMAC)
*
* @param key
* @param valueToDigest 数据
* @return 加密结果
*/
public static String sm3WithKeyHash(final String key, final String valueToDigest) {
SM3Digest digest = new SM3Digest();
byte[] input = (valueToDigest + key).getBytes(StandardCharsets.UTF_8);
digest.update(input, 0, input.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return Base64.getEncoder().encodeToString(hash);
}
/**
* sm3 签名认证.
*
* @param key
* @param data
* @param expectedHash
* @return 结果
*/
public static boolean sm3SimpleVerify(final String key, final String data, final String expectedHash) {
String actualHash = sm3WithKeyHash(key, data);
return actualHash.equalsIgnoreCase(expectedHash);
}
// ========== 通用加密/解密方法 ==========
/**
* 通用加密方法.
*
* @param mode 模式
* @param key 密钥
* @param iv 初始向量
* @param data
* @return 加密结果
*/
private static String sm4Encrypt(String mode, String key, String iv, String data) {
try {
byte[] keyBytes = Base64.getDecoder().decode(key);
String transformation = "SM4/" + mode + "/PKCS5Padding";
Cipher cipher = Cipher.getInstance(transformation, "BC");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
if ("CBC".equalsIgnoreCase(mode)) {
byte[] ivBytes = Base64.getDecoder().decode(iv);
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
}
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("SM4 " + mode + " encrypt error", e);
}
}
/**
* 解密、验签比较.
*
* @param mode
* @param key
* @param iv
* @param data
* @param encryptedBase64
* @return 结果
*/
private static boolean sm4Decrypt(String mode, String key, String iv, String data, String encryptedBase64) {
try {
byte[] keyBytes = Base64.getDecoder().decode(key);
String transformation = "SM4/" + mode + "/PKCS5Padding";
Cipher cipher = Cipher.getInstance(transformation, "BC");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
if ("CBC".equalsIgnoreCase(mode)) {
byte[] ivBytes = Base64.getDecoder().decode(iv);
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, keySpec);
}
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedBase64));
return data.equals(new String(decrypted, StandardCharsets.UTF_8));
} catch (Exception e) {
return false;
}
}
// ===================== SM4 通用签名与验签接口 =====================
/**
* SM4 通用签名(支持 ECB / CBC / GCM).
* @param mode
* @param key 私钥
* @param iv
* @param data
* @return 签名结果
*/
public static String sm4EncryptAndAuthenticate(String mode, String key, String iv, String data) {
validateSm4Key(key);
switch (mode.toUpperCase()) {
case "ECB":
return sm4Encrypt("ECB", key, null, data);
case "CBC":
validateIv(iv);
return sm4Encrypt("CBC", key, iv, data);
case "GCM":
return sm4GcmEncrypt(key, data);
default:
throw new IllegalArgumentException("Unsupported SM4 mode: " + mode);
}
}
/**
* SM4 通用验签(支持 ECB / CBC / GCM).
* 对称加密不具备不可否认性
*
* @param mode
* @param key 公钥
* @param iv
* @param data
* @param encryptedBase64
* @return 验签结果
*/
public static boolean sm4CheckIntegrity(String mode, String key, String iv, String data, String encryptedBase64) {
validateSm4Key(key);
switch (mode.toUpperCase()) {
case "ECB":
return sm4Decrypt("ECB", key, null, data, encryptedBase64);
case "CBC":
validateIv(iv);
return sm4Decrypt("CBC", key, iv, data, encryptedBase64);
case "GCM":
return sm4GcmDecrypt(key, data, encryptedBase64);
default:
throw new IllegalArgumentException("Unsupported SM4 mode: " + mode);
}
}
/**
* 校验 SM4 密钥长度是否为 16 字节(128 位)
*
* @param key
*/
private static void validateSm4Key(String key) {
if (!SmKeyGenUtils.isValidSM4Key(key,SmBaseUtils.KEY_LENGTH)) {
throw new IllegalArgumentException("SM4 key must be 16 bytes (128 bits)");
}
}
/**
* 校验 CBC 模式 IV 长度是否为 16 字节
*
* @param iv
*/
private static void validateIv(String iv) {
if (!SmKeyGenUtils.isValidSM4Key(iv,SmBaseUtils.KEY_LENGTH)) {
throw new IllegalArgumentException("SM4 CBC IV must be 16 bytes");
}
}
// ===================== SM4 GCM =====================
/**
* gcm 加密.
*
* @param key
* @param data
* @return
*/
private static String sm4GcmEncrypt(String key, String data) {
try {
byte[] keyBytes = Base64.getDecoder().decode(key);
byte[] iv = new byte[12];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
byte[] cipherText = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 拼接 IV + 密文
byte[] combined = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
return Base64.getEncoder().encodeToString(combined);
} catch (Exception e) {
throw new RuntimeException("SM4 GCM encrypt error", e);
}
}
/**
* gcm解密.
*
* @param key
* @param data
* @param encryptedBase64
* @return
*/
private static boolean sm4GcmDecrypt(String key, String data, String encryptedBase64) {
try {
byte[] input = Base64.getDecoder().decode(encryptedBase64);
byte[] keyBytes = Base64.getDecoder().decode(key);
byte[] iv = Arrays.copyOfRange(input, 0, 12);
byte[] cipherText = Arrays.copyOfRange(input, 12, input.length);
Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
byte[] decrypted = cipher.doFinal(cipherText);
return data.equals(new String(decrypted, StandardCharsets.UTF_8));
} catch (Exception e) {
return false;
}
}
}
一般生产环境不使用 ECB 模式处理敏感数据,这个模式无法隐藏明文结构;CBC 模式需确保 IV 唯一且不可预测,推荐每次随机生成,IV 长度为 16 字节;GCM 模式推荐用于实际场景,提供认证加密(AEAD)能力,IV 长度为 12 字节;SM2 签名具备不可否认性,适用于对外接口、金融级别系统;安全性高的场景中,密钥一般使用 KMS 或者安全的配置中心管理;SM3 签名不等于数字签名,非标准签名算法。
四、测试工具类
代码说明:
SM2 测试使用预设公私钥;SM3 使用自定义 key 进行哈希校验;SM4 支持 ECB / CBC / GCM 三种模式;
import com.example.sm.utils.SmAlgorithmSignUtils;
import com.example.sm.utils.SmBaseUtils;
import com.example.sm.utils.SmKeyGenUtils;
/***
* @title
* @author shijiangyong
* @date 2025/11/6 20:42
**/
public class SignTest {
public static void main(String[] args) {
String str = "这个是一个signature test~!";
// sm2
System.out.println("=== SM2 Test ===");
String publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAED4a2WmtVnR3lwae4pcX507BL5bTL9BSyFuU3Ij+zc+ppYu8DgoOsUbIvV2ROaDa6+J6FTYEtmhmchsYD/edOzw==";
String privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg1IsF13XAUdqyUWqegCKEOTaVaCPwFrWXF0gIB5d6dMWgCgYIKoEcz1UBgi2hRANCAAQPhrZaa1WdHeXBp7ilxfnTsEvltMv0FLIW5TciP7Nz6mli7wOCg6xRsi9XZE5oNrr4noVNgS2aGZyGxgP9507P";
String sm2Sign = SmAlgorithmSignUtils.sm2Sign(privateKey, str);
boolean sm2Verify = SmAlgorithmSignUtils.sm2Verify(publicKey, str, sm2Sign);
System.out.println("sm2Verify: " + sm2Verify);
// sm3
System.out.println("=== SM3 Test ===");
// 定义自己的字符串即可
String sm3Key = "testkey";
String sm3Sign = SmAlgorithmSignUtils.sm3WithKeyHash(sm3Key, str);
boolean sm3Verify = SmAlgorithmSignUtils.sm3SimpleVerify(sm3Key, str, sm3Sign);
System.out.println("sm3Verify: " + sm3Verify);
//sm4 ecb
System.out.println("=== SM4 Test ===");
String key = SmKeyGenUtils.generateSM4Key(SmBaseUtils.KEY_LENGTH);
String ecbSign = SmAlgorithmSignUtils.sm4EncryptAndAuthenticate("ecb", key, null, str);
boolean ecbVerify = SmAlgorithmSignUtils.sm4CheckIntegrity("ecb", key, null, str, ecbSign);
System.out.println("ecbVerify: " + ecbVerify);
// cbc
String iv = SmKeyGenUtils.generateSM4Key(SmBaseUtils.KEY_LENGTH);
String cbcSign = SmAlgorithmSignUtils.sm4EncryptAndAuthenticate("cbc", key, iv, str);
boolean cbcVerify = SmAlgorithmSignUtils.sm4CheckIntegrity("cbc", key, iv, str, cbcSign);
System.out.println("cbcVerify: " + cbcVerify);
// gcm,实际内部生成 IV,忽略传入参数,这里只是示例gcm需要使用字节IV
// String gcmIv = SmKeyGenUtils.generateSM4Key(SmBaseUtils.IV_LENGTH);
String gcmSign = SmAlgorithmSignUtils.sm4EncryptAndAuthenticate("gcm", key, null, str);
boolean gcmVerify = SmAlgorithmSignUtils.sm4CheckIntegrity("gcm", key, null, str, gcmSign);
System.out.println("gcmVerify: " + gcmVerify);
}
}
测试结果:

五、总结
SM2:真正的数字签名算法, 适用于涉密、金融、司法等需要不可否认性的场景 ;SM3:适合用于数据完整性校验,非签名算法;SM4:用于对称加密,可配合解密比对实现简单认证,非签名算法 。
在实际业务中根据安全等级和合规要求,选用合适的算法吧。


