Talk about how to customize data desensitization

preface

What is data desensitization

Data desensitization refers to the deformation of some sensitive information through desensitization rules to realize the reliable protection of sensitive privacy data

Common desensitization rules

Replace, rearrange, encrypt, truncate, mask

Good data desensitization implementation

1. Keep meaningful information before desensitization for the application after desensitization as much as possible
2. Prevent hackers from cracking to the greatest extent

Today we talk about how to customize data desensitization

Overall thinking

This example realizes desensitization by means of substitution, and then quickly realizes desensitization in combination with common framework features, such as the interceptor mechanism of mybatis or the serialization of json

Specific landing

1. Define a desensitization tool class

You can directly reference the hutool toolkit, but it only provides this tool after version 5.6 +
https://www.hutool.cn/docs/#/core/ Tools / information desensitization tools desensitized util

Otherwise, implement one by yourself, as shown below

public class DesensitizedUtils {



    /**
     * Desensitization, using the default desensitization strategy
     * <pre>
     * DesensitizedUtil.desensitized("100", DesensitizedUtils.DesensitizedType.USER_ID)) =  "0"
     * DesensitizedUtil.desensitized("Duan Zhengchun ", desensitizedutils. Desensitizedtype. Machine_name) =" Duan**“
     * DesensitizedUtil.desensitized("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)) = "5***************1X"
     * DesensitizedUtil.desensitized("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)) = "0915*****79"
     * DesensitizedUtil.desensitized("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)) = "180****1999"
     * DesensitizedUtil.desensitized("No. 289, malianwa street, Haidian District, Beijing, "desensitized utils. Desensitized type. Address") = "malianwa street, Haidian District, Beijing********“
     * DesensitizedUtil.desensitized("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)) = "d*************@gmail.com.cn"
     * DesensitizedUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********"
     * DesensitizedUtil.desensitized("Su d40000 ", desensitizedutils. Desensitizedtype. Car_license) = Su D4***0“
     * DesensitizedUtil.desensitized("11011111222233333256", DesensitizedUtils.DesensitizedType.BANK_CARD)) = "1101 **** **** **** 3256"
     * </pre>
     *
     * @param str              character string
     * @param desensitizedType Desensitization type; Chinese mobile phone can desensitization: user id, Chinese name, id card number, id number, mobile phone number, address, e-mail, password.
     * @return String after desensitization
     * @author dazer and neusoft and qiaomu
     * @since 5.6.2
     */
    public static String desensitized(CharSequence str, DesensitizedType desensitizedType) {
        if (StrUtil.isBlank(str)) {
            return StrUtil.EMPTY;
        }
        String newStr = String.valueOf(str);
        switch (desensitizedType) {
            case USER_ID:
                newStr = String.valueOf(DesensitizedUtils.userId());
                break;
            case CHINESE_NAME:
                newStr = DesensitizedUtils.chineseName(String.valueOf(str));
                break;
            case ID_CARD:
                newStr = DesensitizedUtils.idCardNum(String.valueOf(str), 1, 2);
                break;
            case FIXED_PHONE:
                newStr = DesensitizedUtils.fixedPhone(String.valueOf(str));
                break;
            case MOBILE_PHONE:
                newStr = DesensitizedUtils.mobilePhone(String.valueOf(str));
                break;
            case ADDRESS:
                newStr = DesensitizedUtils.address(String.valueOf(str), 8);
                break;
            case EMAIL:
                newStr = DesensitizedUtils.email(String.valueOf(str));
                break;
            case PASSWORD:
                newStr = DesensitizedUtils.password(String.valueOf(str));
                break;
            case CAR_LICENSE:
                newStr = DesensitizedUtils.carLicense(String.valueOf(str));
                break;
            case BANK_CARD:
                newStr = DesensitizedUtils.bankCard(String.valueOf(str));
                break;
            default:
        }
        return newStr;
    }

    /**
     * [User id] do not provide userId externally
     *
     * @return Primary key after desensitization
     */
    public static Long userId() {
        return 0L;
    }

