MyBatis数据库脱敏

一、国密SM4加密

<dependency>
     <groupId>org.bouncycastle</groupId>
     <artifactId>bcprov-jdk15on</artifactId>
     <version>1.70</version>
</dependency>

1.1 工具类

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Arrays;

public class Sm4Util {

    private static final String ALGORITHM = "SM4";
    private static final String TRANSFORMATION_ECB = ALGORITHM + "/ECB/PKCS7Padding";
    private static final String TRANSFORMATION_CBC = ALGORITHM + "/CBC/PKCS7Padding";
    private static final String TRANSFORMATION_ECB_NoPADDING = ALGORITHM + "/ECB/NoPadding";
    private static final String TRANSFORMATION_CBC_NoPADDING = ALGORITHM + "/CBC/NoPadding";


    private static final String ALGORITHM_HMAC_SM4 = "HmacSHA256";
    private static final String PROVIDER_NAME = "BC";


    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 签名
     */
    public static byte[] sign(byte[] key, byte[] data) throws Exception {
        Mac mac = Mac.getInstance(ALGORITHM_HMAC_SM4, PROVIDER_NAME);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_HMAC_SM4);
        mac.init(secretKeySpec);
        return mac.doFinal(data);
    }

    /**
     * 验证签名
     */
    public static boolean verify(byte[] key, byte[] data, byte[] signature) throws Exception {
        Mac mac = Mac.getInstance(ALGORITHM_HMAC_SM4, PROVIDER_NAME);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_HMAC_SM4);
        mac.init(secretKeySpec);
        byte[] computedSignature = mac.doFinal(data);
        return Arrays.equals(computedSignature, signature);
    }

    /**
     * /CBC/PKCS7Padding 加密
     */
    public static byte[] encryptByCbcPkcs7Padding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        // CBC模式需要一个初始化向量
        byte[] iv = new byte[16]; // 随机生成或指定
        Arrays.fill(iv, (byte) 0x00); // 示例中填充为0
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher.doFinal(data);
    }

    /**
     * /CBC/PKCS7Padding 解密
     */
    public static byte[] decryptByCbcPkcs7Padding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        byte[] iv = new byte[16]; // 随机生成或指定
        Arrays.fill(iv, (byte) 0x00); // 示例中填充为0
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher.doFinal(data);
    }

    /**
     * /ECB/PKCS7Padding 加密
     */
    public static byte[] encryptByEcbPkcs7Padding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        return cipher.doFinal(data);
    }

    /**
     * /ECB/PKCS7Padding 解密
     */
    public static byte[] decryptByEcbPkcs7Padding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        return cipher.doFinal(data);
    }

    /**
     * /ECB/NoPadding 加密
     */
    public static byte[] encryptByEcbPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB_NoPADDING , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        return cipher.doFinal(data);
    }

    /**
     * /ECB/NoPadding 解密
     */
    public static byte[] decryptByEcbPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB_NoPADDING , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        return cipher.doFinal(data);
    }

    /**
     * /CBC/NoPadding 加密
     */
    public static byte[] encryptByCbcPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC_NoPADDING , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher.doFinal(data);
    }

    /**
     * /CBC/NoPadding 解密
     */
    public static byte[] decryptByCbcPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC_NoPADDING , "BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
        byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher.doFinal(data);
    }


}

1.2 SM4Util使用测试

@SpringBootTest(classes = ServerApplication.class)
public class Sm4UtilTest {

    private static final byte[] KEY = KeyGenUtil.generate128BitKey(); // 16字节128位的密钥
    private static final String DATA = "This is a test message for SM4 encryption.";
    //16字节或16字节的整数倍字符串,无填充模式NoPadding要求
    private static final String DATA16 = "HelloWorldYesYes";

    @Test
    public void testEncryptDecryptEcbPkcs7Padding() throws Exception {
        byte[] encrypted = Sm4Util.encryptByEcbPkcs7Padding(KEY, DATA.getBytes());
        //签名
        byte[] signatureData = Sm4Util.sign(KEY, encrypted);
        //验证
        boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
        System.out.println("签名验证结果:" + isValid);
        byte[] decrypted = Sm4Util.decryptByEcbPkcs7Padding(KEY, encrypted);
        System.out.println("解密后数据" + new String(decrypted));
    }

    @Test
    public void testEncryptDecryptCbcPkcs7Padding() throws Exception {
        byte[] encrypted = Sm4Util.encryptByCbcPkcs7Padding(KEY, DATA.getBytes());
        //签名
        byte[] signatureData = Sm4Util.sign(KEY, encrypted);
        //验证
        boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
        System.out.println("签名验证结果:" + isValid);
        byte[] decrypted = Sm4Util.decryptByCbcPkcs7Padding(KEY, encrypted);
        System.out.println("解密后数据" + new String(decrypted));
    }


    @Test
    public void testEncryptDecryptEcbPkcs7PaddingNoPadding() throws Exception {
        byte[] encrypted = Sm4Util.encryptByEcbPkcs7PaddingNoPadding(KEY, DATA16.getBytes());
        byte[] signatureData = Sm4Util.sign(KEY, encrypted);
        boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
        System.out.println("签名验证结果:" + isValid);
        byte[] decrypted = Sm4Util.decryptByEcbPkcs7PaddingNoPadding(KEY, encrypted);
        System.out.println("解密后数据" + new String(decrypted));
    }

    @Test
    public void testEncryptDecryptCbcPkcs7PaddingNoPadding() throws Exception {
        byte[] encrypted = Sm4Util.encryptByCbcPkcs7PaddingNoPadding(KEY, DATA16.getBytes());
        byte[] signatureData = Sm4Util.sign(KEY, encrypted);
        boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
        System.out.println("签名验证结果:" + isValid);
        byte[] decrypted = Sm4Util.decryptByCbcPkcs7PaddingNoPadding(KEY, encrypted);
        System.out.println("解密后数据" + new String(decrypted));
    }
}

