驚呆了糟港!不改一行 Java 代碼竟然就能輕松解決敏感信息加解密|原創(chuàng)

來自公眾號(hào):程序通事
作者:樓下小黑哥

出于安全考慮琉兜,現(xiàn)需要將數(shù)據(jù)庫(kù)的中敏感信息加密存儲(chǔ)到數(shù)據(jù)庫(kù)中,但是正常業(yè)務(wù)交互還是需要使用明文數(shù)據(jù)毙玻,所以查詢返回我們還需要經(jīng)過相應(yīng)的解密才能返回給調(diào)用方豌蟋。

?

ps:日常開發(fā)中,我們要有一定的安全意識(shí)淆珊,對(duì)于密碼夺饲,金融數(shù)據(jù)等敏感信息進(jìn)行加密存儲(chǔ)保護(hù)。

?

這個(gè)需求說起來不是很難施符,我們只需要在執(zhí)行 sql 之前,提前將指定數(shù)據(jù)進(jìn)行加密擂找。執(zhí)行 sql 之后戳吝,獲取返回結(jié)果,再進(jìn)行的相應(yīng)的解密贯涎。稍微改造下原有代碼听哭,很快完成需求。

image

?

現(xiàn)有加密算法如 RSA2 塘雳,AES 等陆盘,密文長(zhǎng)度將會(huì)是明文好幾倍。上線加解密方案一定要評(píng)估數(shù)據(jù)庫(kù)現(xiàn)有字段長(zhǎng)度是否滿足加密之后長(zhǎng)度败明。

?

如果這是一張新建的表隘马,上面的實(shí)現(xiàn)方案并沒有什么問題。但是這次我們改造是幾張已有已有「千萬級(jí)」的存量的數(shù)據(jù)的表妻顶,這些數(shù)據(jù)都未被加密存儲(chǔ)酸员。

如果使用上述代碼,使用加密之后的密文信息查詢歷史數(shù)據(jù)讳嘱,當(dāng)然查詢不到任何結(jié)果幔嗦。另外當(dāng)查詢返回的結(jié)果是明文,解密明文數(shù)據(jù)庫(kù)也可能會(huì)導(dǎo)致相應(yīng)的解密錯(cuò)誤沥潭。

所以為了兼容歷史數(shù)據(jù)邀泉,需要進(jìn)行如下改造:

  • 增加新字段存放對(duì)應(yīng)的加密數(shù)據(jù),sql 等值條件查詢修改成 in 查詢
  • 查詢返回的記錄首先判斷是否是密文钝鸽,如果是密文再去解密

代碼改造如下:

image

上述代碼雖然解決業(yè)務(wù)需求汇恤,但是這個(gè)解決方案不是很優(yōu)雅,業(yè)務(wù)代碼改動(dòng)較大寞埠,加解密的代碼不能通用屁置,所有涉及到相關(guān)字段的方法都需要改動(dòng),且?guī)缀醵际侵貜?fù)代碼仁连,代碼侵入性很強(qiáng)蓝角,不是很友好阱穗。

有經(jīng)驗(yàn)的同學(xué)可能會(huì)想到使用 Spring AOP 解決上述問題。

在切面的前置方法「beforeMethod」統(tǒng)一攔截查詢參數(shù)使鹅,配合自定義的注解揪阶,加密指定的字段。

然后在切面的后置方法「afterReturn」攔截返回值患朱,配合自定義注解鲁僚,解密指定的字段。

?

Spring AOP 代碼實(shí)現(xiàn)比較復(fù)雜裁厅,這里就不貼出具體的代碼冰沙。

?

但是 Spring AOP 方案也并不通用,如果其他的應(yīng)用也有相同的需求执虹,同樣的代碼拓挥,又需要重復(fù)實(shí)現(xiàn),還是很費(fèi)時(shí)費(fèi)力袋励。

