# 了解国密

即国家密码局认定的国产密码算法。主要有 SM1,SM2,SM3,SM4。密钥长度和分组长度均为 128 位

# SM1 为对称加密,其加密强度与 AES 相当。该算法不公开,调用该算法时,需要通过加密芯片的接口调用
# SM2 为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA
      # ECC 256位(SM2 采用的就是 ECC 256 位的一种)安全强度比 RSA 2048 位高,但运算速度快于RSA

# SM3 消息摘要。可以用 MD5 作为对比理解。该算法已公开。校验结果为 256 位
# SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位
  • 加密算法依赖
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk15on</artifactId>
	<version>1.70</version>
</dependency>
  • SM3工具类
public class SM3Util {
    // 静态初始化块,用于添加BouncyCastle安全提供者
    // 这是因为BouncyCastle提供了对SM3哈希算法的支持
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 对给定的数据进行SM3哈希计算
     * SM3是中国国家密码管理局发布的密码哈希算法,类似于SHA-256
     *
     * @param data 待哈希的字符串数据,不能为空
     * @return 计算得到的哈希值的十六进制字符串表示
     * @throws IllegalArgumentException 如果输入数据为空或为null
     */
    public static String hash(String data) {
        // 检查输入数据是否为空或为null,如果是,则抛出异常
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Data cannot be null or empty");
        }

        // 创建SM3哈希算法的实例
        SM3Digest digest = new SM3Digest();

        // 将输入字符串转换为 UTF-8 编码的字节数组
        byte[] bytes = data.getBytes(java.nio.charset.StandardCharsets.UTF_8);

        // 更新哈希算法的状态,为其提供数据
        digest.update(bytes, 0, bytes.length);

        // 创建一个字节数组来存储最终的哈希值
        byte[] hash = new byte[digest.getDigestSize()];

        // 执行哈希计算,将结果存储在hash数组中
        digest.doFinal(hash, 0);

        // 将字节数组形式的哈希值转换为十六进制字符串并返回
        return Hex.toHexString(hash);
    }
}
  • SM3工具类使用
// 注册
@Override
public boolean registerUser(User user) {
	// 对密码进行SM3加密
	String encryptedPassword = SM3Util.hash(user.getPassword());
	user.setPassword(encryptedPassword);

	return  mapper.registerUser(user);
}
// 登录校验
@Override
public User login(User user) {
	// 获取用户输入的密码
	String encryptedPassword = SM3Util.hash(user.getPassword());
	// 从数据库中获取用户信息
	User dbUser = mapper.getUserByUserName(user.getUsername());
	//  比较密码哈希值
	if (dbUser != null && dbUser.getPassword().equals(encryptedPassword)){
		return dbUser;
	}
	return mapper.login(user);
}
  • SM4工具类
package com.ruoyi.common.utils;

import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.exception.ServiceException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

/**
 * sm4加密算法工具类
 * sm4加密、解密与加密结果验证 可逆算法
 */
public class SM4Util {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    private static final String ENCODING = "UTF-8";
    public static final String ALGORITHM_NAME = "SM4";
    // 加密算法/分组加密模式/分组填充方式
    // PKCS5Padding-以8个字节为一组进行分组加密
    // 定义分组加密模式使用:PKCS5Padding
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
    // 128-32位16进制;256-64位16进制
    public static final int DEFAULT_KEY_SIZE = 128;

    /**
     * 当时用ECB模式的时候,和前端key一致
     */
    public static final String hexKey = "6fb7b3dcaa041c798c3798abe6f9575c";

    /**
     * 生成ECB暗号
     * @explain ECB模式(电子密码本模式:Electronic codebook)
     * @param algorithmName 算法名称
     * @param mode 模式
     * @param key
     * @return
     * @throws Exception
     */
    private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) {
        try{
            Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
            Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
            cipher.init(mode, sm4Key);
            return cipher;
        } catch (Exception ex){
            throw new ServiceException("生成ECB异常");
        }
    }

    /**
     * 自动生成密钥
     * @explain
     * @return
     */
    public static byte[] generateKey() throws Exception {
        return generateKey(DEFAULT_KEY_SIZE);
    }


    //加密******************************************
    /**
     * @explain 系统产生秘钥
     * @param keySize
     * @return
     * @throws Exception
     */
    public static byte[] generateKey(int keySize) {
        try{
            KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
            kg.init(keySize, new SecureRandom());
            return kg.generateKey().getEncoded();
        } catch (Exception ex){
            throw new ServiceException("生成密钥异常");
        }
    }

    /**
     * sm4加密
     * @explain 加密模式:ECB 密文长度不固定,会随着被加密字符串长度的变化而变化
     * @param
     * @param paramStr 待加密字符串
     * @return 返回16进制的加密字符串
     * @throws Exception
     */
    public static String encryptEcb(String paramStr) {
        try{
            String cipherText = "";
            // 16进制字符串-->byte[]
            byte[] keyData = ByteUtils.fromHexString(hexKey);
            // String-->byte[]
            byte[] srcData = paramStr.getBytes(ENCODING);
            // 加密后的数组
            byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
            // byte[]-->hexString
            cipherText = ByteUtils.toHexString(cipherArray);
            return cipherText;
        } catch (Exception e){
            throw new ServiceException("加密异常");
        }

    }

    /**
     * 加密模式之Ecb
     * @param key
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data){
        try{
			// 声称Ecb暗号,通过第二个参数判断加密还是解密
            Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(data);
        } catch (Exception ex){
            throw new ServiceException("加密异常");
        }
    }

    //解密****************************************
    /**
     * sm4解密
     * @explain 解密模式:采用ECB
     * @param
     * @param cipherText 16进制的加密字符串(忽略大小写)
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptEcb(String cipherText) {
        try{
            // 用于接收解密后的字符串
            String decryptStr = "";
            // hexString-->byte[]
            byte[] keyData = ByteUtils.fromHexString(hexKey);
            // hexString-->byte[]
            byte[] cipherData = ByteUtils.fromHexString(cipherText);
            // 解密
            byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
            // byte[]-->String
            decryptStr = new String(srcData, ENCODING);
            return decryptStr;
        } catch (Exception ex){
            throw new ServiceException("解密异常");
        }
    }

    /**
     * 解密
     * @explain
     * @param key
     * @param cipherText
     * @return
     * @throws Exception
     */
    public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) {
        try{
			// 生成Ecb暗号,通过第二个参数判断加密还是解密
            Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
            return cipher.doFinal(cipherText);
        } catch (Exception ex){
            throw new ServiceException("解密异常");
        }
    }

    /**
     * 校验加密前后的字符串是否为同一数据
     * @explain
     * @param
     * @param cipherText 16进制加密后的字符串
     * @param paramStr 加密前的字符串
     * @return 是否为同一数据
     * @throws Exception
     */
    public static boolean verifyEcb(String cipherText, String paramStr) {
        try{
            // 用于接收校验结果
            boolean flag = false;
            // hexString-->byte[]
            byte[] keyData = ByteUtils.fromHexString(hexKey);
            // 将16进制字符串转换成数组
            byte[] cipherData = ByteUtils.fromHexString(cipherText);
            // 解密
            byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
            // 将原字符串转换成byte[]
            byte[] srcData = paramStr.getBytes(ENCODING);
            // 判断2个数组是否一致
            flag = Arrays.equals(decryptData, srcData);
            return flag;
        } catch (Exception ex){
            throw new ServiceException("校验加密异常");
        }
    }
}
  • SM4工具类使用
