50行代码,搞定敏感数据读写!

每天早上七点三十,准时推送干货

一、介绍

在实际的软件系统开发过程中,由于业务的需求,在代码层面实现数据的脱敏还是远远不够的,往往还需要在数据库层面针对某些关键性的敏感信息,例如:身份证号、银行卡号、手机号、工资等信息进行加密存储,实现真正意义的数据混淆脱敏,以满足信息安全的需要。

那在实际的研发过程中,我们如何实践呢?

二、方案实践

在此,提供三套方案以供大家选择。

  • 通过 SQL 函数实现加解密

  • 对 SQL 进行解析拦截,实现数据加解密

  • 自定义一套脱敏工具

2.1、通过 SQL 函数实现加解密

最简单的方法,莫过于直接在数据库层面操作,通过函数对某个字段进行加、解密,例如如下这个案例!

-- 对“你好,世界”进行加密
select   HEX(AES_ENCRYPT('你好,世界','ABC123456'));-- 解密,输出:你好,世界
select  AES_DECRYPT(UNHEX('A174E3C13FE16AA0FD071A4BBD7CD7C5'),'ABC123456');

采用Mysql内置的AES协议加、解密函数,密钥是ABC123456,可以很轻松的对某个字段实现加、解密。

如果是很小的需求,需要加密的数据就是指定的信息,此方法可行。

但是当需要加密的表字段非常多的时候,这个使用起来就比较鸡肋了,例如我想更改加密算法或者不同的部署环境配置不同的密钥,这个时候就不得不把所有的代码进行更改一遍。

2.2、对 SQL 进行解析拦截,实现数据加解密

通过上面的方案,我们发现最大的痛点就是加密算法和密钥都写死在SQL上了,因此我们可以将这块的服务从抽出来,在JDBC层面,当sql执行的时候,对其进行拦截处理。

Apache ShardingSphere 框架下的数据脱敏模块,它就可以帮助我们实现这一需求,如果你是SpringBoot项目,可以实现无缝集成,对原系统的改造会非常少。

下面以用户表为例,我们来看看采用ShardingSphere如何实现!

