敏感数据识别:PII、PHI、金融信息等分类与发现方法
说实话,做数据脱敏这么多年,我碰到的第一个坑就是——连要脱什么都不知道。你以为"敏感数据"很好认?身份证号、手机号、银行卡号,这些是个人识别信息(PII);病历、诊断记录、医保号,这些是受保护健康信息(PHI);还有信用卡交易、账户余额、流水记录,典型的金融信息。
但实际项目中,数据往往混在一起。客户表里既有姓名地址,又有诊断代码,还夹着信用卡号。如果不先把敏感数据分类挖出来,后面的脱敏就是瞎忙。我记得有一次帮某银行做数据安全审计,他们信誓旦旦说"所有敏感字段都已经标记",结果我一扫日志,发现备注字段里躺着明文卡号……嗯,那时候我就意识到,识别这块儿,不能靠拍脑袋。
3.1 三种敏感数据的基本定义
先捋清楚概念。我把它们分成三类,方便后面做发现规则。
| 类型 | 全称 | 典型字段 | 监管来源 |
|---|---|---|---|
| PII | 个人识别信息 | 姓名、身份证号、手机号、邮箱、家庭住址、生物特征 | 《个人信息保护法》、GDPR |
| PHI | 受保护健康信息 | 病历、诊断代码(ICD-10)、医保号、检验结果、处方 | HIPAA、《健康医疗大数据安全管理办法》 |
| 金融信息 | 金融相关敏感信息 | 银行卡号、交易金额、账户余额、CVV、PIN码 | PCI DSS、《金融数据安全分级指南》 |
3.2 发现方法:从手动到自动化
发现敏感数据的方法,我按成熟度分了四个层次。你可以在项目里根据情况选一个或组合用。
- 字段名关键字匹配 —— 最粗暴,但最快。比如字段名包含 "id_card"、"phone"、"diagnosis"、"card_no"。
- 正则表达式扫描 —— 针对固定格式的数据。例如身份证号 18 位、手机号 11 位、银行卡号 16-19 位。
- 数据抽样 + 统计特征 —— 计算字段的熵值、重复率、模式比例。例如身份证号前 6 位是地址码,会呈现地域分布特征。
- 机器学习分类模型 —— 对非结构化文本(病历描述、聊天记录)做 NER,提取人名、疾病名、金额。
我个人觉得,前两个方法覆盖了 90% 的场景。后两个在数据量特别大或者字段没有明确格式的时候才用。比如我曾经帮一家医疗信息化公司做数据资产盘点,他们的 "患者备注" 字段里既有用药信息,又有患者吐槽医生的内容。用正则根本搞不定,只能上 NER 模型。
3.3 分类体系与标签设计
数据识别出来之后,要给它们打标签。我的做法是设计一个 两级分类 + 敏感等级 的标签结构。这样脱敏策略就能直接引用标签,不用每次都重新识别。
| 第一级类别 | 第二级类别 | 敏感等级 | 脱敏建议 |
|---|---|---|---|
| PII | 直接标识符(身份证、手机号、邮箱) | L4(极高) | 不可逆脱敏(替换/加密) |
| PII | 准标识符(性别、年龄、邮编) | L3(高) | 可逆脱敏或泛化 |
| PHI | 诊断代码、检验结果 | L4(极高) | 不可逆脱敏 |
| PHI | 就诊日期、科室 | L2(中) | 日期偏移或泛化 |
| 金融信息 | 银行卡号、CVV、交易金额 | L4(极高) | 不可逆脱敏 |
| 金融信息 | 交易流水号、渠道 ID | L2(中) | 可逆脱敏或哈希 |
3.4 发现流程与工具
这里我画了一张图,把整个发现流程串起来。你跟着走一遍,基本不会漏。
3.5 代码示例:Python 敏感字段扫描器
下面是我常用的一个 Python 扫描脚本。它读取表结构信息和样例数据,输出每个字段的敏感标签。
import re
import pandas as pd
# 敏感字段正则字典
SENSITIVE_PATTERNS = {
"身份证号": re.compile(r'^\d{6}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$'),
"手机号": re.compile(r'^1[3-9]\d{9}$'),
"银行卡号": re.compile(r'^\d{16,19}$'),
"邮箱": re.compile(r'^[\w\.-]+@[\w\.-]+\.\w+$'),
"医保号": re.compile(r'^\d{6}[A-Z]\d{8}$')
}
def scan_field(field_name, sample_values):
"""
扫描单个字段,返回敏感类别和等级
"""
# 字段名匹配
name_lower = field_name.lower()
name_pattern_map = {
'id_card': ("身份证号", "L4"),
'phone': ("手机号", "L4"),
'mobile': ("手机号", "L4"),
'email': ("邮箱", "L3"),
'card_no': ("银行卡号", "L4"),
'medical_no': ("医保号", "L4"),
'diagnosis': ("诊断代码", "L4"),
}
for keyword, (category, level) in name_pattern_map.items():
if keyword in name_lower:
return category, level
# 正则匹配样例
for sample in sample_values:
if pd.isna(sample):
continue
for cat, pattern in SENSITIVE_PATTERNS.items():
if pattern.match(str(sample)):
# 身份证号 L4,邮箱 L3,其他 L4
level = "L3" if cat == "邮箱" else "L4"
return cat, level
return None, None
# 使用示例
df_structure = pd.DataFrame({
"field_name": ["id_card", "phone", "address", "amount"],
"sample": ["110101199001011234", "13800138000", "北京市朝阳区", "12800.50"]
})
for _, row in df_structure.iterrows():
cat, level = scan_field(row["field_name"], [row["sample"]])
print(f"{row['field_name']} -> 类别: {cat}, 等级: {level}")
# 输出:
# id_card -> 类别: 身份证号, 等级: L4
# phone -> 类别: 手机号, 等级: L4
# address -> 类别: None, 等级: None
# amount -> 类别: None, 等级: None
注意,这个脚本只做初步筛查。对于"address"和"amount"这种字段名不敏感但内容可能敏感的,需要放到数据抽样统计层去分析。比如"amount"字段如果大部分值都小于 10000,而且没有小数点,那可能是整数 ID 而不是金额。你得多看几个样例才能判断。
3.6 识别后的管理
识别出来的敏感数据清单,要纳入元数据管理平台。我习惯把标签写到数据字典里,并标记脱敏状态。每次数据变更(新增表、新增字段)都需要重新扫描一遍。
- 自动化扫描任务: 每周跑一次全表扫描,增量只扫变更的表。
- 人工抽检: 每个月随机抽 5% 的字段,核对标签准确性。
- 异常告警: 当发现新敏感字段或现有标签变化时,推送通知给数据安全责任人。
说白了,敏感数据识别不是一次性工作。数据在变,业务在变,监管要求也在变。我建议你在项目初期就建好扫描框架,后面就是持续维护的事了。