SpringBoot集成国密算法

一、国密算法介绍

参考: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();
    }
}
相关推荐
技术宝哥1 小时前
解决 Spring Boot 启动报错:数据源配置引发的启动失败
spring boot·后端·mybatis
码农周2 小时前
Spring Boot 启动后自动执行 Service 方法终极指南
java·spring boot·后端
路在脚下@2 小时前
Spring Boot项目中结合MyBatis实现MySQL的自动主从切换
spring boot·mysql·mybatis
日月星辰Ace3 小时前
@SpringBootTest @DirtiesContext
java·spring boot
howard20053 小时前
3.2.2.3 Spring Boot配置拦截器
spring boot·自定义spring mvc配置·配置拦截器
JZC_xiaozhong7 小时前
制造企业如何通过实现数据统一?
大数据·spring boot·后端·制造·mdm·主数据管理·数据集成与应用集成
努力的搬砖人.10 小时前
搭建一个Spring Boot聚合项目
java·spring boot·后端
xiezhr10 小时前
SpringBoot3整合SpringSecurity6(一)快速入门
java·spring boot·spring
谦行11 小时前
前端视角 Java Web 入门手册 5.3:真实世界 Web 开发——RESTful API 与 Spring MVC
java·spring boot·后端