前言

出于安全考虑,现需要将数据库的中敏感信息加密存储到数据库中,但是正常业务交互还是需要使用明文数据,所以查询返回我们还需要经过相应的解密才能返回给调用方。

ps:日常开发中,我们要有一定的安全意识,对于密码,金融数据等敏感信息事实加密存储保护。

这个需求说起来不是很难,我们只需要在执行 sql 之前,提前将指定数据进行加密。执行 sql 之后,获取返回结果,再进行的相应的解密。稍微改造下原有代码,很快完成需求。

现有加密算法如 RSA2 ,AES 等,密文长度将会是明文好几倍。上线加解密方案一定要评估数据库现有字段长度是否满足加密之后长度。

如果这是一张新建的表,上面的实现方案并没有什么问题。但是这次我们改造是几张已有已有千万级的存量的数据的表,这些数据都未被加密存储。

如果使用上述代码,使用加密之后的密文信息查询历史数据,当然查询不到任何结果。另外当查询返回的结果是明文,解密明文数据库也可能会导致相应的解密错误。

所以为了兼容历史数据,需要进行如下改造:

  • 增加新字段存放对应的加密数据,sql 等值条件查询修改成 in 查询
  • 查询返回的记录首先判断是否是密文,如果是密文再去解密

代码改造如下:

上述代码虽然解决业务需求,但是这个解决方案不是很优雅,业务代码改动较大,加解密的代码不能通用,所有涉及到相关字段的方法都需要改动,且几乎都是重复代码,代码侵入性很强,不是很友好。

有经验的同学可能会想到使用 Spring AOP 解决上述问题。

在切面的前置方法(beforeMethod)统一拦截查询参数,配合自定义的注解,加密指定的字段。

然后在切面的后置方法(afterReturn)拦截返回值,配合自定义注解,解密指定的字段。

Spring AOP 代码实现比较复杂,这里就不贴出具体的代码。

但是 Spring AOP 方案也并不通用,如果其他的应用也有相同的需求,同样的代码,又需要重复实现,还是很费时费力。

最终我们参考一个 github 开源项目『typehandlers-encrypt』,借助 mybatis 的 TypeHandler,实现通用的数据加解密解决方案。使用方只需要引入相关依赖,无需改动一行业务代码,仅需少量配置即可实现指定字段加解密操作,省时省力。

typehandlers-encrypt github 地址:https://github.com/drtrang/typehandlers-encrypt

实现原理

mybatis 利用内置类型转换器(typeHandler),实现 Java 类型与 JDBC 类型的相互转换,我们正好可以利用这个特性,在转换之前加入加解密步骤。

typeHandler 底层原理不是复杂,如果我们没有使用 Mybatis,而是直接使用最原始的 JDBC 执行查询语句,相关代码如下:

我们需要手动判断 Java 类型,然后调用 PreparedStatement设置合适类型参数。获取返回结果之后,又需要手动调用 ResultSet 结果集获取相应类型的数据,这个过程十分繁琐。

使用 mybatis 之后,上述步骤就无需我们再实现了。mybatis 可以通过识别 Java/JDBC 类型,调用相应typeHandler,自动实现转换逻辑。

下图为 mybatis 内置类型转换器,基本涵盖了所有 Java/JDBC 数据类型。

通用解决方案

自定义 typeHandler

下面我们来实现带有加解密功能的类型转换器,实现方式也比较简单,只要继承 org.apache.ibatis.type.BaseTypeHandler,重写相关方法。

简单起见,上述加解密仅使用了 Base64,大家可以替换成相应加解密算法即或者引入相应加解密服务。

其中加密转换将在 setNonNullParameter 中执行,解密转换将在 getNullableResult中执行。

CryptTypeHandler 使用一个 MappedTypes 注解,包含一个 CryptType 类,这个类使用 mybatis 别名功能,可以极大简化 sqlmap 相关配置。

注册 typeHandler

使用方必须将 typeHandler 和 alias 注册到 mybatis 中,否则无法生效。

下面提供三种方式,可以根据项目情况选择其中一种即可:

单独使用 mybatis

这种场景需要在 mybatis-config.xml 配置,mybatis 启动时将会加载该配置文件。

<typeHandlers>
<!--类型转换器包路径-->
<package name="com.xx.xx"/>
</typeHandlers>
<!-- 别名定义 -->
<typeAliases>
<!-- 针对单个别名定义 type:类型的路径 alias:别名 -->
<typeAlias type="xx.xx.xx" alias="xx"/>
</typeAliases>

使用 Spring 配置 Mybatis Bean

配合 Spring 使用时需要将 typeHandler 注入 SqlSessionFactoryBean ,配置方式如下:

