惊呆了!不改一行 Java 代码竟然就能轻松解决敏感信息加解密
前言
出于安全考慮,現需要將數據庫的中敏感信息加密存儲到數據庫中,但是正常業務交互還是需要使用明文數據,所以查詢返回我們還需要經過相應的解密才能返回給調用方。
?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
使用方必須將 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 單獨打包發布,其他業務方只需要引用,改造相關配置文件,即可完成數據加解密。
總結
借助于自定義的 typeHandler,我們實現了一個通用的加解密的方案,該方案對于使用方來說代碼侵入性小,開箱即用,可以快速完成加解密的改造。
?ps:你們是否也有遇到同樣的需求,可以在下方留言寫下你們的方案,互相學習,一起成長!
?最后感謝一下@輝哥提供解決思路。
Reference
https://github.com/9526xu/mybatis-encrypt
https://github.com/drtrang/typehandlers-encrypt
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的惊呆了!不改一行 Java 代码竟然就能轻松解决敏感信息加解密的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一致性哈希的分析与实现
- 下一篇: Arrays(Chapter 6 of