2. 基于敏感数据识别规则的个人信息字段扫描

基于敏感数据识别规则的个人信息字段扫描 学习卡片

扫描任务的对象与判定链路

本案例在测试库 demo_hr 中完成一次字段级敏感数据扫描。核心对象是三类个人信息字段:姓名、手机号、身份证号。扫描器对每个字段先做列名匹配(字典命中),再做值域正则匹配(内容命中),两者按“或”关系合并为候选敏感字段,最后经阈值过滤输出分布报告。误报主要来自列名相似但内容非敏感(如 full_name 存的是产品名),漏报主要来自正则过严或字典未覆盖别名(如 mobile_no、id_card)。

测试库 demo_hr employees 表 customers 表 敏感识别引擎 列名字典匹配 值域正则匹配 命中率阈值过滤 识别规则集(可调参数) 姓名:字典 [name, full_name, emp_name] 手机号:正则 ^1[3-9]\d{9}$ 身份证:正则 ^\d{17}[\dXx]$ 阈值:命中率 ≥ 60% 判定为敏感 候选敏感字段 按表/字段/类型输出 含命中率与命中方式 误报(False Positive) full_name 存产品名 phone 存客服分机号 → 收紧正则 / 加白名单 漏报(False Negative) mobile_no 未入字典 id_card 正则过严 → 扩充字典 / 放宽正则 正确命中(True Positive) emp_name → 姓名 mobile → 手机号 id_number → 身份证号 调整正则 / 字典阈值 → 重扫 敏感字段分布报告 表名 | 字段名 | 敏感类型 | 命中率 误报数 | 漏报数 | 调整建议

识别规则配置与扫描执行

以下配置在扫描器规则文件中完成。字典用于列名匹配,正则用于值域匹配,阈值控制判定灵敏度。示例参数为教学假设值,实际需根据数据分布调整。

# sensitive_rules.yaml
rules:
  - name: person_name
    type: 姓名
    column_dict: [name, full_name, emp_name, real_name, user_name]
    value_regex: "^[\u4e00-\u9fa5]{2,4}$"
    threshold: 0.6

  - name: mobile_phone
    type: 手机号
    column_dict: [mobile, phone, mobile_no, tel, contact_phone]
    value_regex: "^1[3-9]\\d{9}$"
    threshold: 0.7

  - name: id_card
    type: 身份证号
    column_dict: [id_card, id_number, identity_no, cert_no]
    value_regex: "^\\d{17}[\\dXx]$"
    threshold: 0.8

扫描执行命令(以 Python 脚本为例,遍历 demo_hr 库所有表的所有列):

import re, yaml, sqlite3

def load_rules(path="sensitive_rules.yaml"):
    with open(path, encoding="utf-8") as f:
        return yaml.safe_load(f)["rules"]

def scan_column(values, rule):
    """返回 (是否命中, 命中率, 命中方式)"""
    total = len(values)
    if total == 0:
        return False, 0.0, "empty"
    regex = re.compile(rule["value_regex"])
    hits = sum(1 for v in values if v and regex.match(str(v)))
    rate = hits / total
    return rate >= rule["threshold"], round(rate, 3), "regex"

def scan_database(db_path, rules):
    conn = sqlite3.connect(db_path)
    cur = conn.cursor()
    cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
    tables = [r[0] for r in cur.fetchall()]
    report = []
    for table in tables:
        cur.execute(f"PRAGMA table_info({table})")
        cols = [c[1] for c in cur.fetchall()]
        for col in cols:
            cur.execute(f"SELECT {col} FROM {table} LIMIT 500")
            values = [r[0] for r in cur.fetchall()]
            for rule in rules:
                col_hit = col.lower() in [d.lower() for d in rule["column_dict"]]
                val_hit, rate, method = scan_column(values, rule)
                if col_hit or val_hit:
                    report.append({
                        "table": table, "column": col,
                        "type": rule["type"],
                        "hit_rate": rate,
                        "method": "column+value" if (col_hit and val_hit) else ("column" if col_hit else "value")
                    })
    conn.close()
    return report

if __name__ == "__main__":
    rules = load_rules()
    results = scan_database("demo_hr.db", rules)
    for r in results:
        print(f"{r['table']}.{r['column']} | {r['type']} | 命中率={r['hit_rate']} | 方式={r['method']}")

误报与漏报的观察与调整

首次扫描后,重点检查两类异常:

  • 误报:full_name 列被姓名规则命中,但实际存的是产品名称。原因是列名字典包含 full_name,且值域正则 ^[\u4e00-\u9fa5]{2,4}$ 对中文产品名也匹配。调整方式:将 full_name 从姓名字典移除,或增加业务白名单过滤。
  • 漏报:mobile_no 列未被手机号规则命中。原因是列名字典缺少 mobile_no,且该列部分值含空格或 +86 前缀,正则未覆盖。调整方式:字典补充 mobile_no,正则改为 ^(\+86)?1[3-9]\d{9}$ 并先做去空格清洗。
调整后重新执行扫描,对比两次报告的字段数量与命中率变化。建议将误报字段加入 exclude_columns 白名单,将漏报字段的别名补充到 column_dict,并记录每次调整的规则版本,便于审计回溯。

敏感字段分布报告示例

表名字段名敏感类型命中率命中方式判定
employeesemp_name姓名0.95column+value正确命中
employeesmobile手机号0.98column+value正确命中
employeesid_number身份证号0.92column+value正确命中
customersfull_name姓名0.72column误报(产品名)
customersmobile_no手机号0.00未命中漏报(字典缺失)
customersid_card身份证号0.45value漏报(正则过严)
阈值调整需权衡:降低阈值可减少漏报但增加误报,提高阈值则相反。建议先以 0.6~0.8 为初始区间,根据业务字段的实际数据分布做针对性微调,并保留调整前后的报告对比作为审计证据。