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

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

所以接下来,咱们需要开展两方面的工作:

  1. 对配置文件进行脱敏操作;

  2. 对敏感的数据库字段进行脱敏操作。

说干就干,接下来咱们一起来对项目进行脱敏操作。

1.配置脱敏

实现配置的脱敏我使用了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/Shanghaiusername: xiaofupassword: 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

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

2.敏感字段脱敏

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

用户数据进入系统,脱敏处理后持久化到数据库,用户查询数据时还要进行反向解密。这种场景一般需要全局处理,那么用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-nds/Springboot-Notebook/tree/master/springboot-jasypt

@Slf4j
@Aspect
@Component
public class EncryptHandler {@Autowiredprivate 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;@EncryptFieldprivate String mobile;@EncryptFieldprivate String address;private String age;
}

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

3.原理分析

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-nds/Springboot-Notebook/tree/master/springboot-jasypt

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 如何统一后端返回格式?老鸟们都是这样玩的!

SpringBoot时间格式化的5种方法!

SpringBoot 优雅的参数效验!

实战:隐藏SpringBoot中的私密数据!相关推荐

  1. splunk中 如何隐藏input_如何隐藏电脑中的私密文件?试试这个加密方法!

    所需工具:Wise Folder Hider(后台回复「148」获取) 适用系统:PC ==== 在日常的生活中,我们的电脑总会有一些秘密不想让人知道. 不管是涉及了公司机密的文件,还是那些年你遇到的 ...

  2. (转) android so 文件存私密数据,且防止 so文件未知应用盗用

    android so 文件存私密数据,且防止 so文件未知应用盗用 有时候我们需要在客户端 存放一些 保密的数据,比如 服务端的IP 端口,接口的地址,授权的key 等等,如果我们直接写在java中, ...

  3. 卧操,女性私密数据曝光,原来富婆都在广西…

    点击上方"蓝色字体",选择"设为星标" 做积极向上的前端人! 作者:好奇博士 温馨提示:曝光中国女性的私密数据,据统计听说广西富婆多,去往广西的车票不多了,请大 ...

  4. 在Mac中保护私密文件,隐藏文件

    在个人电脑中我们有些私密文件或者资料是不希望被人发现或者使用.保护私密文件的方式有很多,比如对文件进行加密,隐藏文件,修改文件后缀等.在Mac中我们也可以通过一些简单方式保护私密文件.这里我们简单介绍 ...

  5. 扒光葡萄游戏厅私密数据,看智能电视游戏用户在干啥?

    本文为葡萄游戏厅CEO 孙峰原创文章,友盟略有删减. 智能电视被誉为最大的一块屏,游戏是最大这块屏上除视频外大家最感兴趣的业务,被行业内称之为下一个千亿金矿.在这个领域,家电厂商热情高涨,游戏厂商试水 ...

  6. Private Data Objects, 私密数据对象,基于TEE、智能合约和分布式账本的可信计算

    本文首发在 https://zhuanlan.zhihu.com/p/109270123 本文的主要内容来自论文:Bowman, Mic, et al. "Private data obje ...

  7. 在springboot中使用easyexcel导出数据excel表格

    第一步: 在pom.xml中添加easyexcel依赖 之前测试一些低版本会出现一些问题,所以在这里使用了2.1.6的版本. <dependency><groupId>com. ...

  8. xr企业级应用在哪里_如何在XR中保持私密

    xr企业级应用在哪里 So you're thinking of buying a surprisingly inexpensive XR headset from a notorious socia ...

  9. 16张扎心漫画,戳中女生私密日常,每一幕都很真实

    全世界只有3.14 % 的人关注了 爆炸吧知识 比利时的插画师Planet Prudence,画了很多女生的真实日常,每一幕都很戳心,一起来看看吧. 别人的痘痘一长就是一个,我一长就是一片. 买买买的 ...

最新文章

  1. oracle中代替in 和not in 高效方法
  2. 亚马逊向土耳其企业启动“全球开店”计划,要和中国卖家抢夺欧洲市场
  3. python:直接插入和简单选择排序
  4. java整数的反转_反转Java中的整数
  5. Spring的Bean实例化、属性注入、对象注入、复杂注入(基于xml配置方式)
  6. wxWidgets:wxDynamicLibrary类用法
  7. BZOJ-2298|区间dp|线段树
  8. 10分钟就能学会的.NET Core配置
  9. Python学习_字符串格式化
  10. Graphviz样例之有向图
  11. 手机怎么用java9_java9_java9官方版 32位64位 最新版_天天下载手机版
  12. mysql数据库原理及应用教案_《数据库原理及应用》全套教案.doc
  13. 【信号处理】脉搏信号处理系统含Matlab源码
  14. 0x0000000指令引用”0x0000000”内存,该内存不能为written
  15. 基因组测序中N50和N90到底指什么?
  16. 1977-1998全国历年高考状元现状
  17. 融360 D轮融资超10亿 平台型互联网金融价值凸显
  18. 学习新时代编程语言Rust-12小胖的裤子
  19. CCF智能无人车比赛(国内绿洲科学实验云平台)心路历程+AWS Deepracer智能无人车比赛经验(附优秀代码re:lnvent 2018赛道)
  20. RENIX报文字段跳变——网络测试仪实操

热门文章

  1. 如果删除网上服务器登陆账号密码,怎么清除SVN的用户名和密码
  2. Linux 出现Permission denied的解决办法
  3. UVA1583 Digit Generator
  4. 做优化的数据库工程师请参考!CynosDB的计算层设计优化揭秘
  5. vue内引入语音播报功能
  6. 埋坑一: vue中子组件调用兄弟组件方法
  7. 我的女朋友漏电了–论C++中的失败(failure),缺陷(bug)和异常(exception)
  8. Verizon的SDN策略:不鸣则已,一鸣惊人?
  9. 永中向香港博览会主办方演示云办公(转载)
  10. Service Broker实现发布-订阅(Publish-Subscribe)框架