springboot配置文件隐私数据脱敏的最佳实践(原理+源码)(代码片段)

程序员内点事 程序员内点事     2022-12-10     712

关键词:

大家好!我是小富~

这几天公司在排查内部数据账号泄漏,原因是发现某些实习生小可爱居然连带着账号、密码将源码私传到GitHub上,导致核心数据外漏,孩子还是没挨过社会毒打,这种事的后果可大可小。

说起这个我是比较有感触的,之前我TM被删库的经历,到现在想起来心里还难受,我也是把数据库账号明文密码误提交到GitHub,然后被哪个大宝贝给我测试库删了,后边我长记性了把配置文件内容都加密了,数据安全问题真的不容小觑,不管工作汇还是生活,敏感数据一定要做脱敏处理。

如果对脱敏概念不熟悉,可以看一下我之前写过的一篇大厂也在用的6种数据脱敏方案,里边对脱敏做了简单的描述,接下来分享工作中两个比较常见的脱敏场景。

配置脱敏

实现配置的脱敏我使用了Java的一个加解密工具Jasypt,它提供了单密钥对称加密非对称加密两种脱敏方式。

单密钥对称加密:一个密钥加盐,可以同时用作内容的加密和解密依据;

非对称加密:使用公钥和私钥两个密钥,才可以对内容加密和解密;

以上两种加密方式使用都非常简单,咱们以springboot集成单密钥对称加密方式做示例。

首先引入jasypt-spring-boot-starter jar

 <!--配置文件加密-->
 <dependency>
     <groupId>com.github.ulisesbocchio</groupId>
     <artifactId>jasypt-spring-boot-starter</artifactId>
     <version>2.1.0</version>
 </dependency>

配置文件加入秘钥配置项jasypt.encryptor.password,并将需要脱敏的value值替换成预先经过加密的内容ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l)

这个格式我们是可以随意定义的,比如想要abc[mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l]格式,只要配置前缀和后缀即可。

jasypt:
  encryptor:
    property:
      prefix: "abc["
      suffix: "]"

ENC(XXX)格式主要为了便于识别该值是否需要解密,如不按照该格式配置,在加载配置项的时候jasypt将保持原值,不进行解密。

spring:
  datasource:
    url: jdbc:mysql://1.2.3.4:3306/xiaofu?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&ze oDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
    username: xiaofu
    password: ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l)

# 秘钥
jasypt:
  encryptor:
    password: 程序员内点事(然而不支持中文)

秘钥是个安全性要求比较高的属性,所以一般不建议直接放在项目内,可以通过启动时-D参数注入,或者放在配置中心,避免泄露。

java -jar -Djasypt.encryptor.password=1123  springboot-jasypt-2.3.3.RELEASE.jar

预先生成的加密值,可以通过代码内调用API生成

@Autowired
private StringEncryptor stringEncryptor;

public void encrypt(String content) 
    String encryptStr = stringEncryptor.encrypt(content);
    System.out.println("加密后的内容:" + encryptStr);

或者通过如下Java命令生成,几个参数D:\\maven_lib\\org\\jasypt\\jasypt\\1.9.3\\jasypt-1.9.3.jar为jasypt核心jar包,input待加密文本,password秘钥,algorithm为使用的加密算法。

java -cp  D:\\maven_lib\\org\\jasypt\\jasypt\\1.9.3\\jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="root" password=xiaofu  algorithm=PBEWithMD5AndDES

一顿操作后如果还能正常启动,说明配置文件脱敏就没问题了。

敏感字段脱敏

生产环境用户的隐私数据,比如手机号、身份证或者一些账号配置等信息,入库时都要进行不落地脱敏,也就是在进入我们系统时就要实时的脱敏处理。

用户数据进入系统,脱敏处理后持久化到数据库,用户查询数据时还要进行反向解密。这种场景一般需要全局处理,那么用AOP切面来实现在适合不过了。

首先自定义两个注解@EncryptField@EncryptMethod分别用在字段属性和方法上,实现思路很简单,只要方法上应用到@EncryptMethod注解,则检查入参字段是否标注@EncryptField注解,有则将对应字段内容加密。

