惊呆了!不改一行 Java 代码居然就能轻松解决敏感信息加解密|原创

file

前言

出于安全考虑,现须要将数据库的中敏感信息加密存储到数据库中,可是正常业务交互仍是须要使用明文数据,因此查询返回咱们还须要通过相应的解密才能返回给调用方。java

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

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

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

若是这是一张新建的表,上面的实现方案并无什么问题。可是此次咱们改造是几张已有已有千万级的存量的数据的表,这些数据都未被加密存储。spring

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

因此为了兼容历史数据,须要进行以下改造:数据库

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

代码改造以下:apache

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

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

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

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

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

可是 Spring AOP 方案也并不通用,若是其余的应用也有相同的需求,一样的代码,又须要重复实现,仍是很费时费力。

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

typehandlers-encrypt github 地址:github.com/drtrang/typ…

实现原理

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 单独打包发布,其余业务方只须要引用,改造相关配置文件,便可完成数据加解密。

上述代码示例已上传至 Github,地址:github.com/9526xu/myba…

总结

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

ps:大家是否也有遇到一样的需求,能够在下方留言写下大家的方案,互相学习,一块儿成长!

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

Reference

  1. github.com/9526xu/myba…
  2. github.com/drtrang/typ…

最后(求关注)

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

当你在 github 提交相关 issue 期待其余人回答解答问题时

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

欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客:studyidea.cn

相关文章
相关标签/搜索