PII 脱敏是什么
个人信息脱敏的原则是发送与入库前最小化:能在本地剔除的字段不要发出去,能在入库前掩码的不要等到查询时才处理。做法上通常组合两类识别:正则处理手机号、身份证号、银行卡号、邮箱这类结构化字段,模型处理姓名、住址、职位关系这类自由文本中的非结构化信息。处理手段有掩码、哈希与占位符替换;确需保留原值做业务核对时改成映射表,真实值存受控存储,外部只看到标识。脱敏后仍需抽检漏检率。
基本信息
| 处理时机 | 发送前与入库前,越早越好 |
|---|---|
| 识别方式 | 正则处理结构化字段,模型补自由文本 |
| 处理手段 | 掩码、哈希、占位符替换 |
| 可逆需求 | 真实值存受控存储,用映射表关联 |
| 验收方式 | 抽检漏检率,重点看自由文本 |
| 覆盖范围 | 日志、工单、测试数据同样要脱敏 |
典型使用场景
- 调用外部模型前:把用户原文中的联系方式与证件号掩掉
- 知识库入库:合同、工单中的个人信息先处理再切分
- 日志与埋点:避免把原始请求体整段写进日志
- 测试数据准备:用脱敏后的样本替代生产数据
- 跨部门共享:对外提供数据时保留标识而不暴露原值
结构化字段粗筛与掩码
结构化字段粗筛与掩码
import re
PHONE = re.compile(r"(?<!\d)1[3-9]\d{9}(?!\d)")
IDCARD = re.compile(r"(?<!\d)\d{17}[\dXx](?!\d)")
BANK = re.compile(r"(?<!\d)\d{16,19}(?!\d)")
def mask_text(text: str) -> str:
text = PHONE.sub(lambda m: m.group()[:3] + "****" + m.group()[-4:], text)
text = IDCARD.sub("[身份证号]", text)
text = BANK.sub(lambda m: "****" + m.group()[-4:], text)
return text
# 正则只能粗筛,姓名与住址需要模型或词典补充识别
# 处理后再抽检:随机取样本人工核对,统计漏检类型常见字段与处理方式
| 字段 | 常见形态 | 处理建议 |
|---|---|---|
| 手机号 | 11 位数字 | 保留前三位与后四位,中间掩码 |
| 身份证号 | 18 位含校验位 | 整体替换为占位符或哈希 |
| 银行卡号 | 16 至 19 位数字 | 只保留后四位 |
| 邮箱 | 含 @ 的地址 | 保留域名或整体替换 |
| 详细住址 | 省市区加门牌号 | 粗化到城市级别 |
只对结构化字段做脱敏远远不够:自由文本里的姓名、职位关系、病历描述往往才是可识别信息,需要模型辅助识别并抽检。
脱敏要一次做在链路的入口:若在多个出口各自处理,容易出现某条路径漏处理,日志与第三方调用最先暴露问题。