@Documented
@Target(ElementType.FIELD,ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField 

    String[] value() default "";
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptMethod 

    String type() default ENCRYPT;

切面的实现也比较简单,对入参加密,返回结果解密。为了方便阅读这里就只贴出部分代码,完整案例Github地址:https://github.com/chengxy-nd...

@Slf4j
@Aspect
@Component
public class EncryptHandler 

    @Autowired
    private StringEncryptor stringEncryptor;

    @Pointcut("@annotation(com.xiaofu.annotation.EncryptMethod)")
    public void pointCut() 
    

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) 
        /**
         * 加密
         */
        encrypt(joinPoint);
        /**
         * 解密
         */
        Object decrypt = decrypt(joinPoint);
        return decrypt;
    

    public void encrypt(ProceedingJoinPoint joinPoint) 

        try 
            Object[] objects = joinPoint.getArgs();
            if (objects.length != 0) 
                for (Object o : objects) 
                    if (o instanceof String) 
                        encryptValue(o);
                     else 
                        handler(o, ENCRYPT);
                    
                    //TODO 其余类型自己看实际情况加
                
            
         catch (IllegalAccessException e) 
            e.printStackTrace();
        
    

    public Object decrypt(ProceedingJoinPoint joinPoint) 
        Object result = null;
        try 
            Object obj = joinPoint.proceed();
            if (obj != null) 
                if (obj instanceof String) 
                    decryptValue(obj);
                 else 
                    result = handler(obj, DECRYPT);
                
                //TODO 其余类型自己看实际情况加
            
         catch (Throwable e) 
            e.printStackTrace();
        
        return result;
    
    。。。

紧接着测试一下切面注解的效果,我们对字段mobileaddress加上注解@EncryptField做脱敏处理。

@EncryptMethod
@PostMapping(value = "test")
@ResponseBody
public Object testEncrypt(@RequestBody UserVo user, @EncryptField String name) 

    return insertUser(user, name);


private UserVo insertUser(UserVo user, String name) 
    System.out.println("加密后的数据:user" + JSON.toJSONString(user));
    return user;


@Data
public class UserVo implements Serializable 

    private Long userId;

    @EncryptField
    private String mobile;

    @EncryptField
    private String address;

    private String age;

请求这个接口,看到参数被成功加密,而返回给用户的数据依然是脱敏前的数据,符合我们的预期,那到这简单的脱敏实现就完事了。

知其然知其所以然

Jasypt工具虽然简单好用,但作为程序员我们不能仅满足于熟练使用,底层实现原理还是有必要了解下的,这对后续调试bug、二次开发扩展功能很重要。

个人认为Jasypt配置文件脱敏的原理很简单,无非就是在具体使用配置信息之前,先拦截获取配置的操作,将对应的加密配置解密后再使用。

具体是不是如此我们简单看下源码的实现,既然是以springboot方式集成,那么就先从jasypt-spring-boot-starter源码开始入手。

starter代码很少,主要的工作就是通过SPI机制注册服务和@Import注解来注入需前置处理的类JasyptSpringBootAutoConfiguration

在前置加载类EnableEncryptablePropertiesConfiguration中注册了一个核心处理类EnableEncryptablePropertiesBeanFactoryPostProcessor

它的构造器有两个参数,ConfigurableEnvironment用来获取所有配属信息,EncryptablePropertySourceConverter对配置信息做解析处理。

顺藤摸瓜发现具体负责解密的处理类EncryptablePropertySourceWrapper,它通过对Spring属性管理类PropertySource<T>做拓展,重写了getProperty(String name)方法,在获取配置时,凡是指定格式如ENC(x) 包裹的值全部解密处理。

既然知道了原理那么后续我们二次开发,比如:切换加密算法或者实现自己的脱敏工具就容易的多了。

案例Github地址:https://github.com/chengxy-nd...

PBE算法

再来聊一下Jasypt中用的加密算法,其实它是在JDK的JCE.jar包基础上做了封装,本质上还是用的JDK提供的算法,默认使用的是PBE算法PBEWITHMD5ANDDES,看到这个算法命名很有意思,段个句看看,PBE、WITH、MD5、AND、DES 好像有点故事,继续看。

PBE算法(Password Based Encryption,基于口令(密码)的加密)是一种基于口令的加密算法,其特点在于口令是由用户自己掌握,在加上随机数多重加密等方法保证数据的安全性。

PBE算法本质上并没有真正构建新的加密、解密算法,而是对我们已知的算法做了包装。比如:常用的消息摘要算法MD5SHA算法,对称加密算法DESRC2等,而PBE算法就是将这些算法进行合理组合,这也呼应上前边算法的名字。

既然PBE算法使用我们较为常用的对称加密算法,那就会涉及密钥的问题。但它本身又没有钥的概念,只有口令密码,密钥则是口令经过加密算法计算得来的。