最終我們參考一個(gè) github 開源項(xiàng)目「typehandlers-encrypt」侥啤,借助 mybatis 的 「TypeHandler」,實(shí)現(xiàn)通用的數(shù)據(jù)加解密解決方案茬故。使用方只需要引入相關(guān)依賴盖灸,「無需改動(dòng)一行業(yè)務(wù)代碼」,僅需少量配置即可實(shí)現(xiàn)指定字段加解密操作磺芭,省時(shí)省力赁炎。

?

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

?

image

實(shí)現(xiàn)原理

mybatis 利用內(nèi)置類型轉(zhuǎn)換器(「typeHandler」),實(shí)現(xiàn) Java 類型與 JDBC 類型的相互轉(zhuǎn)換徘跪,我們正好可以利用這個(gè)特性甘邀,在轉(zhuǎn)換之前加入加解密步驟。

typeHandler 底層原理不是復(fù)雜垮庐,如果我們沒有使用 Mybatis松邪,而是直接使用最原始的 JDBC 執(zhí)行查詢語(yǔ)句,相關(guān)代碼如下:

image

我們需要手動(dòng)判斷 Java 類型哨查,然后調(diào)用 PreparedStatement設(shè)置合適類型參數(shù)逗抑。獲取返回結(jié)果之后,又需要手動(dòng)調(diào)用 ResultSet 結(jié)果集獲取相應(yīng)類型的數(shù)據(jù)寒亥,這個(gè)過程十分繁瑣邮府。

使用 mybatis 之后,上述步驟就無需我們?cè)賹?shí)現(xiàn)了溉奕。mybatis 可以通過識(shí)別 Java/JDBC 類型褂傀,調(diào)用相應(yīng)typeHandler,自動(dòng)實(shí)現(xiàn)轉(zhuǎn)換邏輯加勤。

下圖為 mybatis 內(nèi)置類型轉(zhuǎn)換器仙辟,基本涵蓋了所有 「Java/JDBC」 數(shù)據(jù)類型同波。

image

通用解決方案

自定義 typeHandler

下面我們來實(shí)現(xiàn)帶有加解密功能的類型轉(zhuǎn)換器,實(shí)現(xiàn)方式也比較簡(jiǎn)單叠国,只要繼承 org.apache.ibatis.type.BaseTypeHandler未檩,重寫相關(guān)方法。

?

簡(jiǎn)單起見粟焊,上述加解密僅使用了 Base64冤狡,大家可以替換成相應(yīng)加解密算法即或者引入相應(yīng)加解密服務(wù)。

?

image

其中加密轉(zhuǎn)換將在 setNonNullParameter 中執(zhí)行项棠,解密轉(zhuǎn)換將在 getNullableResult中執(zhí)行悲雳。

CryptTypeHandler 使用一個(gè) MappedTypes 注解,包含一個(gè) CryptType 類香追,這個(gè)類使用 mybatis 別名功能怜奖,可以極大簡(jiǎn)化 sqlmap 相關(guān)配置。

image

注冊(cè) typeHandler

使用方必須將 typeHandleralias 注冊(cè)到 mybatis 中翅阵,否則無法生效。

下面提供三種方式,可以根據(jù)項(xiàng)目情況選擇其中一種即可:

「單獨(dú)使用 mybatis」

這種場(chǎng)景需要在 「mybatis-config.xml」 配置迁央,mybatis 啟動(dòng)時(shí)將會(huì)加載該配置文件掷匠。

<typeHandlers>
  <!--類型轉(zhuǎn)換器包路徑-->
  <package name="com.xx.xx"/>
</typeHandlers>
  <!-- 別名定義 -->
<typeAliases>
  <!-- 針對(duì)單個(gè)別名定義 type:類型的路徑 alias:別名 -->
  <typeAlias type="xx.xx.xx" alias="xx"/>
</typeAliases>

「使用 Spring 配置 Mybatis Bean」

配合 Spring 使用時(shí)需要將 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 方式就最簡(jiǎn)單了岖圈,只要引入 mybatis-starter讹语,配置文件加入如下配置即可:

## mybatis 配置
# 類型轉(zhuǎn)換器包路徑
mybatis.type-handlers-package=com.xx.xx.x
mybatis.type-aliases-package=com.xx.xx

修改 mapper sql 配置

