java 数据加密存储

  • 定义注解用来标识字段是否需要加密
package com.common.annotation;

import java.lang.annotation.*;
/**
 * @Auther: ZHANG PU
 * @Date: 2025/11/13 09:30
 * @Description: 需要加密的字段 注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptField {
}
  • 定义一个mybatis拦截器,在执行sql之前对需要加密或者解密的数据进行处理
package com.framework.interceptor;


import com.ruoyi.common.annotation.EncryptField;
import com.ruoyi.common.utils.Sm4Util;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;

/**
 * @Auther: ZHANG PU
 * @Date: 2025/11/13 09:29
 * @Description: mybatis 拦截器
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
public class EncryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println("进入mybatis 加密拦截器------------------------------------");
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        if (ms.getSqlCommandType() == SqlCommandType.INSERT || ms.getSqlCommandType() == SqlCommandType.UPDATE)
        {
            // 插入/更新时加密字段
            encryptField(parameter);
        }
        // 执行原方法(查询/更新)
        Object result = invocation.proceed();
        if (ms.getSqlCommandType() == SqlCommandType.SELECT)
        {
            // 查询时解密字段(结果可能是单个对象或集合)
            decryptField(result);
        }
        // 对查询参数中的加密字段进行加密
        if (ms.getSqlCommandType() == SqlCommandType.SELECT)
        {
            encryptField(parameter);
        }
        return result;
    }

    /**
     * 功能描述: 加密参数中的标记字段
     *
     * @param parameter
     * @author zhang pu
     * @date 15:48 2025/11/13
     */
    private void encryptField(Object parameter) throws Exception {
        if (parameter == null)
        {
            return;
        }
        Field[] fields = parameter.getClass().getDeclaredFields();
        for (Field field : fields)
        {
            if (field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
            {
                field.setAccessible(true);
                String value = (String) field.get(parameter);
                if (value != null)
                {
                    // 加密后重新设置
                    field.set(parameter, Sm4Util.encryptTextSm4(Sm4Util.MY, value));
                }
            }
        }
    }

    /**
     * 功能描述: 解密结果中的标记字段
     *
     * @param result
     * @author zhang pu
     * @date 15:48 2025/11/13
     */
    private void decryptField(Object result) throws Exception {
        if (result == null)
        {
            return;
        }
        // 处理集合
        if (result instanceof List<?>)
        {
            for (Object item : (List<?>) result)
            {
                decryptSingleField(item);
            }
        } else
        {
            // 处理单个对象
            decryptSingleField(result);
        }
    }

    private void decryptSingleField(Object obj) throws Exception {
        if (obj == null)
        {
            return;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields)
        {
            if (field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
            {
                field.setAccessible(true);
                String value = (String) field.get(obj);
                if (value != null)
                {
                    // 解密后重新设置
                    field.set(obj, Sm4Util.decodeTextSm4(Sm4Util.MY, value));
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置加密参数(如密钥)
    }
}
  • 编写国产加密SM4共工具
<!--国产加密-->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.54</version>
</dependency>
package com.common.utils;

import org.bouncycastle.crypto.digests.SM3Digest;
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.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @description: SM4加密
 * @author: 张璞
 * @date 10:20 2022/9/13
 */
public class Sm4Util {
    // 密文前缀(用于标识已加密)
    private static final String ENC_PREFIX = "ENC(";
    private static final String ENC_SUFFIX = ")";

    static
    {
        Security.addProvider(new BouncyCastleProvider());
    }
    private static final String ENCODING = "UTF-8";
    public static final String ALGORITHM_NAME ="SM4";
    /**
     * 功能描述: 加密算法/分组加密模式/分组填充方式\PKCS5Padding-以8个字节为一组进行分组加密\定义分组加密模式使用:PKCS5Paddings
     */
    public static final String ALGORITHM_NAME_CBC_PADDING ="SM4/CBC/PKCS5Padding";
    /**
     * 功能描述: 128-32位16进制;256-64位16进制
     */
    public static final int DEFAULT_KEY_SIZE = 128;

    /**
     * 密钥
     */
    public static final String MY="xxx";


    /**
     * 功能描述: 测试
     * @author zhang pu
     * @date 10:31 2022/9/13
     */
    public static void main(String[] args) throws Exception {
        String s = generateKeyString();
        System.out.println("密钥:"+s);
        String uuid= UUID.randomUUID().toString();
        System.out.println("带加密数据:"+uuid);
        //sm4对称加密
        String s1 = encryptTextSm4(s, uuid);
        System.out.println("加密数据:"+s1);
        String s2 = decodeTextSm4(s, s1);
        System.out.println("解密数据:"+s2);
        //sm3加密
       // String s3 = encryptTextSm3(uuid);
       // System.out.println(verify(uuid,s3));
    }

    /**
     * 功能描述: sm4加密 加密模式:CBC
     * 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位
     * @author zhang pu
     * @date 10:31 2022/9/13
     * @param hexKey   16进制密钥(忽略大小写)
     * @param paramStr 待加密字符串
     * 返回16进制的加密字符串
     */
    public static String encryptTextSm4(String hexKey, String paramStr) throws Exception {
        if(StringUtils.isBlank(paramStr))
        {
            return null;
        }
        // 若已加密(包含前缀),直接返回(避免重复加密)
        if (isEncrypted(paramStr))
        {
            return paramStr;
        }
        String result = "";
        // 16进制字符串-->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        // String-->byte[]
        byte[] srcData = paramStr.getBytes(ENCODING);
        // 加密后的数组
        byte[] cipherArray = encrypt_Cbc_Padding(keyData, srcData);
        // byte[]-->hexString
        result = ByteUtils.toHexString(cipherArray);

        // 添加密文前缀
        return ENC_PREFIX + result + ENC_SUFFIX;
    }

    /**
     * 功能描述: sm4解密 解密模式:采用CBC
     * @author zhang pu
     * @date 10:31 2022/9/13
     * @param hexKey 16进制密钥
     * @param text   16进制的加密字符串(忽略大小写)
     */
    public static String decodeTextSm4(String hexKey, String text) throws Exception {
        if(StringUtils.isBlank(text))
        {
            return null;
        }
        // 若未加密(不包含前缀),直接返回,防止反复解密
        if (!isEncrypted(text))
        {
            return text;
        }

        // 移除密文前缀
        String realCiphertext = text.substring(ENC_PREFIX.length(), text.length() - ENC_SUFFIX.length());
        String result = "";
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        byte[] resultData = ByteUtils.fromHexString(realCiphertext);
        // 解密
        byte[] srcData = decrypt_Cbc_Padding(keyData, resultData);
        result = new String(srcData, ENCODING);
        return result;
    }


    /**
     * 功能描述: 获取ENC(...) 并进行解密
     *
     * @author zhang pu
     * @date 15:28 2025/11/13
     * @param oldStr
     */
    public static String getJmStr(String oldStr) {
        if (Objects.isNull(oldStr))
        {
            return null;
        }
        try
        {
            // 正则表达式:匹配 ENC(...) 格式,其中 (...) 不含右括号
            Pattern pattern = Pattern.compile("ENC\\([^)]*\\)");
            Matcher matcher = pattern.matcher(oldStr);
            List<String> encList = new ArrayList<>();
            while (matcher.find())
            {
                // 提取匹配到的 ENC(...) 子串
                encList.add(matcher.group());
            }
            for (String s : encList)
            {
                String s1 = Sm4Util.decodeTextSm4(Sm4Util.MY, s);
                oldStr = oldStr.replaceAll(Pattern.quote(s), s1);
            }
        } catch (Exception e)
        {
        }
        return oldStr;
    }

    /**
     * 功能描述: sm3加密
     * 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
     * @author zhang pu
     * @date 10:39 2022/9/13
     * @param text  内容
     */
    public static String  encryptTextSm3(String text) throws UnsupportedEncodingException {
        byte[] bytes = text.getBytes(ENCODING);
        byte[] hash = hash(bytes);
        String s = ByteUtils.toHexString(hash);
        return s;
    }

    public static byte[] hash(byte[] srcData){
        SM3Digest digest=new SM3Digest();
        digest.update(srcData,0,srcData.length);
        byte[] bytes = new byte[digest.getDigestSize()];
        digest.doFinal(bytes,0);
        return bytes;
    }

    /**
     * 功能描述:判断元数据与加密数据是否一致
     * @author zhang pu
     * @date 10:56 2022/9/13
     * 参数 srcStr  元数据
     * 参数 sm3HexString 加密过的数据
     */
    public static boolean verify(String srcStr,String sm3HexString) throws Exception{
        boolean flag=false;
        byte[] srcStrData = srcStr.getBytes(ENCODING);
        byte[] sm3HexStringData = ByteUtils.fromHexString(sm3HexString);
        byte[] hash = hash(srcStrData);
        if(Arrays.equals(hash,sm3HexStringData))
        {
            flag= true;
        }
        return flag;
    }

    /**
     * 功能描述: 生成密钥
     * @author zhang pu
     * @date 10:30 2022/9/13
     */
    public static String generateKeyString() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(DEFAULT_KEY_SIZE, new SecureRandom());
        byte[] encoded = kg.generateKey().getEncoded();
        return ByteUtils.toHexString(encoded);
    }

    /**
     * 加密模式之CBC
     * @param key
     * @param data
     * @return
     * @throws Exception
     * @explain
     */
    public static byte[] encrypt_Cbc_Padding(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateCbcCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    private static Cipher generateCbcCipher(String algorithmName, int mode, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key, generateIV());
        return cipher;
    }

    /**
     * 功能描述: 初始化算法参数
     * @author zhang pu
     * @date 10:33 2022/9/13
     */
    public static AlgorithmParameters generateIV() throws Exception {
        byte[] iv = new byte[16];
        //设置数据全为0
        Arrays.fill(iv, (byte) 0x00);
        AlgorithmParameters params = AlgorithmParameters.getInstance(ALGORITHM_NAME);
        params.init(new IvParameterSpec(iv));
        return params;
    }

    /**
     * 功能描述: cbc模式解密
     * @author zhang pu
     * @date 10:33 2022/9/13
     */
    public static byte[] decrypt_Cbc_Padding(byte[] key, byte[] cipherText) throws Exception {
        Cipher cipher = generateCbcCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    /**
     * 功能描述: 判断是否已加密 是否包含前缀
     *
     * @author zhang pu
     * @date 10:40 2025/11/13
     * @param value
     */
    public static boolean isEncrypted(String value) {
        return value != null && value.startsWith(ENC_PREFIX) && value.endsWith(ENC_SUFFIX);
    }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