<!-- MyBatis 工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" /> <!--alias 注入-->
<property name="typeAliasesPackage" value="xx.xx.xx"/>
<!-- typeHandlers 注入 -->
<property name="typeHandlersPackage" value="xx.xx.xx"/>
</bean>

SpringBoot

SpringBoot 方式就最简单了,只要引入 mybatis-starter,配置文件加入如下配置即可:

## mybatis 配置
# 类型转换器包路径
mybatis.type-handlers-package=com.xx.xx.x
mybatis.type-aliases-package=com.xx.xx

修改 mapper sql 配置

最后我们只要简单修改 mapper 中 resultMap 或 sql s配置就可以实现加解密。

假设我们对现有一张 bank_card 表进行加解密,表结构如下:

CREATE TABLE bank_card (
id int primary key auto_increment,
gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
gmt_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
card_no varchar(256) NOT NULL DEFAULT '' COMMENT '卡号',
phone varchar(256) NOT NULL DEFAULT '' COMMENT '手机号',
name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名',
id_no varchar(256) NOT NULL DEFAULT '' COMMENT '证件号'
);

insert 加密

现需要对 card_no,phone,name,id_no 进行加密,insert 语句加密示例:

<insert id="insertBankCard" keyProperty="id" useGeneratedKeys="true" parameterType="org.demo.pojo.BankCardDO">
INSERT INTO bank_card (card_no, phone,name,id_no)
VALUES
(#{card_no,javaType=crypt},
#{phone,typeHandler=org.demo.type.CryptTypeHandler},
#{name,javaType=crypt},
#{id_no,javaType=crypt})
</insert>

我们只需要在 #{} 指定 typeHandler,传入参数最后将被加密。使用 typeHandler需要使用类的全路径,比较繁琐,我们可以使用 javaType 属性,直接使用上面我们的定义别名 crypt。

数据库最终执行sql 如下:

INSERT INTO bank_card (card_no, phone,name,id_no) VALUES ('NjQzMjEyMzEyMzE=', 'MTM1Njc4OTEyMzQ=', '5rWL6K+V5Y2h', 'MTIzMTIzMTIzMQ==');

ps:推荐一款 IDEA 的插件 mybatis-log-plugin,可以自动将 mybatis sql 日志还原成真实执行 sql

查询加解密

普通查询解密示例如下:

<resultMap id="bankCardXml" type="org.demo.pojo.BankCardDO">
<result property="card_no" column="card_no" typeHandler="org.demo.type.CryptTypeHandler"/>
<result property="name" column="name" typeHandler="org.demo.type.CryptTypeHandler"/>
<result property="id_no" column="id_no" typeHandler="org.demo.type.CryptTypeHandler"/>
<result property="phone" column="phone" typeHandler="org.demo.type.CryptTypeHandler"/>
</resultMap>
<select id="queryById" resultMap="bankCardXml">
select * from bank_card where id=#{id}
</select>

这里我们在 select 配置中只能使用 resultMap 属性,指定 typeHandler 。

数据库明文、密文共存的情况,查询解密示例如下:

<!-- resultMap 同上   -->
<select id="queryByPhone" resultMap="bankCardXml">
select * from bank_card where phone in(#{card_no,javaType=crypt},#{card_no})
</select>

最后我们可以将自定义的 typeHandler 单独打包发布,其他业务方只需要引用,改造相关配置文件,即可完成数据加解密。

上述代码示例已上传至 Github,地址:https://github.com/9526xu/mybatis-encrypt

总结

借助于自定义的 typeHandler,我们实现了一个通用的加解密的方案,该方案对于使用方来说代码侵入性小,开箱即用,可以快速完成加解密的改造。

ps:你们是否也有遇到同样的需求,可以在下方留言写下你们的方案,互相学习,一起成长!

最后感谢一下@辉哥提供解决思路。

Reference

  1. https://github.com/9526xu/mybatis-encrypt
  2. https://github.com/drtrang/typehandlers-encrypt

最后(求关注)

看到这里,想必大家都累了,放一张趣图轻松一下。

最后再次感谢您的阅读,我是楼下小黑哥,一位还未秃头的工具猿,下篇文章我们再见~

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn

惊呆了!不改一行 Java 代码竟然就能轻松解决敏感信息加解密|原创的更多相关文章

  1. 操作系统 | 结合 CPU 理解一行 Java 代码是怎么执行的

    根据冯·诺依曼思想,计算机采用二进制作为数制基础,必须包含:运算器.控制器.存储设备,以及输入输出设备,如下图所示. 我们先来分析 CPU 的工作原理,现代 CPU 芯片中大都集成了,控制单元,运算单 ...

  2. 一行Java代码实现游戏中交换装备

    摘要:JDK 1.5 开始 JUC 包下提供的 Exchanger 类可用于两个线程之间交换信息. 本文分享自华为云社区<一行Java代码实现两玩家交换装备[并发编程]>,作者:陈皮的Ja ...

  3. java实现工程配置文件敏感字段加解密

    以下引自他人博客: 1. 需求背景我们在开发应用时,需要连接数据库,一般把数据库信息放在一个属性配置文件中,比如***.properties,具体的内容 #mysql的配置文件jdbc.url=jdb ...

  4. Java之路第一步——第一行Java代码

    main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法. 名字必须是main: 必须是public static void 类型的: 必须接收一 ...

  5. Eclipse修改java代码后自动重启Tomcat解决办法

    今天甚是郁闷,项目马上要上线了,早上刚到公司打开MyEclipse 10.07提示过期提示,这对于用惯了破解软件的帝国用户的我原本以为小菜一碟. 于是到网上到处找破解软件,不用多长时间,Ok 破解成功 ...

  6. java代码----I/O流从控制台输入信息判断并抛出异常

    package com.a.b; import java.io.*; public class Yu { public static void main(String[] args) throws I ...

  7. Java中使用OpenSSL生成公钥私钥进行数据加解密

    当前使用的是Linux系统,已经安装OpenSSL软件包. 一.使用OpenSSL来生成私钥和公钥1.执行命令openssl version -a 验证机器上已经安装openssl $ openssl ...

  8. java加密工具类,可设置对应的加解密key

    public class AesEncryptUtil { //使用AES-128-CBC加密模式,key需要为16位,key和iv可以相同! private static String KEY =& ...

  9. Java和操作系统交互(Java 代码是怎么执行)(转)

    结合 CPU 理解一行 Java 代码是怎么执行的 根据冯·诺依曼思想,计算机采用二进制作为数制基础,必须包含:运算器.控制器.存储设备,以及输入输出设备,如下图所示. 我们先来分析 CPU 的工作原 ...

随机推荐

  1. java异常和throw和throws的区别

    之前在编程中编译完成后,运行时,会遇见一些常见的错误,如NullPointerException,ArrayIndexOutOfBoundsException等等 在今天重新回顾学习了java异常,总 ...

  2. 软件WEB自动化测试工具之智能元素定位

    江湖一直有着这么一句名言“天下武功,唯快不破".那么在软件测试领域,自然而然我们会想到软件自动化测试.软件自动化测试的实现自然离不开软件自动化测试工具.软件自动化测试工具是软件自动化的载体, ...

  3. JavaFX之多个FXML加载和通信

    前言 在使用了FXML设计布局后,新的问题随之而来,当一个程序需要多个界面时,我们不可能在一个FXML中写出全部布局,这样太过于臃肿不易查看和维护(当然非要这么做也是可以的),这里就涉及到如何在一个F ...

  4. MySQL中INSERT INTO SELECT的使用

    1. 语法介绍      有三张表a.b.c,现在需要从表b和表c中分别查几个字段的值插入到表a中对应的字段.对于这种情况,可以使用如下的语句来实现: INSERT INTO db1_name (fi ...

  5. go源码分析(一) 通过调试看go程序初始化过程

    参考资料:Go 1.5 源码剖析 (书签版).pdf 编写go语言test.go package main import ( "fmt" ) func main(){ fmt.Pr ...

  6. 量化学习 | Tushare 基本面选股 (二)

    量化投资比较重要的是策略,可是你得先选个好股,价值投资需要认同他的价值,值得投资的股票才有投资的机会,现在简单介绍一下基于基本面的选股,其实我现实生活中也有炒股,都是经验之说的选股原则. 首先从tus ...

  7. 【Weiss】简单说一下这一分类下的东西

    主要是学习资料<数据结构与算法分析>(Weiss)的习题 除去习题外,每一章主要用到的数据结构先会写一个版本放上来,包括数据结构代码与测试用代码 这种先行上传的代码只具有基本的功能,毕竟一 ...

  8. 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)

    要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...

  9. 10. webdriver调用javascript

    webdriver提供了操作浏览器的前进和后退的方法,但是对于浏览器公东条并没有提供相应的操作方法.于是就需要借助JavaScript来控制浏览器的滚动条.webdriver提供了execute_sr ...

  10. 理解Go语言组件flag

    作用 主要用来实现命令行的参数解析,以达到实现以下效果的目的 $ cmd -flagname 123 使用方式 flag是Go语言的内置包,能接收的参数类型主要有字符串.布尔和数值类型. 方式一 fu ...