国产SM4加密与自定义注解实现敏感字段加密相结合

国产SM4加密与自定义注解实现敏感字段加密相结合,第1张

国产SM4加密与自定义注解实现敏感字段加密相结合

目录

1.开发环境与工具

2.自定义注解接口:

2.1敏感类的注解接口(SensitiveData)

2.2敏感字段的注解接口(SensitiveField)

2.3注解接口的用法

3.注解注入后需要写加解密的拦截器

3.1加密拦截器(EncryptInterceptor)

3.2解密拦截器(DecryptInterceptor)

4.拦截器用到的SM4相关类

4.1 SM4加解密组件(Sm4crypto)

4.2 SM4工具类(Sm4Utils)

4.3国产加密相关依赖包

5.大功告成!


1.开发环境与工具

                java,idea,mybatis-plus,spring boot

2.自定义注解接口: 2.1敏感类的注解接口(SensitiveData)
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

@documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface SensitiveData {
}
2.2敏感字段的注解接口(SensitiveField)
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

@documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface SensitiveField {
}
2.3注解接口的用法

        举个例子,用户表(SysUser)中的手机(phone)字段需要进行加密处理,则需要在SysUser上加@SensitiveData进行注解,另外在phone字段上加@SensitiveField进行注解。

@SensitiveData
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_user")
public class SysUser extends baseEntity {

    
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    
    @Excel(name = "账号", width = 20)
    private String account;

    
    private String password;

    
    @Excel(name = "姓名", width = 20)
    private String name;


    
    @SensitiveField
    @Excel(name = "手机", width = 30)
    private String phone;

    
    private String salt;


}
3.注解注入后需要写加解密的拦截器

        以上注解写完之后,我们需要写拦截器。让数据在后台存入数据库时进行拦截,拦截后将需要加密的字段在存入数据库之前使用SM4加密后再进行保存。同样的,在我们将数据从数据库中读出来时也需要进行拦截,拦截后使用SM4解密后拿到原值。

3.1加密拦截器(EncryptInterceptor)
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

@Component
@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})
})
public class EncryptInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        // 获取该sql语句的类型,例如update,insert
        String methodName = invocation.getMethod().getName();
        // 获取该sql语句放入的参数
        Object parameter = invocation.getArgs()[1];

        
        if (StringUtils.equalsIgnoreCase("query", methodName)) {

            
            Object result = invocation.proceed(); //执行请求方法,并将所得结果保存到result中
            if (result instanceof ArrayList) {
                ArrayList resultList = (ArrayList) result;
                for (int i = 0; i < resultList.size(); i++) {
                    SensitiveData sensitiveData = AnnotationUtils.findAnnotation(resultList.get(i).getClass(), SensitiveData.class);
                    if (Objects.nonNull(sensitiveData)) {
                        Field[] declaredFields = resultList.get(i).getClass().getDeclaredFields();


                    }
                }
            }
            return result;
        }

        // 拦截 Executor 的 update 方法 生成sql前将 tenantId 设置到实体中
        if (StringUtils.equalsIgnoreCase("update", methodName) ||
                StringUtils.equalsIgnoreCase("insert", methodName)) {

            Class type = statement.getResultMaps().get(0).getClass();
            if(parameter instanceof Map){
                parameter = ((Map) parameter).get("param1");
            }
            SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameter.getClass(), SensitiveData.class);
            if (Objects.nonNull(sensitiveData)) {

                    // 对参数内含注解的字段进行加密
                Field[] declaredFields = parameter.getClass().getDeclaredFields();
                Sm4crypto.encrypt(declaredFields, parameter);

            }
        }

        return invocation.proceed();
    }



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

    
    @Override
    public void setProperties(Properties properties) {
    }
}
3.2解密拦截器(DecryptInterceptor)
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;

@Component
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
})
public class DecryptInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //取出查询的结果
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        //基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    //逐一解密
                    Sm4crypto.decrypt(result);
                }
            }
            //基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                Sm4crypto.decrypt(resultObject);
            }
        }
        return resultObject;
    }

    private boolean needToDecrypt(Object object) {
        Class objectClass = object.getClass();
        SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
        return Objects.nonNull(sensitiveData);
    }


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

    @Override
    public void setProperties(Properties properties) {

    }
}
4.拦截器用到的SM4相关类 4.1 SM4加解密组件(Sm4crypto)
import cn.stylefeng.guns.core.util.Sm4Utils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Objects;

@Component
public class Sm4crypto {

    public static final String DEFAULT_KEY = "自己的加解密key";

    
    public static  T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                //暂时只实现String类型的加密
                if (object instanceof String) {
                    String value = (String) object;
                    String encryptValue = null;
                    try {
                        //加密  这里使用SM4加密工具
                        encryptValue = Sm4Utils.encryptEcb(DEFAULT_KEY, value);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    field.set(paramsObject, encryptValue);
                }
            }
        }
        return paramsObject;
    }
    
    public static  T decrypt(T result) throws IllegalAccessException {
        //取出resultType的类
        Class resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(result);
                //只支持String的解密
                if (object instanceof String) {
                    String value = (String) object;
                    String decryptValue = null;
                    try {
                        //对注解的字段进行逐一解密
                        decryptValue = Sm4Utils.decryptEcb(DEFAULT_KEY, value);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }


                    field.set(result, decryptValue);
                }
            }
        }
        return result;
    }
}
4.2 SM4工具类(Sm4Utils)
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;


public class Sm4Utils {

    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;

    
    private static Cipher generateEcbCipher(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);
        return cipher;
    }

    
    public static byte[] generateKey() throws Exception {
        return generateKey(DEFAULT_KEY_SIZE);
    }

    
    public static byte[] generateKey(int keySize) throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(keySize, new SecureRandom());
        return kg.generateKey().getEncoded();
    }

    
    public static String encryptEcb(String hexKey, String paramStr) throws Exception {
        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;
    }

    
    public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    
    public static String decryptEcb(String hexKey, String cipherText) throws Exception {
        // 用于接收解密后的字符串
        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;
    }

    
    public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    
    public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
        // 用于接收校验结果
        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;
    }
}
4.3国产加密相关依赖包

                org.bouncycastle
                bcprov-jdk15on
                1.57
            
            
                org.bouncycastle
                bcprov-ext-jdk15on
                1.57
            
5.大功告成!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/5564015.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-14
下一篇2022-12-14

发表评论

登录后才能评论

评论列表(0条)

    保存