背景
业务数据库中存储着明文的手机号和身份证号姓名等敏感数据,这些敏感数据理论上需要密文显示,防止不必要的数据安全事件出现
需求
我们需要做的是,将数据库的明文敏感字段加密成密文字段,但是还需要满足原有的查询需求
这时候我们可以来大概计划下需要进行的工作
- 写库的时候需要对敏感数据进行加密
- 读库的时候需要对数据进行解密
- 历史数据需要进行加密
- 支持搜索,比如
=
、like
等
加密方式
我们常用的加密方式分为对称加密和非对称加密,我们都需要对数据进行加密和解密,所以我们需要选择对称加密的加密算法,常见的算法例如:
- AES
- DES
- SM4
实现思路
数据加密
如果我们使用的是mysql作为业务数据库,那么在mysql上是原生支持简单版本的AES和DES加解密函数DES_DECRYPT
、DES_ENCRYPT
、AES_DECRYPT
、AES_ENCRYPT
当我们只需要简易版本的AES和DES加密时,可以直接使用,在模糊查询时只需要在查询字段上加上解密函数即可
但是
把计算放在数据库明显不符合时下的互联网需求,这对数据库来说会带来巨大的计算压力,从而影响整个服务的响应。
我们可以将数据加密解密放到应用上处理,在写入数据的时候先进行加密,在读取数据之后进行解密
实现示例
要优雅的实现,采用是mybatis的实现方法为例,我们可以参照mybatis的类型处理器的实现
java
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
我们可以实现这个接口,并实现它的接口
java
public class EncryptTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
String encodeParameter = Encoder.encode(parameter);
ps.setString(i, encodeParameter);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String encodeCloumnResult = rs.getString(columnName);
String decodeCloumnStr = Decoder.decode(encodeCloumnResult);
return decodeCloumnStr;
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
String encodeCloumnResult = rs.getString(columnIndex);
String decodeCloumnStr = Decoder.decode(encodeCloumnResult);
return decodeCloumnStr;
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
String encodeCloumnResult = cs.getString(columnIndex);
String decodeCloumnStr = Decoder.decode(encodeCloumnResult);
return decodeCloumnStr;
}
}
然后mybatis-config.xml
中注册这个handler
xml
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR"
handler="com.xxx.custom.handler.EncryptTypeHandler"/>
</typeHandlers>
然后在写mapper.xml的时候根据要写入的列这样用
xml
<resultMap id="baseResultMap" type="com.xxxx.mybatis.model.XXXXDTO">
<result column="cloumn" property="cloumn" jdbcType="VARCHAR"
typeHandler="com.xxx.custom.handler.EncryptTypeHandler"></result>
</resultMap>
<select id="selet" resultMap="baseResultMap" >
select * from table where cloumn = #{cloumn,
jdbcType=VARCHAR,typeHandler=com.xxx.custom.handler.EncryptTypeHandler}
</select>
那未来读写数据库的数据加密问题解决了,历史数据呢?
数据清洗
可以根据停机不停机的方法来处理
- 如果允许
停机到数据处理完成的话
只需停机后,批量查询出来数据,再对数据进行加密后批量更新写入 - 如果只允许
短时间停机的话
,可以先备份数据库后对历史数据进行更新,再采用停机的方式,将未停机时间内的增量数据进行更新 - 如果
不允许停机的话
,可以在业务操作时同时对数据进行处理,但是需要对数据进行标识以便于typeHandler
对数据的处理,还需要一个异步任务同步对数据进行加密处理,这个方式较为平稳但是工作量会较大
数据查询
等值查询
等值查询我们只需要暴力
的将要查询的字段值进行加密后进行查询即可
模糊查询
加密算法是对原始数据进行各种换位和计算后得到一个新的字符串数据,加密后的字符串数据的顺序跟原始数据有关系但是并不是一定的顺序关系,所以模糊查询
想通过加密数据来模糊很难实现
这时候,只能曲线救国了,努力说明产品不实现这个需求或者改变这个需求
当然,肯定是只能稍微对需求进行修改
-
手机号 比如说我们对于搜索手机号类的数据,我们主要针对的数据是后4/8位,我们可以单独对这个数据进行加密后写入库中单独的列上,或者增加搜索中间表,这个表可以同时同步进搜索中间如es等,在查询时依旧采用等值搜索
-
身份证号 身份证号的构成是前3位代表省份4-6位代表市区,我们可以针对此进行特定的标记,参考车牌的编制方式,我们可以将
350、212
可以标记成闽、D1
,按照我们国年纪分布,我们可以采用2位数字+字母的方式组成后一个关键词,记录1850之后的数据
以此类似的方式,我们可以对数据进行抽奖标记成特殊的字段,然后进行等值查询,在此之前只需要在搜索时,对搜索框进行逻辑预标定。
不过你要是无法说服产品经理修改需求,非要进行模糊查询的话
只能对加密算法入手了,根据特定的算法,将某个字符加密后变成另外一个字符,加密后原定的顺序排列,搜索时将关键词按同等的加密方式加密后查询,当然这可能会出现问题 ,这样的加密方式比较容易被破解,加密可能会形同虚设