一、国密算法介绍
参考:https://blog.csdn.net/Rookie_CEO/article/details/144229214
国密即国家密码局认定的国产密码算法,即商用密码。
国家密码管理局发布了一系列国产商用密码标准算法,包括SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、祖冲之密码算法(ZUC)等。
SM系列 密码,SM 代表 商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。
- SM1、SM4、SM7、祖冲之密码(ZUC)属于对称算法; 备注:SM1、SM7算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
- SM2、SM9属于非对称算法;
- SM3属于哈希算法。
算法类型 | 国密算法 | 应用场景 | 国际对标算法 | 核心对比差异 |
---|---|---|---|---|
对称加密 | SM1 | 金融IC卡、政务机密数据传输(需专用硬件调用) | AES | 安全性与AES相当,但算法未公开且仅限硬件芯片实现;AES支持软硬件通用 |
对称加密 | SM4 | 无线局域网(WAPI标准)、物联网设备通信加密 | AES/DES | 分组长度与AES相同(128位),效率接近;DES因密钥短已淘汰,SM4安全性更优 |
对称加密 | SM7 | 非接触式IC卡(门禁卡、公交卡、赛事门票等) | DES | 专为轻量级场景设计,密钥未公开;DES因安全性不足被逐步替代 |
非对称加密 | SM2 | 电子合同签名、数字证书认证、SSL/TLS协议 | RSA/ECC | 256位密钥安全强度超RSA2048位,计算效率高于RSA,对标国际ECC算法 |
哈希算法 | SM3 | 数字签名摘要生成、区块链哈希计算、数据完整性校验 | SHA-256 | 摘要长度相同(256位),抗碰撞性对标SHA-256,设计融合MD5与SHA-256优势 |
序列密码 | ZUC(祖冲之) | 4G/5G移动通信加密(3GPP国际标准) | A5/1(GSM加密) | ZUC为4G/5G主流标准,国际无直接对标;A5/1因安全性不足已过时 |
基于身份加密 | SM9 | 物联网设备身份认证、云服务密钥管理(无需数字证书) | 无直接对标 | 国际无同类标准化方案,SM9通过椭圆曲线双线性对简化密钥管理流程 |
二、在JAVA中使用国密算法
1、JCA与Bouncy Castle的定义与角色
1.1、JCA:Java 标准加密框架
JCA(Java Cryptography Architecture) 是 Java 平台内置的加密体系结构,定义了加密服务(如密钥管理、数字签名、消息摘要)的接口与核心逻辑,但不直接提供具体算法实现,而是通过"Provider"机制动态加载第三方实现模块。
参考:JCA.md
1.2、Bouncy Castle:第三方加密库
Bouncy Castle 是一个开源的加密库,通过实现 JCA/JCE(Java Cryptography Extension)接口,提供扩展算法支持(如 SM2/SM4 等中国商用密码算法)和更丰富的安全协议实现,作为 JCA 的补充 Provider 存在。
2、功能扩展与协作
2.1、算法覆盖范围差异
- JCA 默认仅支持基础算法(如 AES、RSA、SHA-256),且受限于国际出口限制,部分高强度算法可能缺失。
- Bouncy Castle 提供 50+ 加密算法(包括国密算法)及 SSL/TLS、OpenPGP 等协议支持,弥补 JCA 的功能局限。
2.2、动态集成方式
开发者可通过 java.security.Security.addProvider(new BouncyCastleProvider()) 将 Bouncy Castle 注册为 JCA 的 Provider,之后即可通过标准 JCA API 调用其算法(如 Cipher.getInstance("SM2", "BC"))。
3、实际应用中的协作模式
3.1、统一接口调用
无论使用 JCA 默认实现还是 Bouncy Castle,均通过统一的 java.security 和 javax.crypto 接口操作加密服务,例如:
// 使用 Bouncy Castle 的 SM4 加密
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", "BC");
此时代码仍遵循 JCA 标准,仅底层实现替换为 Bouncy Castle。
3.2、互补场景
- 合规需求:需支持国密算法时,必须依赖 Bouncy Castle。
- 算法多样性:开发需要 ECC、ElGamal 等非 JCA 内置算法时,Bouncy Castle 提供现成实现。
4、关键差异对比
特性 | JCA (Java 标准) | Bouncy Castle |
---|---|---|
算法实现 | 基础算法(受出口限制) | 扩展算法(含国密/小众算法) |
协议支持 | 有限(如 TLS 1.2) | 丰富(如 OpenPGP、CMS) |
部署方式 | 内置于 JDK | 需手动引入依赖并注册 Provider |
三、编码实现
代码仓库:【guo-mi】
1、引入Bouncy Castle依赖
将 Bouncy Castle 库(如 bcprov-jdk18on)加入项目构建配置。
-
JDK1.8及以上版本支持:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.80</version> </dependency> -
JDK1.5及以上版本支持:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>
2、消息摘要算法
package com.sky.wsp.guo.mi;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
/**
* @author [email protected]
* @desccription 2.4 消息摘要 The MessageDigest Class
* @date 2025/04/09
*/
public class MessageDigestTest {
@BeforeAll
public static void setUpBeforeClass() throws Exception {
Security.addProvider(new BouncyCastleProvider());
System.out.println("BouncyCastle Provider is now available");
}
@Test
public void test() throws NoSuchAlgorithmException {
byte[] bytes1 = "Do you".getBytes(StandardCharsets.UTF_8);
byte[] bytes2 = "love".getBytes(StandardCharsets.UTF_8);
byte[] bytes3 = "China?".getBytes(StandardCharsets.UTF_8);
String algorithm = "SHA-256";
MessageDigest shaDigest = MessageDigest.getInstance(algorithm);
// 可以多次调用update方法以分批处理大量数据
shaDigest.update(bytes1);
// 可以多次调用update方法以分批处理大量数据
shaDigest.update(bytes2);
// 每次调用digest()方法后,MessageDigest对象会被重置
byte[] b1 = shaDigest.digest(bytes3);
String s1 = HexUtil.encode(b1);
System.out.println(algorithm + "=" +s1);
algorithm = "MD5";
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
md5Digest.update(bytes1);
md5Digest.update(bytes2);
byte[] b2 = md5Digest.digest(bytes3);
String s2 = HexUtil.encode(b2);
System.out.println(algorithm + "=" +s2);
algorithm = "SM3";
MessageDigest sm3Digest = MessageDigest.getInstance("SM3");
sm3Digest.update(bytes1);
sm3Digest.update(bytes2);
byte[] b3 = sm3Digest.digest(bytes3);
String s3 = HexUtil.encode(b3);
System.out.println(algorithm + "=" +s3);
}
}
输出结果:
SHA-256=d291f4f3771e28229e424172525621a9bec796d52dc9cc71f638f4a43de1e6d0
MD5=6f0b4e07bc4fde64c9799e2cc6ceb93
SM3=34e6e9c5b94ce9b8f6e53c8dd58eae6dbb912bcf7bf194966678589739dee35
3、对称加密
package com.sky.wsp.guo.mi;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.Security;
/**
* @author [email protected]
* @desccription 对称加密算法
* @date 2025/04/09
*/
public class CipherTest {
@BeforeAll
public static void setUpBeforeClass() throws Exception {
Security.addProvider(new BouncyCastleProvider());
System.out.println("BouncyCastle Provider is now available");
}
@Test
public void testCipher() throws Exception {
test("AES", 128, 16);
test("DES", 56, 8);
test("SM4", 128, 16);
}
public void test(String algorithm, int keysize, int ivLength) throws Exception {
// 生成密钥
KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
keyGen.init(keysize);
SecretKey secretKey = keyGen.generateKey();
// 生成初始化向量 (IV)
byte[] iv = new byte[ivLength];
new java.security.SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// 原文
String plainText = "I love China!";
// 加密
Cipher encryptCipher = Cipher.getInstance(algorithm + "/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] cipherText = encryptCipher.doFinal(plainText.getBytes());
System.out.println(algorithm + "加密后的内容: " + HexUtil.encode(cipherText));
// 解密
Cipher decryptCipher = Cipher.getInstance(algorithm + "/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decryptedText = decryptCipher.doFinal(cipherText);
System.out.println(algorithm + "解密后的内容: " + new String(decryptedText));
}
}
输出结果:
AES加密后的内容: 4ebd67ebea46b3e149376b8a9fa5c282
AES解密后的内容: I love China!
DES加密后的内容: 96913faab9fa3457473c671f4e1301
DES解密后的内容: I love China!
SM4加密后的内容: 117d7d3d36e5e38c8192ee975a7d9d0
SM4解密后的内容: I love China!
4、非对称加密
package com.sky.wsp.guo.mi;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.crypto.Cipher;
import java.security.*;
/**
* @author [email protected]
* @desccription 非对称加密算法
* @date 2025/04/09
*/
public class AsyCipherTest {
@BeforeAll
public static void setUpBeforeClass() throws Exception {
Security.addProvider(new BouncyCastleProvider());
System.out.println("BouncyCastle Provider is now available");
}
@Test
public void testCipher() throws Exception {
KeyPair keyPair = getKeyPair("RSA", 2048);
test("RSA", keyPair);
keyPair = getKeyPair("EC", 256);
test("SM2", keyPair);
}
public void test(String algorithm, KeyPair keyPair) throws Exception {
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
byte[] data = "I love China!".getBytes();
Cipher c1 = Cipher.getInstance(algorithm);
// SM2作为椭圆曲线公钥密码算法,明确规定公钥用于加密,私钥用于解密
// RSA虽然常规用法是公钥加密、私钥解密,但其数学原理允许私钥持有者用私钥加密数据
c1.init(Cipher.ENCRYPT_MODE, publicKey);
c1.update(data);
byte[] cipherData = c1.doFinal();
System.out.printf(algorithm + "加密值:%s%n", HexUtil.encode(cipherData));
Cipher c2 = Cipher.getInstance(algorithm);
c2.init(Cipher.DECRYPT_MODE, privateKey);
c2.update(cipherData);
byte[] data2 = c2.doFinal();
System.out.printf(algorithm + "解密值: %s%n", new String(data2));
}
private KeyPair getKeyPair(String algorithm, int keysize) throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
keyPairGenerator.initialize(keysize);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return keyPair;
}
}
输出结果:
RSA加密值:59173e6f6e154eaa3fcd80faec416121d517fde9dffbfcd64f44586e8496aac2ed609326894d9c64b5cb9e0f7a12a28b4abff16d5c3f649c376d239701842b7d5c513ba3dca5a9959ceff3ca0bc244a712eca66c9156e1c6113518c7963438693485d5ed68c3d4335945c5613307411ad59756b336963b4c1e5b4b9c01da9d0faa296acde7a84558d16da5ee2cb546b83d6ab98fc98d86a5bb859d37ea15adfce661b67d5abb99d8d9ba3092ff579c2ad545d6bf605eb27150125939b88a3d3c26492e72ffd56d1b204b2438507047879e463ce87254a51a77a2ea9651282c5c37656ff161b8054c5c1164719bee63a131321410eb1c74cc
RSA解密值: I love China!
SM2加密值:461b08802c95cdf8fb525f8adbe299a85ec8e31a25b114a2e20948df496bdadc57afbe2dcb1287799c5c54c68512a8cb2eca34996ce910f8b342c2c23d398958dec4ae5cfb335fdbf1bb94d5e49c22d1f037fad57680a4b1cd6cb7c19b57ac3e373258cf6bc8f315eeec
SM2解密值: I love China!
5、HEX工具类
package com.sky.wsp.guo.mi;
/**
* @author [email protected]
* @desccription 工具类:字节数组转16进制字符串
* @date 2025/4/9
*/
public class HexUtil {
public static String encode(byte data[]) {
StringBuffer strBuffer = new StringBuffer();
for (int i = 0; i < data.length; i++) {
strBuffer.append(Integer.toHexString(0xff & data[i]));
}
return strBuffer.toString();
}
}
四、SpringSecurity使用国密算法
代码仓库:【guo-mi】
1、方法一、使用数字摘要
自定义SM3PasswordEncoder
package com.sky.wsp.guo.mi.utils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;
import java.security.Security;
public class SM3PasswordEncoder implements PasswordEncoder {
private static SM3Digest digest;
static {
Security.addProvider(new BouncyCastleProvider());
digest = new SM3Digest();
}
@Override
public String encode(CharSequence rawPassword) {
byte[] rawBytes = rawPassword.toString().getBytes();
digest.update(rawBytes, 0, rawBytes.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return Hex.toHexString(hash); // 将字节数组转换为十六进制字符串
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encode(rawPassword).equals(encodedPassword);
}
}
配置到SpringSecurity
package com.sky.wsp.guo.mi.config;
import com.sky.wsp.guo.mi.utils.SM3PasswordEncoder;
import com.sky.wsp.guo.mi.utils.SM4PasswordEncoder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
// @EnableWebSecurity: 启用SpringSecurity
@EnableWebSecurity
// @EnableMethodSecurity: 启用方法注解鉴权
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/index").permitAll() // 公开访问
.anyRequest().authenticated() // 其他接口需认证
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults()); // 使用 HTTP Basic 认证
return http.build();
}
@Bean
UserDetailsService inMemoryUserDetailsManager(PasswordEncoder passwordEncoder) {
String generatedPassword = passwordEncoder.encode("123456");
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(generatedPassword).roles("ADMIN").build(),
User.withUsername("user")
.password(generatedPassword).roles("USER").build()
);
}
@Bean
PasswordEncoder passwordEncoder() {
return new SM3PasswordEncoder();
}
}
2、方法二、使用对称加密
自定义SM4PasswordEncoder
package com.sky.wsp.guo.mi.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
public class SM4PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return SM4Util.enc(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return SM4Util.dec(encodedPassword).equals(rawPassword.toString());
}
private class SM4Util {
private static Cipher encryptCipher;
private static Cipher decryptCipher;
static {
Security.addProvider(new BouncyCastleProvider());
try {
// 注意: key的秘钥位数需要是 16因为 128位/8 = 16
SecretKeySpec secretKey = new SecretKeySpec("1234567890AbCdEf".getBytes(), "SM4");
// 生成初始化向量 (IV)
IvParameterSpec ivSpec = new IvParameterSpec("AbCdEf1234567890".getBytes());
// 加密
encryptCipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
// 解密
decryptCipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String enc(String str) {
try {
byte[] cipherText = encryptCipher.doFinal(str.getBytes());
return HexUtil.encode(cipherText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String dec(String str) {
try {
byte[] decodeBytes = HexUtil.decode(str);
byte[] decryptedText = decryptCipher.doFinal(decodeBytes);
return new String(decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
}
配置到SpringSecurity
package com.sky.wsp.guo.mi.config;
import com.sky.wsp.guo.mi.utils.SM3PasswordEncoder;
import com.sky.wsp.guo.mi.utils.SM4PasswordEncoder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
// @EnableWebSecurity: 启用SpringSecurity
@EnableWebSecurity
// @EnableMethodSecurity: 启用方法注解鉴权
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/index").permitAll() // 公开访问
.anyRequest().authenticated() // 其他接口需认证
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults()); // 使用 HTTP Basic 认证
return http.build();
}
@Bean
UserDetailsService inMemoryUserDetailsManager(PasswordEncoder passwordEncoder) {
String generatedPassword = passwordEncoder.encode("123456");
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(generatedPassword).roles("ADMIN").build(),
User.withUsername("user")
.password(generatedPassword).roles("USER").build()
);
}
@Bean
PasswordEncoder passwordEncoder() {
// return new SM3PasswordEncoder();
return new SM4PasswordEncoder();
}
}