敏感数据识别:PII、PHI、金融信息等分类与发现方法

说实话,做数据脱敏这么多年,我碰到的第一个坑就是——连要脱什么都不知道。你以为"敏感数据"很好认?身份证号、手机号、银行卡号,这些是个人识别信息(PII);病历、诊断记录、医保号,这些是受保护健康信息(PHI);还有信用卡交易、账户余额、流水记录,典型的金融信息。

但实际项目中,数据往往混在一起。客户表里既有姓名地址,又有诊断代码,还夹着信用卡号。如果不先把敏感数据分类挖出来,后面的脱敏就是瞎忙。我记得有一次帮某银行做数据安全审计,他们信誓旦旦说"所有敏感字段都已经标记",结果我一扫日志,发现备注字段里躺着明文卡号……嗯,那时候我就意识到,识别这块儿,不能靠拍脑袋。

3.1 三种敏感数据的基本定义

先捋清楚概念。我把它们分成三类,方便后面做发现规则。

类型 全称 典型字段 监管来源
PII 个人识别信息 姓名、身份证号、手机号、邮箱、家庭住址、生物特征 《个人信息保护法》、GDPR
PHI 受保护健康信息 病历、诊断代码(ICD-10)、医保号、检验结果、处方 HIPAA、《健康医疗大数据安全管理办法》
金融信息 金融相关敏感信息 银行卡号、交易金额、账户余额、CVV、PIN码 PCI DSS、《金融数据安全分级指南》
我的习惯:每接到一个新数据源,我都会先拉一份 "字段清单 + 样例值" 的 CSV。对着样例扫一眼,80% 的敏感字段就能看出来。剩下的 20% 才需要跑正则或机器学习。

3.2 发现方法:从手动到自动化

发现敏感数据的方法,我按成熟度分了四个层次。你可以在项目里根据情况选一个或组合用。

  1. 字段名关键字匹配 —— 最粗暴,但最快。比如字段名包含 "id_card"、"phone"、"diagnosis"、"card_no"。
  2. 正则表达式扫描 —— 针对固定格式的数据。例如身份证号 18 位、手机号 11 位、银行卡号 16-19 位。
  3. 数据抽样 + 统计特征 —— 计算字段的熵值、重复率、模式比例。例如身份证号前 6 位是地址码,会呈现地域分布特征。
  4. 机器学习分类模型 —— 对非结构化文本(病历描述、聊天记录)做 NER,提取人名、疾病名、金额。

我个人觉得,前两个方法覆盖了 90% 的场景。后两个在数据量特别大或者字段没有明确格式的时候才用。比如我曾经帮一家医疗信息化公司做数据资产盘点,他们的 "患者备注" 字段里既有用药信息,又有患者吐槽医生的内容。用正则根本搞不定,只能上 NER 模型。

我曾经踩过的坑: 正则表达式扫描身份证号时,如果不加校验位和前六位合法性验证,会把随机 18 位数字也当成身份证号。结果误报率高达 40%。后来我改成先校验格式,再校验校验码,误报率降到 3% 以下。所以提醒你,正则只是第一步,一定要加合法性校验。

3.3 分类体系与标签设计

数据识别出来之后,要给它们打标签。我的做法是设计一个 两级分类 + 敏感等级 的标签结构。这样脱敏策略就能直接引用标签,不用每次都重新识别。

第一级类别 第二级类别 敏感等级 脱敏建议
PII 直接标识符(身份证、手机号、邮箱) L4(极高) 不可逆脱敏(替换/加密)
PII 准标识符(性别、年龄、邮编) L3(高) 可逆脱敏或泛化
PHI 诊断代码、检验结果 L4(极高) 不可逆脱敏
PHI 就诊日期、科室 L2(中) 日期偏移或泛化
金融信息 银行卡号、CVV、交易金额 L4(极高) 不可逆脱敏
金融信息 交易流水号、渠道 ID L2(中) 可逆脱敏或哈希
关键原则: 直接标识符(能唯一确定一个人的字段)必须走不可逆脱敏;准标识符(多个一起才能推断人)可以走可逆脱敏或泛化。金融信息中的交易金额虽然是敏感字段,但一般不作为直接标识,所以可以根据场景选择不可逆或可逆。

3.4 发现流程与工具

这里我画了一张图,把整个发现流程串起来。你跟着走一遍,基本不会漏。

敏感数据识别流程图 敏感数据识别流程 数据源接入 字段名 + 正则表达式扫描 匹配? 是 否 数据抽样 + 统计特征分析 打标签 + 等级评定 输出敏感数据资产清单 说明:匹配成功则直接打标签,匹配失败则进入抽样统计层,两者最终合并输出
实际落地建议:我在中等规模(几百张表)的项目中,用字段名+正则扫描跑一遍大概需要 2-3 小时,然后人工抽检 10% 的匹配结果,再补漏。整个过程两天内搞定。千万不要一开始就上机器学习,成本太高。

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 而不是金额。你得多看几个样例才能判断。

我曾经遇到过:有一个字段名是 "remark",样例里有 "患者张三,诊断高血压,卡号 6217********"。字段名匹配完全没命中,正则也只认出卡号,没认出人名和疾病名。后来我加上 NER 模型做补充扫描,才把所有敏感信息找出来。所以记住:字段名+正则覆盖不了所有情况,抽样统计和 NLP 是必要的兜底。

3.6 识别后的管理

识别出来的敏感数据清单,要纳入元数据管理平台。我习惯把标签写到数据字典里,并标记脱敏状态。每次数据变更(新增表、新增字段)都需要重新扫描一遍。

  • 自动化扫描任务: 每周跑一次全表扫描,增量只扫变更的表。
  • 人工抽检: 每个月随机抽 5% 的字段,核对标签准确性。
  • 异常告警: 当发现新敏感字段或现有标签变化时,推送通知给数据安全责任人。

说白了,敏感数据识别不是一次性工作。数据在变,业务在变,监管要求也在变。我建议你在项目初期就建好扫描框架,后面就是持续维护的事了。