加密字段模糊搜索

导读

加密字段由于“雪崩效应”特性,无法直接进行 SQL 模糊查询,但在实际业务中往往有脱敏字段的模糊搜索需求。本文介绍主流的安全实现思路(分片哈希索引法),兼顾安全与业务需求,并提供代码与原理讲解实现“加密字段模糊搜索”。

背景

敏感信息(如手机号、身份证号、银行卡号、地址等)加密后无法直接使用 SQL 的 LIKE ‘%xxx%’ 进行模糊搜索,因为加密是“雪崩效应”的:明文微小变化 → 密文完全不同,且密文长度/格式通常与明文不一致。

但是如果业务需要,输入 6688 查出手机号包含 6688 的用户,我们需要采用特殊的方式解决。

1. 分片+索引

将明文按固定长度滚动切片 → 对每个片段用 HMAC(带密钥哈希) 生成指纹 → 存入索引表。查询时对关键词同样切片+HMAC,匹配索引。

存储时:

  1. 原文为13866889900,在内存中对原文进行滚动分片按,长度 3 滚动切片:138, 386, 866, 668, 688, 889, 899, 990, 900
  2. 对每个分片计算HMAC-SHA256
1
String hmac = HmacUtils.hmacSha256Hex(hmacKey, "668");
  1. 加密后原文存表
1
INSERT INTO users (id, encrypted_phone) VALUES (1001, 'AES(13866889900)');
  1. 加密后切片存表
1
2
3
4
5
6
7
INSERT INTO phone_idx (user_id, piece_hmac) VALUES 
(1001, HMAC('138')),
(1001, HMAC('386')),
(1001, HMAC('866')),
(1001, HMAC('668')), -- ← 关键片段
(1001, HMAC('688')), -- ← 关键片段
...;
  1. 查询时输入6688,切片len=3,为668,688,计算两个切片的HMAC-SHA256,通过子查询或者内联查询
1
2
3
4
5
6
7
8
9
SELECT u.encrypted_phone
FROM users u
INNER JOIN (
SELECT user_id
FROM phone_idx
WHERE piece_hmac IN ('h1_value', 'h2_value') -- 替换为实际 HMAC 值
GROUP BY user_id
HAVING COUNT(*) = 2
) idx ON u.id = idx.user_id;

或者

1
2
3
4
SELECT DISTINCT u.encrypted_phone
FROM users u
INNER JOIN phone_idx p1 ON u.id = p1.user_id AND p1.piece_hmac = 'h1_value'
INNER JOIN phone_idx p2 ON u.id = p2.user_id AND p2.piece_hmac = 'h2_value';

找到同时包含两个切片的结果。
6. 在返回结果时,为防止哈希碰撞或非连续匹配,在内存中返回

1
2
3
if (decryptedPhone.contains("6688")) {
return decryptedPhone; // 或脱敏后返回
}

特点:

  • 支持前中后模糊搜索
  • 需要切片长度>=3

2. 确定性加密+索引前缀

适用于 只支持“前缀搜索” 的场景(如查手机号 138%)

  1. 使用确定性加密算法(对同一原文,每次加密的结果相同)
    如AES-SIV等。
  2. 对明文的每个前缀单独加密存储,对于原文 13866889900
  3. 存储:
    enc("1"), enc("13"), enc("138"), …, enc("13866889900")
  4. 查询138%,计算enc(138)

特点

  • 仅限前缀搜索
  • 每一条原文都需要增加n条记录

错误做法

  1. 加载全量数据到内存解密再匹配,会导致OOM问题,数据量大时不可用。
  2. 数据库内置解密函数并使用Like:SELECT * FROM t WHERE AES_DECRYPT(col) LIKE '%6688%',会导致全表扫描,性能极差;密钥暴露给 DBA。

加密字段模糊搜索
https://yicizhang00.github.io/posts/编程语言/Java/工程实践/加密字段模糊搜索/
作者
Yici Zhang
发布于
2026年1月14日
许可协议