2.2.1、创建用户表
CREATE TABLE user (id bigint(20) NOT NULL COMMENT '用户ID',email varchar(255)  NOT NULL DEFAULT '' COMMENT '邮件',nick_name varchar(255)  DEFAULT NULL COMMENT '昵称',pass_word varchar(255)  NOT NULL DEFAULT '' COMMENT '二次密码',reg_time varchar(255)  NOT NULL DEFAULT '' COMMENT '注册时间',user_name varchar(255)  NOT NULL DEFAULT '' COMMENT '用户名',salary varchar(255) DEFAULT NULL COMMENT '基本工资',PRIMARY KEY (id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
2.2.2、创建 springboot 项目并添加依赖包
<dependencies><!--spring boot核心--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--spring boot 测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--springmvc web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql 数据源--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--mybatis 支持--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency> <!--shardingsphere数据分片、脱敏工具--><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-namespace</artifactId><version>4.1.0</version></dependency>
</dependencies>
2.2.3、添加脱敏配置

application.properties文件中,添加shardingsphere相关配置,即可实现针对某个表进行脱敏

server.port=8080logging.path=log#shardingsphere数据源集成
spring.shardingsphere.datasource.name=ds
spring.shardingsphere.datasource.ds.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds.jdbc-url=jdbc:mysql://127.0.0.1:3306/test
spring.shardingsphere.datasource.ds.username=xxxx
spring.shardingsphere.datasource.ds.password=xxxx#加密方式、密钥配置
spring.shardingsphere.encrypt.encryptors.encryptor_aes.type=aes
spring.shardingsphere.encrypt.encryptors.encryptor_aes.props.aes.key.value=hkiqAXU6Ur5fixGHaO4Lb2V2ggausYwW
#plainColumn表示明文列,cipherColumn表示脱敏列
spring.shardingsphere.encrypt.tables.user.columns.salary.plainColumn=
spring.shardingsphere.encrypt.tables.user.columns.salary.cipherColumn=salary
#spring.shardingsphere.encrypt.tables.user.columns.pass_word.assistedQueryColumn=
spring.shardingsphere.encrypt.tables.user.columns.salary.encryptor=encryptor_aes#sql打印
spring.shardingsphere.props.sql.show=true
spring.shardingsphere.props.query.with.cipher.column=true#基于xml方法的配置
mybatis.mapper-locations=classpath:mapper/*.xml

其中下面的配置信息是关键的一部,spring.shardingsphere.encrypt.tables是指要脱敏的表,user是表名,salary表示user表中的真实列,其中plainColumn指的是明文列,cipherColumn指的是脱敏列,如果是新工程,只需要配置脱敏列即可!

spring.shardingsphere.encrypt.tables.user.columns.salary.plainColumn=
spring.shardingsphere.encrypt.tables.user.columns.salary.cipherColumn=salary
#spring.shardingsphere.encrypt.tables.user.columns.pass_word.assistedQueryColumn=
spring.shardingsphere.encrypt.tables.user.columns.salary.encryptor=encryptor_aes
2.2.4、编写数据持久层
<mapper namespace="com.example.shardingsphere.mapper.UserMapperXml" ><resultMap id="BaseResultMap" type="com.example.shardingsphere.entity.UserEntity" ><id column="id" property="id" jdbcType="BIGINT" /><result column="email" property="email" jdbcType="VARCHAR" /><result column="nick_name" property="nickName" jdbcType="VARCHAR" /><result column="pass_word" property="passWord" jdbcType="VARCHAR" /><result column="reg_time" property="regTime" jdbcType="VARCHAR" /><result column="user_name" property="userName" jdbcType="VARCHAR" /><result column="salary" property="salary" jdbcType="VARCHAR" /></resultMap><select id="findAll" resultMap="BaseResultMap">SELECT * FROM user</select><insert id="insert" parameterType="com.example.shardingsphere.entity.UserEntity">INSERT INTO user(id,email,nick_name,pass_word,reg_time,user_name, salary)VALUES(#{id},#{email},#{nickName},#{passWord},#{regTime},#{userName}, #{salary})</insert>
</mapper>
public interface UserMapperXml {/*** 查询所有的信息* @return*/List<UserEntity> findAll();/*** 新增数据* @param user*/void insert(UserEntity user);
}
public class UserEntity {private Long id;private String email;private String nickName;private String passWord;private String regTime;private String userName;private String salary;//省略set、get...}
2.2.5、最后我们来测试一下程序运行情况

编写启用服务程序

@SpringBootApplication
@MapperScan("com.example.shardingsphere.mapper")
public class ShardingSphereApplication {public static void main(String[] args) {SpringApplication.run(ShardingSphereApplication.class, args);}
}

编写单元测试

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ShardingSphereApplication.class)
public class UserTest {@Autowiredprivate UserMapperXml userMapperXml;@Testpublic void insert() throws Exception {UserEntity entity = new UserEntity();entity.setId(3l);entity.setEmail("123@123.com");entity.setNickName("阿三");entity.setPassWord("123");entity.setRegTime("2021-10-10 00:00:00");entity.setUserName("张三");entity.setSalary("2500");userMapperXml.insert(entity);}@Testpublic void query() throws Exception {List<UserEntity> dataList = userMapperXml.findAll();System.out.println(JSON.toJSONString(dataList));}
}

插入数据后,如下图,数据库存储的数据已被加密!

5e5308f2d3578e680f2f284babd77801.png

我们继续来看看,运行查询服务,结果如下图,数据被成功解密!

9af72022f4532ab994ebf3ab5b008916.png

采用配置方式,最大的好处就是直接通过配置脱敏列就可以完成对某些数据表字段的脱敏,非常方便。

2.3、自定义一套脱敏工具

当然,有的同学可能会觉得shardingsphere配置虽然简单,但是还是不放心,里面的很多规则自己无法掌控,想自己开发一套数据库的脱敏工具。

方案也是有的,例如如下这套实践方案,以Mybatis为例:

  • 首先编写一套加解密的算法工具类

  • 通过MybatistypeHandler插件,实现特定字段的加解密

实践过程如下:

2.3.1、加解密工具类
public class AESCryptoUtil {private static final Logger log = LoggerFactory.getLogger(AESCryptoUtil.class);private static final String DEFAULT_ENCODING = "UTF-8";private static final String AES = "AES";/*** 加密** @param content 需要加密内容* @param key     任意字符串* @return* @throws Exception*/public static String encryptByRandomKey(String content, String key) {try {//构造密钥生成器,生成一个128位的随机源,产生原始对称密钥KeyGenerator keygen = KeyGenerator.getInstance(AES);SecureRandom random = SecureRandom.getInstance("SHA1PRNG");random.setSeed(key.getBytes());keygen.init(128, random);byte[] raw = keygen.generateKey().getEncoded();SecretKey secretKey = new SecretKeySpec(raw, AES);Cipher cipher = Cipher.getInstance(AES);cipher.init(Cipher.ENCRYPT_MODE, secretKey);byte[] encrypted = cipher.doFinal(content.getBytes("utf-8"));return Base64.getEncoder().encodeToString(encrypted);} catch (Exception e) {log.warn("AES加密失败,参数:{},错误信息:{}", content, e);return "";}}public static String decryptByRandomKey(String content, String key) {try {//构造密钥生成器,生成一个128位的随机源,产生原始对称密钥KeyGenerator generator = KeyGenerator.getInstance(AES);SecureRandom random = SecureRandom.getInstance("SHA1PRNG");random.setSeed(key.getBytes());generator.init(128, random);SecretKey secretKey = new SecretKeySpec(generator.generateKey().getEncoded(), AES);Cipher cipher = Cipher.getInstance(AES);cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] encrypted = Base64.getDecoder().decode(content);byte[] original = cipher.doFinal(encrypted);return new String(original, DEFAULT_ENCODING);} catch (Exception e) {log.warn("AES解密失败,参数:{},错误信息:{}", content, e);return "";}}public static void main(String[] args) {String encryptResult = encryptByRandomKey("Hello World", "123456");System.out.println(encryptResult);String decryptResult = decryptByRandomKey(encryptResult, "123456");System.out.println(decryptResult);}
}
2.3.2、针对 salary 字段进行单独解析
<mapper namespace="com.example.shardingsphere.mapper.UserMapperXml" ><resultMap id="BaseResultMap" type="com.example.shardingsphere.entity.UserEntity" ><id column="id" property="id" jdbcType="BIGINT" /><result column="email" property="email" jdbcType="VARCHAR" /><result column="nick_name" property="nickName" jdbcType="VARCHAR" /><result column="pass_word" property="passWord" jdbcType="VARCHAR" /><result column="reg_time" property="regTime" jdbcType="VARCHAR" /><result column="user_name" property="userName" jdbcType="VARCHAR" /><result column="salary" property="salary" jdbcType="VARCHAR"typeHandler="com.example.shardingsphere.handle.EncryptDataRuleTypeHandler"/></resultMap><select id="findAll" resultMap="BaseResultMap">select * from user</select><insert id="insert" parameterType="com.example.shardingsphere.entity.UserEntity">INSERT INTO user(id,email,nick_name,pass_word,reg_time,user_name, salary)VALUES(#{id},#{email},#{nickName},#{passWord},#{regTime},#{userName},#{salary,jdbcType=INTEGER,typeHandler=com.example.shardingsphere.handle.EncryptDataRuleTypeHandler})</insert>
</mapper>

EncryptDataRuleTypeHandler解析器,内容如下:

public class EncryptDataRuleTypeHandler implements TypeHandler<String> {private static final String EMPTY = "";/*** 写入数据* @param preparedStatement* @param i* @param data* @param jdbcType* @throws SQLException*/@Overridepublic void setParameter(PreparedStatement preparedStatement, int i, String data, JdbcType jdbcType) throws SQLException {if (StringUtils.isEmpty(data)) {preparedStatement.setString(i, EMPTY);} else {preparedStatement.setString(i, AESCryptoUtil.encryptByRandomKey(data, "123456"));}}/*** 读取数据* @param resultSet* @param columnName* @return* @throws SQLException*/@Overridepublic String getResult(ResultSet resultSet, String columnName) throws SQLException {return decrypt(resultSet.getString(columnName));}/*** 读取数据* @param resultSet* @param columnIndex* @return* @throws SQLException*/@Overridepublic String getResult(ResultSet resultSet, int columnIndex) throws SQLException {return decrypt(resultSet.getString(columnIndex));}/*** 读取数据* @param callableStatement* @param columnIndex* @return* @throws SQLException*/@Overridepublic String getResult(CallableStatement callableStatement, int columnIndex) throws SQLException {return decrypt(callableStatement.getString(columnIndex));}/*** 对数据进行解密* @param data* @return*/private String decrypt(String data) {return AESCryptoUtil.decryptByRandomKey(data, "123456");}
}
2.3.3、单元测试

再次运行单元测试,程序读写正常!

4845ea7174c497cda7517d81a5227f3c.png5071a3c1a62b71815d600609dc13d715.png

通过如下的方式,也可以实现对数据表中某个特定字段进行数据脱敏处理!

三、小结

因业务的需求,当需要对某些数据表字段进行脱敏处理的时候,有个细节很容易遗漏,那就是字典类型,例如salary字段,根据常规,很容易想到使用数字类型,但是却不是,要知道加密之后的数据都是一串乱码,数字类型肯定是无法存储字符串的,因此在定义的时候,这个要留心一下。

其次,很多同学可能会觉得,这个也不能防范比人窃取数据啊!

如果加密使用的密钥和数据都在一个项目里面,答案是肯定的,你可以随便解析任何人的数据。因此在实际的处理上,这个更多的是在流程上做变化。例如如下方式:

  • 首先,加密采用的密钥会在另外一个单独的服务来存储管理,保证密钥不轻易泄露出去,最重要的是加密的数据不轻易被别人解密。

  • 其次,例如某些人想要访问谁的工资条数据,那么就需要做二次密码确认,也就是输入自己的密码才能获取,可以进一步防止研发人员随意通过接口方式读取数据。

  • 最后就是,杜绝代码留漏洞。

以上三套方案,都可以帮助大家实现数据库字段数据的脱敏,希望能帮助到大家,谢谢欣赏!

四、参考

1、敏感数据,“一键脱敏”,Sharding Sphere 完美搞定 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544503.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python】导入资源管理器的文件列表(计算文件和文件夹大小)

文章目录1.按照扩展名进行分类2.导出文件的大小3.计算文件夹大小4.分类到字典5.完整代码及效果1.按照扩展名进行分类 使用Python查询一个路径下的所有文件可以借助glob模块以及os模块。 在导入文件列表之前&#xff0c;我们需要指定我们的操作目录&#xff0c;操作目录一经指定…

HashMap 中的一个“坑”!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;最近公司新来了一个小伙伴&#xff0c;问了磊哥一个比较“奇怪”的问题&#xff0c;这个问题本身的难度并不大&#xff0c;但…

【Python】交互式界面创建函数

文章目录简介规则思路示例代码与运行效果简介 运行Python时&#xff0c;在程序运行过程中手动输入一个函数表达式&#xff0c;并将其作为一个函数进行后续的调用工作&#xff0c;类似于Matlab里面的匿名函数。这个功能使用传统语言会相当麻烦&#xff0c;当然&#xff0c;我也…

原生 js前端路由系统实现3之代码 构建工具 和 querystring功能

为什么80%的码农都做不了架构师&#xff1f;>>> 构建 目前前端构建工具流行的是 grunk.js 功能是大而全&#xff0c;但往往大而全的东西为了多样性 需要做额外的配置 我还是想要有一个专门为自己特性项目而生构建工具 我不想加载第三方的node模块&#xff0c;也不…

单例模式,真不简单

前言单例模式无论在我们面试&#xff0c;还是日常工作中&#xff0c;都会面对的问题。但很多单例模式的细节&#xff0c;值得我们深入探索一下。这篇文章透过单例模式&#xff0c;串联了多方面基础知识&#xff0c;非常值得一读。1 什么是单例模式&#xff1f;单例模式是一种非…

【python】最优化方法之一维搜索(黄金分割法+斐波那契法)

文章目录1.概念2.遍历搜索3.优化算法3.1.一维搜索原则3.2.黄金分割法Code Block3.3.斐波拉契法Code Block1.概念 \qquad一维搜索是最优化方法最简单的一种&#xff0c;即求一个在(a,b)内&#xff0c;连续下单峰函数f(x)f(x)f(x)的极小值。所谓下单峰函数就是只有一个极小值的函…

MySQL系列之E-1------MySQL主从复制原理

主从复制是异步复制,可以通过google的一个插件实现半同步E.1 主从复制原理1、建立主从复制的用户名和密码2、将master上主库需要进行复制的库“锁库”3、通过mysqldump备份master上主库&#xff0c;“解锁“&#xff0c;在slave端进行恢复4、更改配置文件5、在丛库上执行change…

工作中常用的 6 种设计模式!

前言 哈喽&#xff0c;大家好。平时我们写代码呢&#xff0c;多数情况都是流水线式写代码&#xff0c;基本就可以实现业务逻辑了。如何在写代码中找到乐趣呢&#xff0c;我觉得&#xff0c;最好的方式就是&#xff1a;使用设计模式优化自己的业务代码。今天跟大家聊聊日常工作中…

【Matlab/C/Python/VB/...】代码复制到word时如何变成彩色的

文章目录下载Notepad复制代码在Notepad粘贴在word中粘贴下载Notepad Notepad是一款免费的Windows软件&#xff0c;一般Windows10和Windows7系统都已经自带&#xff0c;也可以在应用商店直接搜索下载 「win10系统兼容的是7.8版本」 复制代码 在语言编辑乱码复制代码&#xff…

hadoop 2.5.0安装和配置

安装hadoop要先做以下准备&#xff1a; 1.jdk&#xff0c;安装教程在 http://www.cnblogs.com/stardjyeah/p/4640917.html 2.ssh无密码验证&#xff0c;配置教程在 http://www.cnblogs.com/stardjyeah/p/4641524.html 3.linux静态ip配置&#xff0c;教程在 http://www.cnblo…

基于双线性插值的图像旋转原理及MATLAB实现(非自带函数)

目录1.图像旋转的原理1.1.旋转矩阵1.2.双线性插值1.3.像素点匹配2.实现效果与说明1.图像旋转的原理 1.1.旋转矩阵 旋转一幅图像&#xff08;假设这幅图像大小是矩形的&#xff09;&#xff0c;当然应该从像素点&#xff08;pixels&#xff09;开始&#xff0c;在直角坐标系中…

漫画:给女朋友介绍什么是 “元宇宙” ?

什么是更高的自由度呢&#xff1f;或许有人觉得&#xff0c;我们在网络游戏当中&#xff0c;不是也很自由吗&#xff1f;想怎么玩就怎么玩。但是&#xff0c;无论一款网络游戏的元素有多么丰富&#xff0c;游戏当中的角色、任务、职业、道具、场景&#xff0c;都是游戏设计师预…

MyBatis 中为什么不建议使用 where 1=1?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;最近接手了一个老项目&#xff0c;“愉悦的心情”自然无以言表&#xff0c;做开发的朋友都懂&#xff0c;这里就不多说了&am…

【openMV与机器视觉】四旋翼飞行控制背景下的PID控制与摄像头算法简介

文章目录声明1.四旋翼飞行控制简介2.飞行控制算法2.1.接收机PWM生成2.2.PID算法位置PID速度PID3.摄像头算法3.1.图像处理3.2.霍夫曼变换3.3.巡线算法3.3.寻找目标点降落算法声明 \qquad本文的算法在openMV IDE例程的基础上进行原创&#xff0c;在比赛结束后予以发表&#xff1b…

聊聊sql优化的15个小技巧

前言sql优化是一个大家都比较关注的热门话题&#xff0c;无论你在面试&#xff0c;还是工作中&#xff0c;都很有可能会遇到。如果某天你负责的某个线上接口&#xff0c;出现了性能问题&#xff0c;需要做优化。那么你首先想到的很有可能是优化sql语句&#xff0c;因为它的改造…

【MATLAB】Parzen窗与K近邻算法原理与代码详解

文章目录1.非参数估计原理2.Parzen窗2.1.算法原理2.2.Matlab实现与参数探究3.K近邻3.1.算法原理3.2.Matlab实现与参数探究1.非参数估计原理 \qquad已知一个样本的概率分布时&#xff0c;我们只需要对概率分布中的参数进行估计即可得到该样本的概率密度函数。例如已知样本X服从正…

使用 Lambda 表达式实现超强的排序功能

我们在系统开发过程中&#xff0c;对数据排序是很常见的场景。一般来说&#xff0c;我们可以采用两种方式&#xff1a;借助存储系统&#xff08;SQL、NoSQL、NewSQL 都支持&#xff09;的排序功能&#xff0c;查询的结果即是排好序的结果查询结果为无序数据&#xff0c;在内存中…

【mongodb系统学习之四】查看mongodb进程

四、查看mongodb进程&#xff08;可以配合启动和关闭使用&#xff09;&#xff1a; 1&#xff09;、方法一&#xff1a;直接查看mongodb进程是否已经存在&#xff08;用上面的方式启动后&#xff0c;需要另开一个窗口操作&#xff09;&#xff1a;ps –ef|grep mongodb, 如图&a…

【Simulink】粒子群算法(PSO)整定PID参数(附代码和讲解)

目录0.背景1.粒子群算法1.1.算法简介1.2.算法步骤1.3.算法举例2.PID自整定2.1.基于M文件编写的PID参数自整定*2.2.复杂系统的PID自整定&#xff08;基于simulink仿真&#xff09;2.2.1.PSO优化PID的过程详解2.2.2.在PSO优化过程中修改参数价值权重阅读前必看&#xff1a;本代码…

SpringBoot 使用注解实现消息广播功能

背景在开发工作中&#xff0c;会遇到一种场景&#xff0c;做完某一件事情以后&#xff0c;需要广播一些消息或者通知&#xff0c;告诉其他的模块进行一些事件处理&#xff0c;一般来说&#xff0c;可以一个一个发送请求去通知&#xff0c;但是有一种更好的方式&#xff0c;那就…