口令本身并不会很长,所以不能用来替代密钥,只用口令很容易通过穷举攻击方式破译,这时候就得加点盐了。

盐通常会是一些随机信息,比如随机数、时间戳,将盐附加在口令上,通过算法计算加大破译的难度。

源码里的猫腻

简单了解PBE算法,回过头看看Jasypt源码是如何实现加解密的。

在加密的时候首先实例化秘钥工厂SecretKeyFactory,生成八位盐值,默认使用的jasypt.encryptor.RandomSaltGenerator生成器。

public byte[] encrypt(byte[] message) 
    // 根据指定算法,初始化秘钥工厂
    final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
    // 盐值生成器,只选八位
    byte[] salt = saltGenerator.generateSalt(8);
    // 
    final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations);
    // 盐值、口令生成秘钥
    SecretKey key = factory.generateSecret(keySpec);

    // 构建加密器
    final Cipher cipherEncrypt = Cipher.getInstance(algorithm1);
    cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
    // 密文头部(盐值)
    byte[] params = cipherEncrypt.getParameters().getEncoded();

    // 调用底层实现加密
    byte[] encryptedMessage = cipherEncrypt.doFinal(message);

    // 组装最终密文内容并分配内存(盐值+密文)
    return ByteBuffer
            .allocate(1 + params.length + encryptedMessage.length)
            .put((byte) params.length)
            .put(params)
            .put(encryptedMessage)
            .array();

由于默认使用的是随机盐值生成器,导致相同内容每次加密后的内容都是不同的。

那么解密时该怎么对应上呢?

看上边的源码发现,最终的加密文本是由两部分组成的,params消息头里边包含口令和随机生成的盐值,encryptedMessage密文。

而在解密时会根据密文encryptedMessage的内容拆解出params内容解析出盐值和口令,在调用JDK底层算法解密出实际内容。

@Override
@SneakyThrows
public byte[] decrypt(byte[] encryptedMessage) 
    // 获取密文头部内容
    int paramsLength = Byte.toUnsignedInt(encryptedMessage[0]);
    // 获取密文内容
    int messageLength = encryptedMessage.length - paramsLength - 1;
    byte[] params = new byte[paramsLength];
    byte[] message = new byte[messageLength];
    System.arraycopy(encryptedMessage, 1, params, 0, paramsLength);
    System.arraycopy(encryptedMessage, paramsLength + 1, message, 0, messageLength);

    // 初始化秘钥工厂
    final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
    final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
    SecretKey key = factory.generateSecret(keySpec);

    // 构建头部盐值口令参数
    AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algorithm1);
    algorithmParameters.init(params);

    // 构建加密器,调用底层算法
    final Cipher cipherDecrypt = Cipher.getInstance(algorithm1);
    cipherDecrypt.init(
            Cipher.DECRYPT_MODE,
            key,
            algorithmParameters
    );
    return cipherDecrypt.doFinal(message);

我是小富,下期见~

整理了几百本各类技术电子书,有需要的同学自取。技术群快满了,想进的同学可以加我好友,和大佬们一起吹吹技术。

电子书地址

个人公众号: 程序员内点事,欢迎交流

springboot配置文件隐私数据脱敏的最佳实践(原理+源码)(代码片段)

这几天公司在排查内部数据账号泄漏,原因是发现某些实习生小可爱居然连带着账号、密码将源码私传到GitHub上,导致核心数据外漏,孩子还是没挨过社会毒打,这种事的后果可大可小。说起这个我是比较有感触... 查看详情

springboot配置文件隐私数据脱敏的最佳实践(原理+源码)(代码片段)

这几天公司在排查内部数据账号泄漏,原因是发现某些实习生小可爱居然连带着账号、密码将源码私传到GitHub上,导致核心数据外漏,孩子还是没挨过社会毒打,这种事的后果可大可小。说起这个我是比较有感触... 查看详情

仅需一个注解,实现springboot项目中的隐私数据脱敏!(代码片段)

点击关注公众号,实用技术文章及时了解这两天在整改等保测出的问题,里面有一个“用户信息泄露”的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏。数据脱... 查看详情

仅需一个注解,实现springboot项目中的隐私数据脱敏!(代码片段)

点击关注公众号,实用技术文章及时了解这两天在整改等保测出的问题,里面有一个“用户信息泄露”的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏。数据脱... 查看详情

