驚呆了!不改一行 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 地址: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 單獨打包發佈,其餘業務方只須要引用,改造相關配置文件,便可完成數據加解密。

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

總結

藉助於自定義的 typeHandler,咱們實現了一個通用的加解密的方案,該方案對於使用方來講代碼侵入性小,開箱即用,能夠快速完成加解密的改造。

ps:大家是否也有遇到一樣的需求,能夠在下方留言寫下大家的方案,互相學習,一塊兒成長!

最後感謝一下**@輝哥**提供解決思路。

Reference

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

最後(求關注)

看到這裏,想必你們都累了,放一張趣圖輕鬆一下。

當你在 github 提交相關 issue 期待其餘人回答解答問題時

最後再次感謝您的閱讀,我是樓下小黑哥,一位還未禿頭的工具猿,下篇文章咱們再見~

歡迎關注個人公衆號:程序通事,得到平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客:studyidea.cn

相關文章
相關標籤/搜索