解密后存储到内存「不推荐」

准备

  1. 将数据加载到内存中,将密文数据解密得到明文
  2. 创建映射Map,例如明文—数据 ID,存储到内存
  3. 数据新增、修改【密文数据更新】、删除时,需要同步更新内存中的数据

使用

  1. 后端代码,从内存中取出映射数据,根据 key 模糊查询,筛选出满足条件的 value 即数据 ID
  2. 根据数据 ID 过滤数据

缺点

  • 数据量大时,会出现 OOM 问题
  • 需要保证数据一致性,也就是数据库数据变化时需要同步更新内存中的数据
  • 多实例部署时,需要解决多实例服务内存中数据一致性问题。对于分布式系统来说,这会增加系统的复杂度,得不偿失
    • 一个后端服务实例数据变化时,除了更新当前服务内存中数据,还要通知其它服务实例更新内存数据
    • 通知会有延迟问题,还要保证通知必达,一定执行等

解密后存储到缓存「不推荐」

与上述方案类似,但将解密后的数据放入 Redis 中,虽然解决了多服务实例时数据一致性问题,但解密后的数据放入 Redis,这本身就带来了数据泄露的隐患

使用数据库DES、AES 加密解密函数「不推荐」

  • 在 MySQL 中使用DES_ENCRYPTDES_DECRYPT 函数,或者更安全的AES_DECRYPT()AES_DECRYPT 函数
  • 缺点:由于解密的开销,这种查询可能比普通的文本搜索慢得多,不推荐使用
1
2
3
4
5
SELECT * FROM encrypted_data 
WHERE AES_DECRYPT(UNHEX(encrypted_phone), 'your-secret-key') LIKE '%search-term%';

SELECT * FROM encrypted_data
WHERE DES_DECRYPT(encrypted_phone, 'your-secret-key') = '18638787898'

增加分段加密数据字段「大数量时不推荐」

准备

  1. 例如手机号字段 phone 存储的是加密后的手机号,再增加一个模糊查询字段 encrypt_fragment_phone
  2. 将手机号按照每连续 4 位一组拆分,得到 8 组数据
  3. 每组数据加密,然后使用逗号将加密后的数据连接起来,存储到字段encrypt_fragment_phone

使用

  • 前端输入 4 位手机号
1
2
3
4
5
6
7
8
# f4G71mp+tJbCIQGOmnISkA== 为前端输入的 4 位手机号加密后的数据
# 使用 like
select * from user
where encrypt_fragment_phone like '%f4G71mp+tJbCIQGOmnISkA==%' limit 0,20;

# 或者使用 find_in_set 函数
select * from user
where find_in_set(encrypt_fragment_phone, 'f4G71mp+tJbCIQGOmnISkA==') limit 0,20;

缺点

  • 模糊匹配不管是 like 还是 find_in_set 效率并不高,特别是数据量大时,查询速度比较慢,不推荐

增加扩展表,保存分段加密数据「推荐」

准备

  1. 增加扩展表encrypt_value_mapping,并为 ref_idencrypt_value 创建索引,提高联表查询效率

    1
    2
    3
    4
    5
    CREATE 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='分段加密映射表'
  2. 示例手机号,将手机号按照每连续 4 位一组拆分,得到 8 组数据

  3. 每组数据加密,对应扩展表中增加一条关联数据

使用

  • 前端输入 4 位手机号

  • sql 示例

    1
    2
    3
    4
    5
    # f4G71mp+tJbCIQGOmnISkA== 为前端输入的 4 位手机号加密后的数据
    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 = 'f4G71mp+tJbCIQGOmnISkA=='
    limit 0,20;
  • 后端获取到加密后的手机号后,解密得到明文。此时还需要做下干扰处理,例如中间替换为*号,示例 186******98