package com.ruoyi.common.utils; import com.ruoyi.common.config.Sm4Config; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.Security; import java.util.Base64; /** * SM4加密算法工具类 * 使用固定密钥,直接从配置文件读取 * 兼容Java 8版本 */ @Component public class SM4Util { private final byte[] key; static { // 添加BouncyCastleProvider Security.addProvider(new BouncyCastleProvider()); } // 算法名称 private static final String ALGORITHM_NAME = "SM4"; private static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding"; @Autowired public SM4Util(Sm4Config sm4Config) { // 从配置文件中读取密钥并转换为字节数组 this.key = hexStringToBytes(sm4Config.getSecretKey()); if (this.key.length != 16) { throw new IllegalArgumentException("SM4密钥必须是16字节(32位十六进制字符串)"); } } /** * 将十六进制字符串转换为字节数组(兼容Java 8) */ private byte[] hexStringToBytes(String hexString) { if (hexString == null || hexString.length() % 2 != 0) { throw new IllegalArgumentException("无效的十六进制字符串"); } int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16)); } return data; } /** * 将字节数组转换为十六进制字符串(兼容Java 8) */ private String bytesToHexString(byte[] bytes) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString().toUpperCase(); } /** * 加密字符串 * * @param data 待加密的字符串 * @return Base64编码的加密结果 */ public String encrypt(String data) { if (StringUtils.isNull(data)) return null; try { Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_NAME); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new RuntimeException("SM4加密失败", e); } } /** * 解密字符串 * * @param encryptedData Base64编码的加密数据 * @return 解密后的原始字符串 */ public String decrypt(String encryptedData) { if (StringUtils.isNull(encryptedData)) return null; try { Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData); byte[] decrypted = cipher.doFinal(encryptedBytes); return new String(decrypted, StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException("SM4解密失败", e); } } /** * 加密字节数组 * * @param data 待加密的字节数组 * @return 加密后的字节数组 */ public byte[] encrypt(byte[] data) { try { Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_NAME); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); return cipher.doFinal(data); } catch (Exception e) { throw new RuntimeException("SM4加密失败", e); } } /** * 解密字节数组 * * @param encryptedData 加密的字节数组 * @return 解密后的原始字节数组 */ public byte[] decrypt(byte[] encryptedData) { try { Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); return cipher.doFinal(encryptedData); } catch (Exception e) { throw new RuntimeException("SM4解密失败", e); } } /** * 获取当前使用的密钥(十六进制格式) */ public String getKeyHex() { return bytesToHexString(key); } /** * 验证加密解密功能是否正常 */ public boolean selfCheck() { try { String testData = "SM4测试数据123"; String encrypted = encrypt(testData); String decrypted = decrypt(encrypted); return testData.equals(decrypted); } catch (Exception e) { return false; } } }