问题模拟
一串 11 位手机号:18812345678
密钥:123
使用 AES 加密后是91a0cbd90e7e18c3dc7346ba13b01cd9
我想通过模糊查询去查询手机号,然而进行加密后的手机号没办法通过 SQL 的 like 进行模糊查询。
解决方法
1. 一次加载到内存
把个人隐私数据一次性加载到内存中缓存起来,然后在内存中解密,再在代码中实现模糊搜索功能。这样实现起来比较简单,成本非常低。
但是个人隐私数据很多的话,内存可能不够,可能会出现 OOM 内存溢出问题。
还有就是数据一致性问题,如果用户修改了手机号,数据库更新成功了,需要同步更新到内存中缓存,否则用户查询的结果会跟实际结果不一样。
这个方案不仅可能会导致 OOM 内存溢出还可能提升系统复杂度。
2. 使用数据库函数
可以使用 MySQL 的 DES_ENCRYPT 函数加密,然后使用 DES_DECRYPT 函数解密。
SELECT
DES_DECRYPT ('91a0cbd90e7e18c3dc7346ba13b01cd9','123');
用户所有隐私信息加密解密都在 MySQL 层实现,这样不存在加解密存在不一致的情况。该方案中保存数据时,只对单个用户的数据进行操作,数据量比较小,性能还好。
但模糊查询数据时,每一次都需要通过 DES_DECRYPT 函数,把数据库中用户隐私信息字段的所有数据都解密,然后再通过解密后的数据做模糊查询。如果该字段的数据量非常大,这样每次查询的性能会非常差。
3. 分段保存
将一个完整的字符串拆分成多个小字符串。
手机号: 18812345678 按每 3 位为一组,进行拆分。
拆分后的字符串为 188、881、812、123、234、345、456、567、678 这九组数据。然后再进行加密。
然后建一张表
CREAT TABLE 'encrypt_value_mapping'(
'id' bigint NOT NULL COMMENT '系统编号',
'ref_id' bigint NOT NULL COMMENT '关联系统编号',
'encrypt_value' varchar(255) NOT NULL COMMENT '加密后的字符串'
)
ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4 _bin
COMMENT ='分段加密映射表';
这样用户在写入手机号时,同步把拆分之后的手机号分组数据也一起写入,可以保证在同一个事务当中,保证数据的一致性。
如果要模糊查询手机号,可以直接通过 encrypt_value_mapping
表 的 encrypt_value
模糊查询出用户标的 ref_id
,再通过 ref_id
查询用户信息。
select s2.id,s2.name,s2.phone
from encrypt_value_mapping s1
inner join 'user's2 on s1.ref_id=s2.id
where s1.encrypt_value = '加密后的结果'
limit 0,20;
这样就能通过模糊查询搜索出手机号。
这里 SQL 查询出来的手机号是加密的,在接口返回给前端之前需要在代码中进行解密处理。
为了安全性还可以将解密后的明文用 *
号增加一些干扰,防止手机号泄露。
4. 其他隐私字段模糊查询
将 encrypt_value_mapping
表扩展一下,增加一个 type
字段,该字段用于表示数据类型,比如 1. 手机号 2. 身份证 3. 银行卡号等等。
流程就是:将数据写入到 encrypt_value_mapping
表,然后根据不同 type 查询出不同的分组数据。
业务表中数据量少这套方案可以满足需求,但如果数据量很大的话,比如一个手机号就要保存 9 条数据,身份证或者银行卡号也需要保存很多条数据,这样会导致 encrypt_value_mapping
表的数据急剧增加,可能会导致这张表变得非常大,后果是影响查询性能。
这种情况就需要第五种方法增加模糊查询字段。
5. 增加模糊查询字段
数据量多的情况下,将所有用户隐私信息字段分组之后都集中在一张表中,我们可以增加模糊查询字段来提高性能。
以手机模糊查询为例,我们可以在用户表的手机号旁边增加一个 encrypt_phone
字段,然后我们在保存数据的时候将分组之后的数据拼接起来。
这里以手机号为例。
还是和第三种方法一样,将手机号按 3 位一组拆分。将他们分组,然后再分别加密。
CREATE TABLE user(
`id` int NOT NULL,
`code` varchar(20) NOT NULL,
`age` int NOT NULL DEFAULT '0',
`name` varchar(30) NOT NULL,
`height` int NOT NULL DEFAULT '0',
`address` varchar(30) DEFAULT NULL,
`phone` varchar(11) DEFAULT NULL,
`encrypt_phone` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_bin
COMMENT='用户表';
分组加密之后,用 ,
进行分割,就会得到九组加密的数据。以后可以直接通过 SQL 模糊查询字段 encrypt_phone
。
select id,name,phone
from user where encrypt_phone like '加密后的结果'
limit 0,20;
这里用的 like
用 ,
分割是为了防止直接字符串拼接,这是因为在极端的情况下,两个分组的数据原本不满足模糊查询条件,但拼接在一起会出现部分满足条件的情况发生。也可以根据实际情况将 ,
改为其他字符。
结语
没有最好的方案,只有最合适的方案。
想想你的文章写的特别好