二、MyBatis使用SM4加解密

2.1 创建表结构

DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
	id BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
	name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '姓名'
);

SELECT * FROM user_info;
INSERT INTO user_info (name) VALUES ('c4f6371b67fac0f224c5b44456d3c71f');

2.2 pom

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.5.2</version>
</dependency>

2.3 配置连接地址

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&nullCatalogMeansCurrent=true
    driver-class-name: com.mysql.cj.jdbc.Driver

2.4 编写mapper和xml

public interface UserInfoMapper {

    UserInfo getUserInfoById(@Param("id") Long id);

    List<UserInfo> list();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zn.opit.demo.mapper.UserInfoMapper">

    <select id="getUserInfoById" resultType="com.zn.opit.demo.entity.UserInfo">
        SELECT id, name FROM user_info WHERE id = #{id}
    </select>

    <select id="list" resultType="com.zn.opit.demo.entity.UserInfo">
        SELECT id, name FROM user_info
    </select>
</mapper>

2.5 编写SQL拦截器对查询字段解密

@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
        }
)
public class EncryptInterceptor implements Interceptor {

    private static final byte[] KEY = Sm4Util.decodeHex("6c376375e8e3979f91f805faa860dd43");// KeyGenUtil.generate128BitKey(); // 16字节128位的密钥

    @SuppressWarnings(value = "unchecked")
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObj = invocation.proceed();
        ArrayList<UserInfo> list = (ArrayList<UserInfo>) resultObj;
        for (UserInfo userInfo : list) {
            byte[] decrypted = Sm4Util.decryptByEcbPkcs7Padding(KEY, Hex.decodeHex(userInfo.getName()));
            userInfo.setName(new String(decrypted));
        }
        return list;
    }

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

2.6 配置解密拦截器

@Configuration
public class MyBatisConfig {

    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer() {
        return configuration -> {
            // 在这里可以修改MyBatis的全局配置
            configuration.setMapUnderscoreToCamelCase(true);
            // 添加自定义的插件等
            // 加解密插件
            EncryptInterceptor encryptInterceptor = new EncryptInterceptor();

            //添加插件-configuration.addInterceptor(...);
            configuration.addInterceptor(encryptInterceptor);
        };
    }
}

2.7 测试执行

@SpringBootTest(classes = ServerApplication.class)
public class MyBatisInterceptorTest {

    @Resource
    private UserInfoMapper userInfoMapper;

    @Test
    public void test() {
        UserInfo userInfoById = userInfoMapper.getUserInfoById(1L);
        System.out.println(userInfoById.getName());
        System.out.println("====================");
        List<UserInfo> list = userInfoMapper.list();
        list.forEach(item -> System.out.println(item.getName()));
    }
}

在这里插入图片描述
在这里插入图片描述

### MyBatis Plus 数据脱敏存储到数据库实现方法 为了确保敏感数据的安全性和隐私保护,在使用 MyBatis Plus 进行数据库操作时,可以采用多种技术手段来实现数据的自动脱敏处理。以下是具体的技术方案: #### 1. 利用 MyBatis 拦截器机制 通过自定义 `Interceptor` 来拦截 SQL 执行过程中的参数设置阶段,可以在实际执行前对指定字段的内容进行转换或加密处理[^1]。 ```java @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class DataMaskingInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(DataMaskingInterceptor.class); public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement)invocation.getArgs()[0]; Object parameterObject = invocation.getArgs()[1]; MetaObject metaObject = SystemMetaObject.forObject(parameterObject); // 对特定字段应用脱敏逻辑 String maskedValue = maskSensitiveData(metaObject.getValue("sensitiveField").toString()); metaObject.setValue("sensitiveField", maskedValue); return invocation.proceed(); } private String maskSensitiveData(String original){ // 实现具体的脱敏算法 return "***"; } } ``` 此代码片段展示了如何创建一个简单的拦截器用于修改传入实体对象内的某些属性值。这里假设有一个名为 `"sensitiveField"` 的字段需要被遮蔽显示。 #### 2. 结合注解简化配置工作量 除了编写拦截器外,还可以利用 Java 注解特性进一步减少编码复杂度。对于那些经常变动或者新增的数据表结构来说尤为有用[^3]。 ```java @TableField(fill= FieldFill.INSERT_UPDATE) @DataMasked(maskType = MaskType.PARTIAL_MASKED) private String phoneNumber; ``` 上述例子中引入了一个假定存在的 `@DataMasked` 自定义注解,它能够指示哪些列应该接受特殊处理以及采取何种形式的掩码策略(比如部分隐藏)。当应用程序启动时会扫描这些标记并注册相应的处理器实例给全局上下文中供后续调用链路识别匹配。 #### 3. XML 映射文件增强功能支持 如果项目里已经存在大量的 Mapper 接口及其对应的 XML 文件,则可以直接在此基础上扩展而无需重构现有业务逻辑层代码[^2]。 ```xml <resultMap id="BaseResultMap" type="com.example.entity.User"> <!-- 其他映射 --> <result column="phone_number" property="phoneNumber"/> </resultMap> <select id="selectUserById" resultMap="BaseResultMap"> SELECT * FROM users WHERE user_id=#{id}; </select> ``` 尽管这段XML看起来并没有特别之处,但是配合前面提到过的插件体系之后就可以轻松完成读写分离下的动态替换效果——即查询出来的记录经过适当调整后再返回给前端展示;反之亦然地保存之前也会先按照预设规则加工一遍原始输入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值