// 获取公钥 前端用来密码加密
@GetMapping("/publicKey")
public String  publicKey() throws Exception {
	return Sm4Util.hexKey;
}


// 前端安装依赖
// npm install --save sm-crypto
// 新建sm4.js
const sm4 =require('sm-crypto').sm4
// 可以为16进制串或字节数组,要求为128比特
// const key='7f5cd501e5548914edaed6824d3ff79d'
/**
 * 加密
 * @param txt
 * @returns {*}
 */
export function encrypt(txt,key) {
  console.log(txt)
  console.log(key)
  return sm4.encrypt(txt,key);
}
 
export function decrypt(txt,key) {
  return sm4.decrypt(txt,key);
}




// login.js新增路由
export function getPublicKey() {
  return request({
    url: '/publicKey',
    method: 'get',
  })
}



// store 中 user.js 修改登入
getPublicKey() {
  return new Promise((resolve, reject) => {
	getPublicKey()
	  .then(res => {
		resolve(res)
	  })
	  .catch(error => {
		reject(error)
	  })
  })
},
Login({ commit, dispatch }, userInfo) {
  return new Promise((resolve, reject) => {
	dispatch('getPublicKey').then(res => {
	  let publicKey = res
	  const username = userInfo.username.trim()
	  //调用加密方法(传密码和公钥)
	  const password = encrypt(userInfo.password,publicKey)
	  const code = userInfo.code
	  const uuid = userInfo.uuid

	  login(username, password, code, uuid).then(res => {
		setToken(res.token)
		commit('SET_TOKEN', res.token)
		resolve()
	  }).catch(error => {
		reject(error)
	  })
	})
  })
},
  • SpringSecurity 中 使用 SM4 替换 PasswordEncoder
// 1、创建文件 Sm4PasswordEncoder
public class Sm4PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        try {
            return SM4Utils.encryptEcb(rawPassword.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        try {
            return encodedPassword.equals(SM4Utils.encryptEcb(rawPassword.toString()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}


// 2、修改登录方法,使用解密后的密码
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {
	AjaxResult ajax = AjaxResult.success();
	// 生成令牌
	String token = loginService.login(loginBody.getUsername(), 
	                                  SM4Utils.decryptEcb(loginBody.getPassword()), 
									  loginBody.getCode(),
			                          loginBody.getUuid());
	ajax.put(Constants.TOKEN, token);
	return ajax;
}

// 3、SecurityConfig 中替换 PasswordEncoder 的加密方式
@Bean
public Sm4PasswordEncoder bCryptPasswordEncoder() {
	return new Sm4PasswordEncoder();
}

// 4、修改 SecurityUtils 的2个相关方法
/**
 * 生成BCryptPasswordEncoder密码
 *
 * @param password 密码
 * @return 加密字符串
 */
public static String encryptPassword(String password) {
	// BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	// return passwordEncoder.encode();
	return SM4Utils.encryptEcb(password);
}

/**
 * 判断密码是否相同
 *
 * @param rawPassword 真实密码
 * @param encodedPassword 加密后字符
 * @return 结果
 */
public static boolean matchesPassword(String rawPassword, String encodedPassword)
{
	//BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	//return passwordEncoder.matches(rawPassword, encodedPassword);
	return SM4Utils.verifyEcb(encodedPassword, rawPassword);
}

# 国产JDK替代

阿里云部署基于Dragonwell JDK的Java运行环境 (opens new window) 安装阿里的Dragonwell替代JDK (opens new window)

# WEB服务器改造

ruoyi-vue国产化适配之东方通TongWEB (opens new window) springboot项目东方通TongWeb改造适配 (opens new window)

# 代理服务器改造

ruoyi-vue国产化适配之东方通TongHttpServer (opens new window)

# 缓存服务改造

JeecgBoot集成东方通TongRDS (opens new window)