前言

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

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 执行查询语句,相关代码如下:

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 相关配置。

alias

注册 typeHandler

使用方必须将 typeHandleralias 注册到 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_nophonenameid_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 单独打包发布,其他业务方只需要引用,改造相关配置文件,即可完成数据加解密。

总结

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

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

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

Reference

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

  2. https://github.com/drtrang/typehandlers-encrypt

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

惊呆了!不改一行 Java 代码竟然就能轻松解决敏感信息加解密相关推荐

  1. delphi7aes加密解密与java互转_惊呆了!不改一行Java代码竟然就能轻松解决敏感信息加解密|原创

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

  2. 解决 IDEA 调用其他类的时候自动加上包路径和类名的情况_惊呆了!不改一行 Java 代码竟然就能轻松解决敏感信息加解密...

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

  3. idea2020shezhi代码检查级别_结合 CPU 理解一行 Java 代码是怎么执行的

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

  4. java main函数_一行JAVA代码如何运行起来?

    在程序员的世界中,你总会听到一句"PHP是世界上最好的语言"的调侃.然而在你进入软件程序开发之后,你会发现即使开发语言千千万,最盛行的还是JAVA.从淘宝的技术变迁中我们可以见一些 ...

  5. java JLabel改变大小后如何刷新_到底一行java代码是如何在计算机上执行的

    不知道你是否思考过,每次我们在IDEA中右键Run Application启动主方法,假如程序运行正常,控制台也打印出了你所要打印的信息,在这个过程中你知道这台计算机上那些硬件及其软件都是以什么样的方 ...

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

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

  7. 第一行Java代码,java高级面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  8. 一行 Java 代码是怎么执行的。

    一行代码能够执行,必须要有可以执行的上下文环境,包括,指令寄存器,数据寄存器,栈空间等内存资源,然后这行代码必须作为一个执行流能够被操作系统的任务调度器识别,并给他分配 CPU 资源,当然这行代码所代 ...

  9. 写了这么久Java项目,是否还记得你的第一行Java代码

    前言 个人情况 首先介绍一下本人的情况,我来自于一个双非渣渣二本学院,目前处于大四阶段,由于在小学的时候就开始接触了电脑,一直以来也对IT的各方面有着浓厚的兴趣,所以在高考结束填写志愿书的时候,就毅然 ...

最新文章

  1. 最新发布| Jira官宣中国区本地部署特殊政策,公布Data Center价格
  2. spark RDD官网RDD编程指南
  3. js在IE下面弹出打开和保存文件的对话框
  4. 订单编号,递增且不连续(php版)
  5. sudo: Cannot execute /usr/local/bin/zsh: No such file or directory 问题
  6. linux下Java环境的配置
  7. 刚买的iPad可获1100元退款
  8. 计算机专业职称入深户,最近深圳很火的职称入户,到底要选什么职称?
  9. 面向对象(OOP)五大基本原则
  10. linux jenkins自动部署,【linux】【jenkins】自动化部署一 安装jenkins
  11. 错误代码1500什么意思_为什么藏族可以吃牦牛肉?(笑cry~
  12. 仿照MEMZ做一个特效程序
  13. plsql 破解注册码
  14. SAP 金税解决方案
  15. IIS管理器FTP站点中FTP防火墙支持页面
  16. TF标准模型TensorFlow Mobile for Android
  17. 中学关于计算机方面的课题研究,《多媒体信息技术与初中生物教学的整合》课题研究方案...
  18. 面试必须学会的八大技巧——让你在面试官面前游刃有余(面试注意事项等)
  19. 2、面向对象的思维(与结构化思维比较)
  20. 虾扑 - 货源采集便捷无忧

热门文章

  1. wps的流程图怎么导出_还在当灵魂画手?WPS教育版“绘图工具”助你做大牛—思维导图篇...
  2. html上拉下拉查看文字内容,html5上拉下拉事件效果演示
  3. mybatis是什么_深入解析:Mybatis接口没有实现类为什么可以执行增删改查?
  4. 导出到文件_Java项目导出可运行的jar文件
  5. 移动互联网APP测试流程及测试点(转载) (二)
  6. 后端返回页面ajax的处理
  7. Windows中常用的函数调用规范
  8. Shell字符串比较(等于、不等于、大于、小于、起始字符、结尾字符)
  9. Unicode-objects must be encoded before hashing
  10. mysql整除、取余、四舍五入