医疗行业数据安全最佳实践案例(脱敏版)

...展,为医患提供便捷的医疗服务,进而会产生大量的医疗数据。特别是借助互联网的应用、提供的小程序,导致大量医疗数据的流转,如电子挂号、电子病历、电子诊断信息的传输,加大了数据泄露的风险,关乎国家和人民的利... 查看详情

springboot之logback日志最佳实践

一、SpringBoot日志介绍  SpringBoot对所有内部日志记录使用了CommonsLogging,但是底层日志实现是开放的。为JavaUtil日志记录、Log4J2和Logback提供了缺省配置。在每种情况下,日志记录器都预先配置为使用控制台输出和可选的文件输... 查看详情

数据脱敏

...l数据脱敏介绍数据脱敏(DataMasking),又称数据漂白、数据去隐私化或数据变形。百度百科对数据脱敏的定义为:指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。这样,就可以在开发、测试和其它非... 查看详情

springboot自定义kafka消费者配置containerfactory最佳实践

SpringBoot自定义kafka消费者配置ContainerFactory最佳实践本篇博文主要提供一个在SpringBoot中自定义kafka配置的实践,想象这样一个场景:你的系统需要监听多个不同集群的消息,在不同的集群中topic冲突了,所以你需要分别定义kafka消... 查看详情

这个开源组件太强了,仅需三步完成springboot日志脱敏

参考技术A在我们书写代码的时候,会书写许多日志代码,但是有些敏感数据是需要进行安全脱敏处理的。对于日志脱敏的方式有很多,常见的有①使用conversionRule标签,继承MessageConverter②书写一个脱敏工具类,在打印日志的时... 查看详情

云小课|大数据时代的隐私利器-gaussdb(dws)数据脱敏

...,具有灵活、高效、透明、友好等优点。使得敏感及隐私信息不被泄 查看详情

springboot最佳实践,它来了,它来了(代码片段)

前言上篇博文(SpringBoot自动配置原理,你真的懂吗?)我们详细的介绍了SpringBoot是如何完成自动配置功能的,以及具体的原理。SpringBoot的强大,大家也有所了解了,SpringBoot更加强大的地方是提供了很... 查看详情

什么是数据脱敏?

...对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。在涉及客户安全数据或者一些商业性敏感数据的情况下,在不违反系统规则条件下,对真实数据进行改造并提供测试使用,如×××号、手机号、卡号... 查看详情

哪个品牌的数据库脱敏系统比较好?

...别敏感数据和管理敏感数据,提供灵活的策略和脱敏方案配置,高效可并行的脱敏能力,帮助企业快速实施敏感数据脱敏处理,同时保证数据的有效性和可用性,使脱敏后的数据能够安全的应用于测试、开发、分析,和第三方使... 查看详情

Spring Boot 中 JWT 的最佳实践是啥?

】SpringBoot中JWT的最佳实践是啥?【英文标题】:WhatisthebestpracticeforJWTinSpringBoot?SpringBoot中JWT的最佳实践是什么?【发布时间】:2022-01-0711:24:14【问题描述】:我想用SpringBoot和JWT启动一个项目,我看到了一些将刷新令牌保存在数... 查看详情

将应用程序配置保存在 plist 文件中的最佳实践

】将应用程序配置保存在plist文件中的最佳实践【英文标题】:Bestpracticeofkeepingapplicationconfigurationinplistfile【发布时间】:2012-02-2811:18:05【问题描述】:这听起来可能是个新手问题,反正我是iOS开发新手,我想知道将应用程序配... 查看详情

配置文件等的 Git 最佳实践

】配置文件等的Git最佳实践【英文标题】:Gitbestpracticeforconfigfilesetc【发布时间】:2012-03-0917:12:06【问题描述】:我对git的所有东西还是新手,想知道关于配置文件的最佳实践是什么。我的本地开发服务器需要与我的实时服务器... 查看详情

重学springboot系列之整合数据库开发框架---中(代码片段)

重学Springboot系列之整合数据库开发框架---中javabean的赋值转换为什么要做javabean赋值转换BeanUtils和Dozer?引入Dozer(6.2.0)自定义类型转换(非对称类型转换)映射localDateTime的问题整合MybatisGenerator操作数据整合M... 查看详情

springboot生产中16条最佳实践

...依赖2、使用自动配置3、使用SpringInitializr来开始一个新的SpringBoot项目4、考虑为常见的组织问题创建自己的自动配置5、正确设计代码目 查看详情