數據加密概述 加密:將明文信息改變為難以讀取的密文內容。 解密:將密文內容轉化為原來數據。 分類 對稱加密:加密與解密密鑰相同。 非對稱加密:加密使用公鑰,公鑰可公開;解密使用私鑰。 相關閱讀: 加密 - wikipedia 資料庫隱私欄位加密註解組件實現 說明 資料庫存儲密文欄位,記憶體可見為明文信 ...
數據加密概述
加密:將明文信息改變為難以讀取的密文內容。
解密:將密文內容轉化為原來數據。
分類
- 對稱加密:加密與解密密鑰相同。
- 非對稱加密:加密使用公鑰,公鑰可公開;解密使用私鑰。
相關閱讀:
資料庫隱私欄位加密註解組件實現
說明
- 資料庫存儲密文欄位,記憶體可見為明文信息
- 可設定欄位保存值支持 整體加密(僅可全部匹配查詢)、模糊加密(支持模糊查詢)
- 相關做法: 資料庫隱私欄位加密以及加密後的數據如何進行模糊查詢? - 業餘草
MyBatis-Plus 實現(做法:使用 常規2 處理)
-
整體加密資料庫欄位長度與保存值長度對應(需要額外存儲一個標識符)
資料庫欄位長度 字元長度 varchar(49) 12個中文字元、36個ascii字元 varchar(97) 24個中文字元、72個ascii字元、3個ascii字元+23個中文字元、68個ascii字元+1個中文字元 varchar(197) 49個中文字元、145個ascii字元、3個ascii字元+48個中文字元 、 142個ascii字元+1個中文字元 varchar(253) 63個中文字元、189個ascii字元 、3個ascii字元+62個中文字元 、 186個ascii字元+1個中文字元 -
模糊加密資料庫欄位長度與保存值長度對應(4個位元組加密一次大致對應8個字元+1個標識符),根據4位英文字元(半形),2個中文字元(全形)為一個檢索條件
資料庫欄位長度 字元長度 varchar(99) 12個中文字元、14個ascii字元 varchar(198) 23個中文字元、25個ascii字元 varchar(252) 29個中文字元、31個ascii字元
1 :利用 TypeHandler 支持數據加解密轉換
INSERT VALUE、UPDATE ENTITY、SELECT RESULT
只對資料庫和程式之間的數據轉換,查詢條件不會調用。
-
整體加密類型處理器
使用示例: 1. MyBatis-Plus 註解(自動生產 ResultMap ,存在場景不生效) @TableField(typeHandler = OverallCryptoTypeHandler.class) 2. 自定義 ResultMap 配置 <result column="phone" property="phone" typeHandler="cn.eastx.practice.demo.crypto.config.mp.OverallCryptoTypeHandler" />
整體加密類型處理器 OverallCryptoTypeHandler.java
@Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { /* 對非null參數值進行加密,需要通過實體類處理方可,支持 INSERT/UPDATE ENTITY 當前處理 INSERT ENTITY,UPDATE ENTITY 會先通過攔截器處理 因為攔截器修改元數據將導致實體類屬性值產生變更,所以實體類還是由 TypeHandler 來進行處理 */ ps.setString(i, CryptoDataUtil.overallEncrypt(parameter)); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { // 對可為null的結果進行解密 return CryptoDataUtil.decrypt(rs.getString(columnName)); }
-
模糊加密類型處理器
註意:根據4位英文字元(半形),2個中文字元(全形)為一個檢索條件,如果欄位值較少查詢可能存在問題使用示例: 1. MyBatis-Plus 註解(自動生產 ResultMap ,存在場景不生效) @TableField(typeHandler = FuzzyCryptoTypeHandler.class) 2. 自定義 ResultMap 配置 <result column="phone" property="phone" typeHandler="cn.eastx.practice.demo.crypto.config.mp.FuzzyCryptoTypeHandler" />
模糊加密類型處理器 FuzzyCryptoTypeHandler.java
@Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { /* 對非null參數值進行加密,需要通過實體類處理方可,支持 INSERT/UPDATE ENTITY 當前處理 INSERT ENTITY,UPDATE ENTITY 會先通過攔截器處理 因為攔截器修改元數據將導致實體類屬性值產生變更,所以實體類還是由 TypeHandler 來進行處理 */ ps.setString(i, CryptoDataUtil.fuzzyEncrypt(parameter)); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { // 對可為null的結果進行解密 return CryptoDataUtil.decrypt(rs.getString(columnName)); }
2 :利用欄位註解配合 MyBatis 攔截器對條件進行攔截處理
註:目前僅支持簡單查詢處理,複雜查詢可能存在問題。
-
自定義欄位註解
CryptoCond.java
- 使用
replacedColumn()
替換 SQL 查詢條件中的欄位名 - 使用
encryption()
對 SQL 中條件值、參數值進行加密,支持兩種方式(整體匹配、模糊匹配) - 使用示例:
User.java
- 使用
-
自定義 MyBatis 攔截器
- 實現 Interceptor 介面,重寫攔截器攔截 SQL 邏輯
- 攔截器執行在 TypeHandler 之前,註意避免衝突
自定義 MyBatis 攔截器 CryptoCondInterceptor.java
@Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); // 支持處理 SELECT、UPDATE、DELETE boolean canHandler = Stream.of(SqlCommandType.SELECT, SqlCommandType.UPDATE, SqlCommandType.DELETE) .anyMatch(item -> item.equals(mappedStatement.getSqlCommandType())); if (canHandler && !getIntercept()) { clearIntercept(); return invocation.proceed(); } clearIntercept(); // 判斷是否有參數需要處理 BoundSql boundSql = statementHandler.getBoundSql(); if (Objects.isNull(boundSql.getParameterObject())) { return invocation.proceed(); } // 獲取自定義註解,通過 MapperID 獲取到 Mapper 對應的實體類,獲取實體類所有註解欄位與註解對應 Map Map<String, CryptoCond> condMap = mapEntityFieldCond(mappedStatement.getId()); if (CollectionUtil.isNotEmpty(condMap)) { replaceHandle(mappedStatement.getConfiguration(), condMap, boundSql); } return invocation.proceed(); } // 替換數據處理 private void replaceHandle(Configuration configuration, Map<String, CryptoCond> condMap, BoundSql boundSql) { String sql = boundSql.getSql(); List<SqlCondOperation> operationList = SqlUtil.listSqlCondOperation(sql); if (CollectionUtil.isEmpty(operationList)) { return; } MetaObject paramMetaObject = configuration.newMetaObject(boundSql.getParameterObject()); List<ParameterMapping> mappings = boundSql.getParameterMappings(); int condParamStart = SqlUtil.getSqlCondParamStartIdx(sql); int mappingStartIdx = 0; for (SqlCondOperation operation : operationList) { String columnName = operation.getColumnName(); String condStr = operation.getOriginCond(); int condNum = SqlUtil.countPreparePlaceholder(condStr); CryptoCond ann = condMap.get(operation.getColumnName()); if (Objects.nonNull(ann)) { // 替換查詢條件參數中的列名 if (StrUtil.isNotBlank(ann.replacedColumn()) && condParamStart < operation.getOriginCondStartIdx()) { sql = sql.replace(condStr, condStr.replace(columnName, ann.replacedColumn())); } // 替換屬性值為加密值 if (condNum == 0) { // 存在非預編譯語句條件,直接替換 SQL 條件值 String propVal = String.valueOf(paramMetaObject.getValue(columnName)); String useVal = getCryptoUseVal(ann, propVal); sql = sql.replace(condStr, condStr.replace(propVal, useVal)); } else { // 預編譯語句條件通過替換條件值處理 for (int i = 0; i < condNum; i++) { String propName = mappings.get(mappingStartIdx + i).getProperty(); if (!propName.startsWith("et.")) { // 非實體類屬性進行值替換,實體類屬性通過 TypeHandler 處理 String propVal = String.valueOf(paramMetaObject.getValue(propName)); paramMetaObject.setValue(propName, getCryptoUseVal(ann, propVal)); } } } } mappingStartIdx += condNum; } ReflectUtil.setFieldValue(boundSql, "sql", sql); }
3 :相關彙總
測試
- 執行 /resources/db/schema.sql 創建資料庫( java-practice-demos )、示例表( crypto-user )
- 用戶表 Service 層
IUserService.java
- 用戶表 Service 層 測試
UserServiceTest.java
使用問題
-
TypeHandler 不起效
- 在 xml 中自定義 ResultMap ,示例:
UserMapper.xml
點擊查看代碼 UserMapper.xml
<!-- 使用自定義SQL時,對於加密處理需要使用ResultMap作為返回對象,否則對解析成實際數據會存在問題 --> <resultMap id="BaseResultMap" type="cn.eastx.practice.demo.crypto.pojo.po.User"> <result column="id" property="id" /> <result column="name" property="name" /> <result column="password" property="password" /> <result column="salt" property="salt" /> <result column="phone" property="phone" typeHandler="cn.eastx.practice.demo.crypto.config.mp.OverallCryptoTypeHandler" /> <result column="email" property="email" typeHandler="cn.eastx.practice.demo.crypto.config.mp.FuzzyCryptoTypeHandler" /> <result column="create_time" property="createTime" /> <result column="update_time" property="updateTime" /> </resultMap>
- 在實體類上增加
@TableName(autoResultMap = true)
,自動構建 ResultMap - https://gitee.com/baomidou/mybatis-plus/issues/I103ZO
- 在 xml 中自定義 ResultMap ,示例:
-
加密後解密數據亂碼
- 可能是密鑰存在問題,建議重新生成
-
CryptoCondInterceptor 不起效
- 設置 CryptoCondInterceptor.setIntercept(false)
- 在實體類上相應欄位設置
@CryptoCond
,示例:User.java
用戶實體類 User.java
@Data @TableName(value = "crypto_user", autoResultMap = true) public class User { /** * 用戶表主鍵ID */ private Long id; /** * 用戶名 */ private String name; /** * 加密後的密碼,MD5加鹽 */ private String password; /** * 加密密碼使用的鹽 */ private String salt; /** * 手機號碼,整體加密 */ @TableField(typeHandler = OverallCryptoTypeHandler.class) @CryptoCond(encryption = CryptoCond.EncryptionEnum.DEFAULT_OVERALL) private String phone; /** * 郵箱,模糊加密 */ @TableField(typeHandler = FuzzyCryptoTypeHandler.class) @CryptoCond(encryption = CryptoCond.EncryptionEnum.DEFAULT_FUZZY) private String email; /** * 創建時間 */ @TableField(fill = INSERT) private LocalDateTime createTime; /** * 更新時間 */ @TableField(fill = INSERT_UPDATE) private LocalDateTime updateTime; }
- Mapper 實現 BaseMapper 並指定實體類,示例:
public interface UserMapper extends BaseMapper<User>
- 將攔截器加入 Spring IOC 管理
-
MySQL 異常
- 索引長度
MySQL 預設索引長度最大長度是767bytes
Specified key was too long; max key length is 3072
bytes
- 索引長度
參考
其他
demo 地址:https://github.com/EastX/java-practice-demos/tree/main/demo-crypto
推薦閱讀:
作者:EastX本文來自博客園,轉載請註明原文鏈接:https://www.cnblogs.com/cnx01/p/16887088.html