50行代码,搞定敏感数据读写!
每天早上七點三十,準時推送干貨
一、介紹
在實際的軟件系統開發過程中,由于業務的需求,在代碼層面實現數據的脫敏還是遠遠不夠的,往往還需要在數據庫層面針對某些關鍵性的敏感信息,例如:身份證號、銀行卡號、手機號、工資等信息進行加密存儲,實現真正意義的數據混淆脫敏,以滿足信息安全的需要。
那在實際的研發過程中,我們如何實踐呢?
二、方案實踐
在此,提供三套方案以供大家選擇。
通過 SQL 函數實現加解密
對 SQL 進行解析攔截,實現數據加解密
自定義一套脫敏工具
2.1、通過 SQL 函數實現加解密
最簡單的方法,莫過于直接在數據庫層面操作,通過函數對某個字段進行加、解密,例如如下這個案例!
--?對“你好,世界”進行加密 select???HEX(AES_ENCRYPT('你好,世界','ABC123456'));--?解密,輸出:你好,世界 select??AES_DECRYPT(UNHEX('A174E3C13FE16AA0FD071A4BBD7CD7C5'),'ABC123456');采用Mysql內置的AES協議加、解密函數,密鑰是ABC123456,可以很輕松的對某個字段實現加、解密。
如果是很小的需求,需要加密的數據就是指定的信息,此方法可行。
但是當需要加密的表字段非常多的時候,這個使用起來就比較雞肋了,例如我想更改加密算法或者不同的部署環境配置不同的密鑰,這個時候就不得不把所有的代碼進行更改一遍。
2.2、對 SQL 進行解析攔截,實現數據加解密
通過上面的方案,我們發現最大的痛點就是加密算法和密鑰都寫死在SQL上了,因此我們可以將這塊的服務從抽出來,在JDBC層面,當sql執行的時候,對其進行攔截處理。
Apache ShardingSphere 框架下的數據脫敏模塊,它就可以幫助我們實現這一需求,如果你是SpringBoot項目,可以實現無縫集成,對原系統的改造會非常少。
下面以用戶表為例,我們來看看采用ShardingSphere如何實現!
2.2.1、創建用戶表
CREATE?TABLE?user?(id?bigint(20)?NOT?NULL?COMMENT?'用戶ID',email?varchar(255)??NOT?NULL?DEFAULT?''?COMMENT?'郵件',nick_name?varchar(255)??DEFAULT?NULL?COMMENT?'昵稱',pass_word?varchar(255)??NOT?NULL?DEFAULT?''?COMMENT?'二次密碼',reg_time?varchar(255)??NOT?NULL?DEFAULT?''?COMMENT?'注冊時間',user_name?varchar(255)??NOT?NULL?DEFAULT?''?COMMENT?'用戶名',salary?varchar(255)?DEFAULT?NULL?COMMENT?'基本工資',PRIMARY?KEY?(id)?USING?BTREE )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4?COLLATE=utf8mb4_general_ci;2.2.2、創建 springboot 項目并添加依賴包
<dependencies><!--spring?boot核心--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--spring?boot?測試--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--springmvc?web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql?數據源--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--mybatis?支持--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency>?<!--shardingsphere數據分片、脫敏工具--><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-namespace</artifactId><version>4.1.0</version></dependency> </dependencies>2.2.3、添加脫敏配置
在application.properties文件中,添加shardingsphere相關配置,即可實現針對某個表進行脫敏
server.port=8080logging.path=log#shardingsphere數據源集成 spring.shardingsphere.datasource.name=ds spring.shardingsphere.datasource.ds.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds.jdbc-url=jdbc:mysql://127.0.0.1:3306/test spring.shardingsphere.datasource.ds.username=xxxx spring.shardingsphere.datasource.ds.password=xxxx#加密方式、密鑰配置 spring.shardingsphere.encrypt.encryptors.encryptor_aes.type=aes spring.shardingsphere.encrypt.encryptors.encryptor_aes.props.aes.key.value=hkiqAXU6Ur5fixGHaO4Lb2V2ggausYwW #plainColumn表示明文列,cipherColumn表示脫敏列 spring.shardingsphere.encrypt.tables.user.columns.salary.plainColumn= spring.shardingsphere.encrypt.tables.user.columns.salary.cipherColumn=salary #spring.shardingsphere.encrypt.tables.user.columns.pass_word.assistedQueryColumn= spring.shardingsphere.encrypt.tables.user.columns.salary.encryptor=encryptor_aes#sql打印 spring.shardingsphere.props.sql.show=true spring.shardingsphere.props.query.with.cipher.column=true#基于xml方法的配置 mybatis.mapper-locations=classpath:mapper/*.xml其中下面的配置信息是關鍵的一部,spring.shardingsphere.encrypt.tables是指要脫敏的表,user是表名,salary表示user表中的真實列,其中plainColumn指的是明文列,cipherColumn指的是脫敏列,如果是新工程,只需要配置脫敏列即可!
spring.shardingsphere.encrypt.tables.user.columns.salary.plainColumn= spring.shardingsphere.encrypt.tables.user.columns.salary.cipherColumn=salary #spring.shardingsphere.encrypt.tables.user.columns.pass_word.assistedQueryColumn= spring.shardingsphere.encrypt.tables.user.columns.salary.encryptor=encryptor_aes2.2.4、編寫數據持久層
<mapper?namespace="com.example.shardingsphere.mapper.UserMapperXml"?><resultMap?id="BaseResultMap"?type="com.example.shardingsphere.entity.UserEntity"?><id?column="id"?property="id"?jdbcType="BIGINT"?/><result?column="email"?property="email"?jdbcType="VARCHAR"?/><result?column="nick_name"?property="nickName"?jdbcType="VARCHAR"?/><result?column="pass_word"?property="passWord"?jdbcType="VARCHAR"?/><result?column="reg_time"?property="regTime"?jdbcType="VARCHAR"?/><result?column="user_name"?property="userName"?jdbcType="VARCHAR"?/><result?column="salary"?property="salary"?jdbcType="VARCHAR"?/></resultMap><select?id="findAll"?resultMap="BaseResultMap">SELECT?*?FROM?user</select><insert?id="insert"?parameterType="com.example.shardingsphere.entity.UserEntity">INSERT?INTO?user(id,email,nick_name,pass_word,reg_time,user_name,?salary)VALUES(#{id},#{email},#{nickName},#{passWord},#{regTime},#{userName},?#{salary})</insert> </mapper>public?interface?UserMapperXml?{/***?查詢所有的信息*?@return*/List<UserEntity>?findAll();/***?新增數據*?@param?user*/void?insert(UserEntity?user); }public?class?UserEntity?{private?Long?id;private?String?email;private?String?nickName;private?String?passWord;private?String?regTime;private?String?userName;private?String?salary;//省略set、get...}2.2.5、最后我們來測試一下程序運行情況
編寫啟用服務程序
@SpringBootApplication @MapperScan("com.example.shardingsphere.mapper") public?class?ShardingSphereApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(ShardingSphereApplication.class,?args);} }編寫單元測試
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes?=?ShardingSphereApplication.class) public?class?UserTest?{@Autowiredprivate?UserMapperXml?userMapperXml;@Testpublic?void?insert()?throws?Exception?{UserEntity?entity?=?new?UserEntity();entity.setId(3l);entity.setEmail("123@123.com");entity.setNickName("阿三");entity.setPassWord("123");entity.setRegTime("2021-10-10?00:00:00");entity.setUserName("張三");entity.setSalary("2500");userMapperXml.insert(entity);}@Testpublic?void?query()?throws?Exception?{List<UserEntity>?dataList?=?userMapperXml.findAll();System.out.println(JSON.toJSONString(dataList));} }插入數據后,如下圖,數據庫存儲的數據已被加密!
我們繼續來看看,運行查詢服務,結果如下圖,數據被成功解密!
采用配置方式,最大的好處就是直接通過配置脫敏列就可以完成對某些數據表字段的脫敏,非常方便。
2.3、自定義一套脫敏工具
當然,有的同學可能會覺得shardingsphere配置雖然簡單,但是還是不放心,里面的很多規則自己無法掌控,想自己開發一套數據庫的脫敏工具。
方案也是有的,例如如下這套實踐方案,以Mybatis為例:
首先編寫一套加解密的算法工具類
通過Mybatis的typeHandler插件,實現特定字段的加解密
實踐過程如下:
2.3.1、加解密工具類
public?class?AESCryptoUtil?{private?static?final?Logger?log?=?LoggerFactory.getLogger(AESCryptoUtil.class);private?static?final?String?DEFAULT_ENCODING?=?"UTF-8";private?static?final?String?AES?=?"AES";/***?加密**?@param?content?需要加密內容*?@param?key?????任意字符串*?@return*?@throws?Exception*/public?static?String?encryptByRandomKey(String?content,?String?key)?{try?{//構造密鑰生成器,生成一個128位的隨機源,產生原始對稱密鑰KeyGenerator?keygen?=?KeyGenerator.getInstance(AES);SecureRandom?random?=?SecureRandom.getInstance("SHA1PRNG");random.setSeed(key.getBytes());keygen.init(128,?random);byte[]?raw?=?keygen.generateKey().getEncoded();SecretKey?secretKey?=?new?SecretKeySpec(raw,?AES);Cipher?cipher?=?Cipher.getInstance(AES);cipher.init(Cipher.ENCRYPT_MODE,?secretKey);byte[]?encrypted?=?cipher.doFinal(content.getBytes("utf-8"));return?Base64.getEncoder().encodeToString(encrypted);}?catch?(Exception?e)?{log.warn("AES加密失敗,參數:{},錯誤信息:{}",?content,?e);return?"";}}public?static?String?decryptByRandomKey(String?content,?String?key)?{try?{//構造密鑰生成器,生成一個128位的隨機源,產生原始對稱密鑰KeyGenerator?generator?=?KeyGenerator.getInstance(AES);SecureRandom?random?=?SecureRandom.getInstance("SHA1PRNG");random.setSeed(key.getBytes());generator.init(128,?random);SecretKey?secretKey?=?new?SecretKeySpec(generator.generateKey().getEncoded(),?AES);Cipher?cipher?=?Cipher.getInstance(AES);cipher.init(Cipher.DECRYPT_MODE,?secretKey);byte[]?encrypted?=?Base64.getDecoder().decode(content);byte[]?original?=?cipher.doFinal(encrypted);return?new?String(original,?DEFAULT_ENCODING);}?catch?(Exception?e)?{log.warn("AES解密失敗,參數:{},錯誤信息:{}",?content,?e);return?"";}}public?static?void?main(String[]?args)?{String?encryptResult?=?encryptByRandomKey("Hello?World",?"123456");System.out.println(encryptResult);String?decryptResult?=?decryptByRandomKey(encryptResult,?"123456");System.out.println(decryptResult);} }2.3.2、針對 salary 字段進行單獨解析
<mapper?namespace="com.example.shardingsphere.mapper.UserMapperXml"?><resultMap?id="BaseResultMap"?type="com.example.shardingsphere.entity.UserEntity"?><id?column="id"?property="id"?jdbcType="BIGINT"?/><result?column="email"?property="email"?jdbcType="VARCHAR"?/><result?column="nick_name"?property="nickName"?jdbcType="VARCHAR"?/><result?column="pass_word"?property="passWord"?jdbcType="VARCHAR"?/><result?column="reg_time"?property="regTime"?jdbcType="VARCHAR"?/><result?column="user_name"?property="userName"?jdbcType="VARCHAR"?/><result?column="salary"?property="salary"?jdbcType="VARCHAR"typeHandler="com.example.shardingsphere.handle.EncryptDataRuleTypeHandler"/></resultMap><select?id="findAll"?resultMap="BaseResultMap">select?*?from?user</select><insert?id="insert"?parameterType="com.example.shardingsphere.entity.UserEntity">INSERT?INTO?user(id,email,nick_name,pass_word,reg_time,user_name,?salary)VALUES(#{id},#{email},#{nickName},#{passWord},#{regTime},#{userName},#{salary,jdbcType=INTEGER,typeHandler=com.example.shardingsphere.handle.EncryptDataRuleTypeHandler})</insert> </mapper>EncryptDataRuleTypeHandler解析器,內容如下:
public?class?EncryptDataRuleTypeHandler?implements?TypeHandler<String>?{private?static?final?String?EMPTY?=?"";/***?寫入數據*?@param?preparedStatement*?@param?i*?@param?data*?@param?jdbcType*?@throws?SQLException*/@Overridepublic?void?setParameter(PreparedStatement?preparedStatement,?int?i,?String?data,?JdbcType?jdbcType)?throws?SQLException?{if?(StringUtils.isEmpty(data))?{preparedStatement.setString(i,?EMPTY);}?else?{preparedStatement.setString(i,?AESCryptoUtil.encryptByRandomKey(data,?"123456"));}}/***?讀取數據*?@param?resultSet*?@param?columnName*?@return*?@throws?SQLException*/@Overridepublic?String?getResult(ResultSet?resultSet,?String?columnName)?throws?SQLException?{return?decrypt(resultSet.getString(columnName));}/***?讀取數據*?@param?resultSet*?@param?columnIndex*?@return*?@throws?SQLException*/@Overridepublic?String?getResult(ResultSet?resultSet,?int?columnIndex)?throws?SQLException?{return?decrypt(resultSet.getString(columnIndex));}/***?讀取數據*?@param?callableStatement*?@param?columnIndex*?@return*?@throws?SQLException*/@Overridepublic?String?getResult(CallableStatement?callableStatement,?int?columnIndex)?throws?SQLException?{return?decrypt(callableStatement.getString(columnIndex));}/***?對數據進行解密*?@param?data*?@return*/private?String?decrypt(String?data)?{return?AESCryptoUtil.decryptByRandomKey(data,?"123456");} }2.3.3、單元測試
再次運行單元測試,程序讀寫正常!
通過如下的方式,也可以實現對數據表中某個特定字段進行數據脫敏處理!
三、小結
因業務的需求,當需要對某些數據表字段進行脫敏處理的時候,有個細節很容易遺漏,那就是字典類型,例如salary字段,根據常規,很容易想到使用數字類型,但是卻不是,要知道加密之后的數據都是一串亂碼,數字類型肯定是無法存儲字符串的,因此在定義的時候,這個要留心一下。
其次,很多同學可能會覺得,這個也不能防范比人竊取數據啊!
如果加密使用的密鑰和數據都在一個項目里面,答案是肯定的,你可以隨便解析任何人的數據。因此在實際的處理上,這個更多的是在流程上做變化。例如如下方式:
首先,加密采用的密鑰會在另外一個單獨的服務來存儲管理,保證密鑰不輕易泄露出去,最重要的是加密的數據不輕易被別人解密。
其次,例如某些人想要訪問誰的工資條數據,那么就需要做二次密碼確認,也就是輸入自己的密碼才能獲取,可以進一步防止研發人員隨意通過接口方式讀取數據。
最后就是,杜絕代碼留漏洞。
以上三套方案,都可以幫助大家實現數據庫字段數據的脫敏,希望能幫助到大家,謝謝欣賞!
四、參考
1、敏感數據,“一鍵脫敏”,Sharding Sphere 完美搞定?
總結
以上是生活随笔為你收集整理的50行代码,搞定敏感数据读写!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spyder:Python中机器学习的强
- 下一篇: c#串口程序接收数据并打印_C#程序可打