    /**
     * [Chinese name] only displays the first Chinese character, and others are hidden as two asterisks, such as Li**
     *
     * @param fullName full name
     * @return Name after desensitization
     */
    public static String chineseName(String fullName) {
        if (StrUtil.isBlank(fullName)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(fullName, 1, fullName.length());
    }

    /**
     * [ID number 1 and the last 2
     *
     * @param idCardNum ID
     * @param front     Reserved: front digits in front; Start with 1
     * @param end       Reserved: the following end digits; Start with 1
     * @return ID card after desensitization
     */
    public static String idCardNum(String idCardNum, int front, int end) {
        //ID card cannot be empty
        if (StrUtil.isBlank(idCardNum)) {
            return StrUtil.EMPTY;
        }
        //The length of interception must not exceed the length of the ID number.
        if ((front + end) > idCardNum.length()) {
            return StrUtil.EMPTY;
        }
        //The to be intercepted cannot be less than 0
        if (front < 0 || end < 0) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(idCardNum, front, idCardNum.length() - end);
    }

    /**
     * [The first four and the last two of the landlines
     *
     * @param num fixed telephone
     * @return Fixed telephone after desensitization;
     */
    public static String fixedPhone(String num) {
        if (StrUtil.isBlank(num)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(num, 4, num.length() - 2);
    }

    /**
     * [[mobile phone number] the first three digits, the last four digits, and others are hidden, such as 135 * * * * 2210
     *
     * @param num Mobile phone;
     * @return Desensitized mobile phone;
     */
    public static String mobilePhone(String num) {
        if (StrUtil.isBlank(num)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(num, 3, num.length() - 4);
    }

    /**
     * [Address] displays only the region, not the detailed address, for example, Haidian District, Beijing****
     *
     * @param address       Home address
     * @param sensitiveSize Sensitive information length
     * @return Home address after desensitization
     */
    public static String address(String address, int sensitiveSize) {
        if (StrUtil.isBlank(address)) {
            return StrUtil.EMPTY;
        }
        int length = address.length();
        return StrUtil.hide(address, length - sensitiveSize, length);
    }

    /**
     * [E-mail] the e-mail prefix only displays the first letter, other prefixes are hidden, replaced by an asterisk, @ and the following address are displayed, such as D * * @ 126 com
     *
     * @param email mailbox
     * @return Mailbox after desensitization
     */
    public static String email(String email) {
        if (StrUtil.isBlank(email)) {
            return StrUtil.EMPTY;
        }
        int index = StrUtil.indexOf(email, '@');
        if (index <= 1) {
            return email;
        }
        return StrUtil.hide(email, 1, index);
    }

    /**
     * [Password] all characters of the password are replaced by *, for example:******
     *
     * @param password password
     * @return Desensitized password
     */
    public static String password(String password) {
        if (StrUtil.isBlank(password)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.repeat('*', password.length());
    }

    /**
     * [[Chinese license plate] the middle of the license plate is replaced by *
     * eg1: null       -> ""
     * eg1: ""         -> ""
     * eg3: Su D40000 - Su D4***0
     * eg4: Shaanxi A12345D - * * * * D
     * eg5: Jing A123 - if the license plate is wrong, it will not be processed
     *
     * @param carLicense Complete license plate number
     * @return Desensitized license plate
     */
    public static String carLicense(String carLicense) {
        if (StrUtil.isBlank(carLicense)) {
            return StrUtil.EMPTY;
        }
        // Ordinary license plate
        if (carLicense.length() == 7) {
            carLicense = StrUtil.hide(carLicense, 3, 6);
        } else if (carLicense.length() == 8) {
            // New energy license plate
            carLicense = StrUtil.hide(carLicense, 3, 7);
        }
        return carLicense;
    }

    /**
     * Bank card number desensitization
     * eg: 1101 **** **** **** 3256
     *
     * @param bankCardNo Bank card No
     * @return Bank card number after desensitization
     * @since 5.6.3
     */
    public static String bankCard(String bankCardNo) {
        if (StrUtil.isBlank(bankCardNo)) {
            return bankCardNo;
        }
        bankCardNo = StrUtil.trim(bankCardNo);
        if (bankCardNo.length() < 9) {
            return bankCardNo;
        }

        final int length = bankCardNo.length();
        final int midLength = length - 8;
        final StringBuilder buf = new StringBuilder();

        buf.append(bankCardNo, 0, 4);
        for (int i = 0; i < midLength; ++i) {
            if (i % 4 == 0) {
                buf.append(CharUtil.SPACE);
            }
            buf.append('*');
        }
        buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length);
        return buf.toString();
    }
}

In fact, in this step, desensitization can be completed through replacement. You can call this tool directly in the program. But as a programmer who knows how to be lazy, he is certainly not satisfied with this. So we will further package

2. Custom desensitization annotation

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Sensitive {

    DesensitizedType strategy() default DesensitizedType.NONE;

    /**
     * Whether to use dfa algorithm
     * @return
     */
    boolean useDFA() default false;

    /**
     * dfa Replace sensitive characters with "*" by default
     * @return
     */
    String dfaReplaceChar() default "*";


    /**
     * dfa Number of sensitive character replacements
     * @return
     */
    int dfaReplaceCharRepeatCount() default 1;

}

3. Use some framework features to improve efficiency

a. If your project already has mybatis, you can take advantage of the mybatis interceptor feature. The implementation principle is to intercept the results returned from the response, and then desensitize the results

@Intercepts(@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class))
public class DesensitizedInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> list = (List<Object>) invocation.proceed();
        list.forEach(EntityUtils::desensitized);

        return list;
    }

}

b. If the project is a springboot based web project, you can use the jackson custom serialization implementation that comes with springboot. Its implementation is actually desensitization when json is serialized and rendered to the front end.

If this is the case, the user-defined annotation needs to be modified and added

@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedJsonSerializer.class)

