正则表达式在敏感数据识别中的应用
正则表达式,说穿了就是一把“文本手术刀”。
做DLP(数据防泄漏)的时候,你得先知道敏感数据长什么样,才能去识别它。正则就是最快、最直接的方式。我个人习惯先把常见的规则跑通,再去优化性能。不然一上来就写花哨的“回溯陷阱”,后面可有你受的。
这一节我们聚焦三个高频场景:身份证、手机号、信用卡号。写正则容易,但写得对、跑得快、不漏报、不误报,才是真功夫。
4.1 身份证号识别规则
中国身份证号,18位(偶尔还有老15位的,但新系统基本只处理18位)。格式大致是这样:
- 前6位:地址码(纯数字)
- 中间8位:出生日期(YYYYMMDD)
- 再3位:顺序码(纯数字,奇数男偶数女)
- 最后1位:校验码(数字或大写X)
最简单的写法可能就是 \d{17}[\dXx]。但小心,这一下子会匹配掉很多不该匹配的。我记得在项目中遇到过一个问题:一段英文文本里恰好有“12345678901234567X”这种东西,直接被误报成身份证。后来我们加了“边界限定”。
(?<!\d)\d{17}[\dXx](?!\d)。这样不会把长数字串的中间一段抠出来。
另外,出生日期也要校验一下合理性。02月29日这种,可以用更细的逻辑去拦。不过纯正则做日期校验很痛苦,我一般在匹配后加一段代码逻辑做二次校验,正则只负责“粗筛”。
// 粗筛正则
String ID_REGEX = "(?<!\\d)[1-9]\\d{5}(19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx](?!\\d)";
4.2 手机号识别规则
中国大陆手机号,目前是11位,第一位是1,第二位通常是3、5、7、8、9。写起来比较简单:
- 开头:
1[3-9] - 后面9位:
\d{9} - 整体:
(?<!\d)1[3-9]\d{9}(?!\d)
不过有个坑:有些文本会把手机号写成“138 1234 5678”带空格,或者“138-1234-5678”带短横。
只匹配纯数字会漏掉带格式的。怎么办?我建议写两条规则:一条处理纯数字,一条处理格式化数字。或者用正则先去除所有非数字字符再匹配,但这样会丢失原始位置,不适合实时DLP。更好的做法是在原文本中直接匹配带可选格式的版本:
// 允许带空格或短横但不是必须
String PHONE_REGEX = "(?<!\\d)1[3-9]\\d{1}[ -]?\\d{4}[ -]?\\d{4}(?!\\d)";
小心!这个正则会匹配“13-4567-8901”这样的奇葩格式。实际上用户输入格式千奇百怪,我们得在工程上做权衡。有时候我会定义两条规则:
| 规则类型 | 正则表达式 | 典型匹配 |
|---|---|---|
| 严格 | (?<!\d)1[3-9]\d{9}(?!\d) | 13812345678 |
| 宽松 | (?<!\d)1[3-9]\d{1}[- ]?\d{4}[- ]?\d{4}(?!\d) | 138-1234-5678 |
4.3 信用卡号识别规则
信用卡号(PAN)一般是16位,前6位是发卡行标识(BIN)。常见卡组织:Visa(以4开头)、Mastercard(51-55开头)、银联(62开头)。
写正则时,不能只写 \d{16},因为16位纯数字到处都有。需要加上Luhn算法校验才靠谱。不过实时DLP场景下,Luhn校验会带来一定性能开销。我个人的做法是:先正则快速扫出16位数字串,再异步跑Luhn。
// 信用卡号粗筛(Visa/Mastercard/银联)
String CARD_REGEX = "(?<!\\d)(4[0-9]{15}|5[1-5][0-9]{14}|62[0-9]{14})(?!\\d)";
4.4 正则性能优化要点
正则写不对是小事,写慢了DLP引擎直接卡死。尤其是在大文件或者网络流量截获场景下。
我建议关注这几个点:
- 避免回溯爆炸:不要用
.*或.+过多嵌套,尽量用具体字符类。 - 使用锚点:
^和$在单行模式中能大幅剪枝。 - 优先编译正则:Java里用
Pattern.compile(regex)缓存,别每次新建。 - 分而治之:不要写一个超级巨大的正则包含所有规则。拆成多个小正则,用短路逻辑。
- 超时保护:生产环境必须给正则匹配加超时,比如用
re.finditer设置timeout(Python3.8+)。
(?<!\d)(?=...) 比 (?:(?<!\d)...) 在大多数引擎里效率更高。你可以测一下看看。
本章知识结构图
最佳实践总结
写正则不难,难在“不漏报、不误报、不卡死”。我整理了几个关键原则,你直接拿去用:
| 原则 | 说明 | 案例 |
|---|---|---|
| 加边界 | 避免匹配长数字串的子串 | (?<!\d)...(?!\d) |
| 白名单 | 排除测试卡号、无效区号 | 4000000000000000 |
| 分隔符容忍 | 允许空格、短横,但要二次清理 | 手机号分段匹配 |
| 异步校验 | 正则快速过,算法慢步校验 | Luhn校验放队列 |
| 超时兜底 | 防止恶意输入导致CPU爆满 | 每个正则匹配设500ms上限 |