最后我們只要簡(jiǎn)單修改 mapper 中 resultMap 或 sql s配置就可以實(shí)現(xiàn)加解密。

假設(shè)我們對(duì)現(xiàn)有一張 「bank_card」 表進(jìn)行加解密蜂科,表結(jié)構(gòu)如下:

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 '卡號(hào)',
phone varchar(256) NOT NULL DEFAULT '' COMMENT '手機(jī)號(hào)',
name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名',
id_no varchar(256) NOT NULL DEFAULT '' COMMENT '證件號(hào)'
);

「insert 加密」

現(xiàn)需要對(duì) card_no顽决,phonename导匣,id_no 進(jìn)行加密才菠,「insert」 語(yǔ)句加密示例:

<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,傳入?yún)?shù)最后將被加密贡定。使用 typeHandler需要使用類的全路徑赋访,比較繁瑣,我們可以使用 「javaType」 屬性缓待,直接使用上面我們的定義別名 「crypt」蚓耽。

數(shù)據(jù)庫(kù)最終執(zhí)行sql 如下:

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

?

ps:推薦一款 IDEA 的插件 「mybatis-log-plugin」,可以自動(dòng)將 mybatis sql 日志還原成真實(shí)執(zhí)行 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>

這里我們?cè)?「select」 配置中只能使用 resultMap 屬性旋炒,指定 typeHandler 步悠。

數(shù)據(jù)庫(kù)明文、密文共存的情況瘫镇,查詢解密示例如下:

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

最后我們可以將自定義的 typeHandler 單獨(dú)打包發(fā)布鼎兽,其他業(yè)務(wù)方只需要引用答姥,改造相關(guān)配置文件,即可完成數(shù)據(jù)加解密接奈。

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

總結(jié)

借助于自定義的 typeHandler,我們實(shí)現(xiàn)了一個(gè)通用的加解密的方案序宦,該方案對(duì)于使用方來說代碼侵入性小睁壁,開箱即用,可以快速完成加解密的改造互捌。

?

ps:你們是否也有遇到同樣的需求潘明,可以在下方留言寫下你們的方案,互相學(xué)習(xí)秕噪,一起成長(zhǎng)钳降!

?

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

Reference

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

最后

看到這里腌巾,想必大家都累了遂填,放一張趣圖輕松一下。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末澈蝙,一起剝皮案震驚了整個(gè)濱河市吓坚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灯荧,老刑警劉巖礁击,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逗载,居然都是意外死亡哆窿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門厉斟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挚躯,“玉大人,你說我怎么就攤上這事捏膨⊙砭” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵号涯,是天一觀的道長(zhǎng)目胡。 經(jīng)常有香客問我,道長(zhǎng)链快,這世上最難降的妖魔是什么誉己? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮域蜗,結(jié)果婚禮上巨双,老公的妹妹穿的比我還像新娘噪猾。我一直安慰自己,他們只是感情好筑累,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布袱蜡。 她就那樣靜靜地躺著,像睡著了一般慢宗。 火紅的嫁衣襯著肌膚如雪坪蚁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天镜沽,我揣著相機(jī)與錄音敏晤,去河邊找鬼。 笑死缅茉,一個(gè)胖子當(dāng)著我的面吹牛嘴脾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蔬墩,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼译打,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了拇颅?” 一聲冷哼從身側(cè)響起扶平,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔬蕊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哥谷,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岸夯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了们妥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猜扮。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖监婶,靈堂內(nèi)的尸體忽然破棺而出旅赢,到底是詐尸還是另有隱情,我是刑警寧澤惑惶,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布煮盼,位于F島的核電站,受9級(jí)特大地震影響带污,放射性物質(zhì)發(fā)生泄漏僵控。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一鱼冀、第九天 我趴在偏房一處隱蔽的房頂上張望报破。 院中可真熱鬧悠就,春花似錦、人聲如沸充易。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盹靴。三九已至炸茧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鹉究,已是汗流浹背宇立。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留自赔,地道東北人妈嘹。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绍妨,于是被迫代替她去往敵國(guó)和親润脸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355