Notes. The shape is as follows

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedJsonSerializer.class)
public @interface Sensitive {

    DesensitizedType strategy() default DesensitizedType.NONE;

    /**
     * Whether to use dfa algorithm
     * @return
     */
    boolean useDFA() default false;

    /**
     * dfa Replace sensitive characters with "*" by default
     * @return
     */
    String dfaReplaceChar() default "*";


    /**
     * dfa Number of sensitive character replacements
     * @return
     */
    int dfaReplaceCharRepeatCount() default 1;

}

The core code of serialization desensitization logic is as follows

public class DesensitizedJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private Sensitive sensitive;
    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(EntityUtils.getDesensitizedValue(sensitive,s));

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {

        sensitive = beanProperty.getAnnotation(Sensitive.class);

        if(!ObjectUtils.isEmpty(sensitive) && String.class.isAssignableFrom(beanProperty.getType().getRawClass())){
            return this;
        }
        return serializerProvider.findValueSerializer(beanProperty.getType(),beanProperty);
    }
}

Example

Take json as an example

1. When defining entity objects, desensitization annotations are added to the desensitized attributes

@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDTO {

    private Integer id;

    private String username;

    @Sensitive(strategy = DesensitizedType.PASSWORD)
    private String password;

    @Sensitive(strategy = DesensitizedType.CHINESE_NAME)
    private String fullname;

    @Sensitive(strategy = DesensitizedType.MOBILE_PHONE)
    private String mobile;

    @Sensitive(strategy = DesensitizedType.EMAIL)
    private String email;

    @Sensitive(useDFA = true,dfaReplaceChar = "#",dfaReplaceCharRepeatCount = 3)
    private String remark;
}

2. Write a test controller

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;


    @GetMapping(value="/list")
    public AjaxResult listUsers(){
       return AjaxResult.success(userService.listUserDTO());
    }

}

test result


Desensitization has been carried out as shown in the figure

Other programmes

1. Data desensitization based on Sharding Sphere

The specific implementation can refer to the following articles

https://jaskey.github.io/blog/2020/03/18/sharding-sphere-data-desensitization/

2. Custom annotation formatting

The main implementation steps are as follows

  • 1. Implements the AnnotationFormatterFactory interface
  • 2. Create a desensitization formatting class to implement Formatter
  • 3. Register the interface implemented by AnnotationFormatterFactory with FormatterRegistry

The specific implementation can refer to the following articles

https://blog.csdn.net/qq_27081015/article/details/103295983

4. Desensitization with fastjson

The main implementation steps are as follows

  • 1. Implement the ValueFilter interface and desensitize in process
  • 2. Configure fastjson as the default JSON transformation
/**
     * Configure fastjson as the default JSON transformation
     *
     * @return
     */
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1. Define an object that converts messages
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2. Add the configuration information of fastjson, such as whether to format the returned json data
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastJsonConfig.setSerializeFilters(new ValueDesensitizeFilter());//Add your own interceptor
        // 3. Add configuration information to the converter
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4. Assign converter to HttpMessageConverter
        HttpMessageConverter<?> converter = fastConverter;
        // 5. Return HttpMessageConverters object
        return new HttpMessageConverters(converter);
    }

The specific implementation can refer to the following articles

https://blog.csdn.net/qq_27081015/article/details/103297316

5. Using mybatis mate

For the mybatis plus enterprise (data processing) module, configure the authorization code when using it. It is as follows:

mybatis-mate:
  cert:
    grant: jinTianYiXueKe
    license: GKXP9r4MCJhGID/DTGigcBcLmZjb1YZGjE4GXaAoxbtGsPC20sxpEtiUr2F7Nb1ANTUekvF6Syo6DzraA4M4oacwoLVTglzfvaEyUogW8L7mydqlsZ4+hlm20kK85eLJK1QsskrSJmreMnEaNh9lsV7Lpbxy9JeGCeM0HPEbRvq8Y+8dUt5bQYLklsa3ZIBexir+4XykZY15uqn1pYIp4pEK0+aINTa57xjJNoWuBIqm7BdFIb4l1TAcPYMTsMXhF5hfMmKD2h391HxWTshJ6jbt4YqdKD167AgeoM+B+DE1jxlLjcpskY+kFs9piOS7RCcmKBBUOgX2BD/JxhR2gQ==

Its implementation mechanism is to use json serialization. If you are interested, please refer to the following link

https://gitee.com/baomidou/mybatis-mate-examples

The demo of this article also realizes desensitization based on mybatis mate. The link is as follows
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-desensitization/springboot-desensitzation-mybatis-mate

summary

Sometimes business scenarios can be implemented in a variety of ways. You should know how to judge whether to choose or not. For example, if your project does not use mybatis, but introduces mybatis in order to desensitize, this scheme will add additional complexity, and later maintenance estimation will have to be tossed

demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-desensitization

Keywords: Java data security

Added by Qlubbie on Wed, 29 Dec 2021 13:26:06 +0200