AI 应用的数据脱敏——什么数据不能发给大模型
AI 应用的数据脱敏——什么数据不能发给大模型
适读人群:做企业 AI 项目的开发者、技术负责人 | 阅读时长:约 14 分钟 | 核心价值:搞清楚数据安全边界,掌握工程脱敏方案
去年做一个法律科技公司的 AI 项目,甲方需求很简单:用 AI 帮助律师快速整理案件材料、提炼关键事实。
需求谈完,我问了一个问题:这些案件材料里有什么?
对方说:有当事人身份证号、联系方式、银行卡信息、公司内部财务数据、涉及商业纠纷的保密协议……
我直接说:这些数据现在的方案不能直接发给云端 API。
对方沉默了一下,说:我们其实已经在测试 ChatGPT 了,效果挺好的。
我问:你们知道数据发出去之后 OpenAI 怎么处理吗?
又沉默了。
这个场景在我做企业 AI 项目里遇到过不止三次。很多公司对"把数据发给大模型"这件事缺乏基本的风险意识,或者说,知道有风险,但没有人站出来说"我们需要一个方案"。
这篇文章就是为这种场景写的。
哪些数据绝对不能发给云端 API
先给一个明确的分类,不搞模糊表述。
第一类:个人身份信息(PII)
- 身份证号、护照号、社保号
- 手机号、邮箱地址(如果和真实身份绑定)
- 家庭住址、出生日期
- 银行卡号、信用卡信息
- 人脸照片、指纹等生物特征
这类数据受《个人信息保护法》直接约束。一旦泄露,公司面临的不只是声誉风险,是法律风险。
第二类:企业商业秘密
- 未发布的产品设计和技术方案
- 客户名单和采购数据
- 财务数据(尤其是上市公司的非公开信息)
- 商务合同的具体条款和价格
- 内部战略规划文件
这类数据是企业的核心资产,某些情况下泄露构成不正当竞争或违反保密协议。
第三类:受监管行业的特殊数据
- 医疗数据:病历、诊断记录、用药信息
- 金融数据:交易记录、账户明细、风险评级
- 政府数据:涉密文件、公民信息
这些领域有专项监管,监管要求高于一般标准。
第四类:可组合识别的数据
这类最容易被忽视。单独看不敏感,但组合起来能识别到具体个人:
- 公司名 + 部门 + 职位 + 入职年份 → 基本上能锁定到人
- 城市 + 年龄 + 职业 + 某个具体事件 → 可以定位到个人
云端 API 的数据风险到底是什么
很多人知道"有风险",但说不清风险在哪里。我来拆解一下。
训练风险:主流云端 LLM 服务商(OpenAI、Anthropic、Google 等)的企业版 API 默认声称不用 API 输入数据训练模型,但免费版和消费端产品没有这个保证。如果你用的是"企业版 API",这个风险理论上可控。如果你的研发人员在用免费版 ChatGPT 测试真实数据,风险是真实存在的。
访问风险:数据在传输和处理过程中经过了第三方服务商的基础设施。即使服务商安全做得好,数据也已经"离开"了你的边界控制。
司法风险:数据存储在境外服务器,万一涉及法律纠纷,数据的司法管辖权就复杂了。《数据安全法》对跨境数据传输有明确要求。
合同风险:很多企业和客户签了保密协议,明确规定数据不能传给第三方。把数据发给 OpenAI 的 API 算不算违反保密协议?大概率算。
脱敏的工程实现方案
知道哪些数据不能发,下一步是怎么既保护数据、又能用 AI 处理。
方案一:正则 + 规则脱敏(适合已知格式的 PII)
最简单直接的方式,针对格式固定的敏感信息:
import re
import hashlib
from typing import Tuple
class PIISanitizer:
"""
PII 数据脱敏处理器
"""
def __init__(self):
self.patterns = {
# 中国身份证号(18位)
'id_card': r'\b\d{17}[\dX]\b',
# 手机号
'phone': r'\b1[3-9]\d{9}\b',
# 银行卡号(16-19位数字)
'bank_card': r'\b\d{16,19}\b',
# 邮箱
'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
# IPv4(可能关联个人)
'ipv4': r'\b(?:\d{1,3}\.){3}\d{1,3}\b',
}
# 用于可逆脱敏的映射表(如果需要还原)
self._mapping = {}
def mask(self, text: str, reversible: bool = False) -> Tuple[str, dict]:
"""
对文本进行脱敏处理
reversible=True 时保留映射关系,可以后续还原
返回 (脱敏后文本, 脱敏映射表)
"""
result = text
mapping = {}
for pii_type, pattern in self.patterns.items():
matches = re.findall(pattern, result)
for match in set(matches): # 去重,同一个值用同一个占位符
if reversible:
# 生成稳定的占位符(同一输入产生同一占位符)
placeholder = self._get_placeholder(pii_type, match)
mapping[placeholder] = match
else:
placeholder = self._mask_value(pii_type, match)
result = result.replace(match, placeholder)
return result, mapping
def restore(self, text: str, mapping: dict) -> str:
"""还原脱敏内容(用于展示时替换回真实值)"""
result = text
for placeholder, original in mapping.items():
result = result.replace(placeholder, original)
return result
def _mask_value(self, pii_type: str, value: str) -> str:
"""不可逆脱敏:部分保留,部分用*替换"""
if pii_type == 'id_card':
return value[:6] + '********' + value[-4:]
elif pii_type == 'phone':
return value[:3] + '****' + value[-4:]
elif pii_type == 'bank_card':
return value[:4] + '****' + value[-4:]
elif pii_type == 'email':
parts = value.split('@')
masked_user = parts[0][0] + '***' if len(parts[0]) > 1 else '***'
return f"{masked_user}@{parts[1]}"
elif pii_type == 'ipv4':
parts = value.split('.')
return f"{parts[0]}.{parts[1]}.*.*"
return '***'
def _get_placeholder(self, pii_type: str, value: str) -> str:
"""生成稳定的占位符"""
hash_val = hashlib.md5(value.encode()).hexdigest()[:8]
return f"[{pii_type.upper()}_{hash_val}]"
# 使用示例
sanitizer = PIISanitizer()
original_text = (
"客户张三,身份证号 310110199001011234,手机 13812345678,\n"
"银行卡 6222021234567890,邮箱 zhangsan@company.com。"
)
masked_text, mapping = sanitizer.mask(original_text, reversible=True)
print("脱敏后:", masked_text)
# 脱敏后:客户张三,身份证号 310110********1234,手机 138****5678,
# 银行卡 6222****7890,邮箱 z***@company.com。
# 发给 LLM 处理...
llm_result = call_llm(masked_text)
# 如果需要,把结果里的占位符还原回真实值
final_result = sanitizer.restore(llm_result, mapping)方案二:命名实体识别(NER)脱敏
对于格式不固定的 PII,用 NLP 模型识别:
# 用 spacy 或 transformers 做命名实体识别
# 推荐用本地 NER 模型,不要用云端服务处理敏感文本
import spacy
# 加载中文 NER 模型(本地运行)
# pip install spacy && python -m spacy download zh_core_web_sm
nlp = spacy.load("zh_core_web_sm")
def ner_sanitize(text: str) -> Tuple[str, dict]:
"""
用 NER 识别人名、机构名等,进行脱敏
"""
doc = nlp(text)
result = text
mapping = {}
# 从后往前替换,避免偏移量错乱
entities = sorted(doc.ents, key=lambda e: e.start_char, reverse=True)
for ent in entities:
if ent.label_ in ['PERSON', 'ORG', 'GPE', 'LOC']:
placeholder = f"[{ent.label_}_{len(mapping):04d}]"
mapping[placeholder] = ent.text
result = result[:ent.start_char] + placeholder + result[ent.end_char:]
return result, mapping对于中文企业场景,推荐用 HanLP 或微软的 NER 模型,效果比 spacy 的中文模型好。
方案三:结构化数据的字段级脱敏
处理数据库查询结果或 JSON 数据时,按字段标注敏感级别:
from dataclasses import dataclass, field
from typing import Any, Optional
import json
@dataclass
class FieldPolicy:
"""字段脱敏策略"""
action: str # 'mask', 'remove', 'hash', 'keep'
mask_pattern: Optional[str] = None # 保留哪些位置
# 定义数据模型的脱敏策略
CUSTOMER_FIELD_POLICY = {
'customer_id': FieldPolicy(action='keep'),
'name': FieldPolicy(action='mask', mask_pattern='*_lastname'), # 保留姓,隐藏名
'id_card': FieldPolicy(action='remove'), # 直接移除,不发给模型
'phone': FieldPolicy(action='mask', mask_pattern='prefix_4_suffix_4'),
'email': FieldPolicy(action='mask', mask_pattern='email_partial'),
'address': FieldPolicy(action='mask', mask_pattern='city_only'), # 只保留城市
'balance': FieldPolicy(action='hash'), # 哈希处理
'created_at': FieldPolicy(action='keep'),
'status': FieldPolicy(action='keep'),
'notes': FieldPolicy(action='keep'), # 业务备注保留(假设不含敏感信息)
}
def sanitize_record(record: dict, policy: dict) -> dict:
"""
按策略对数据记录进行脱敏
"""
result = {}
for key, value in record.items():
if key not in policy:
# 未定义策略的字段默认移除(安全优先)
continue
field_policy = policy[key]
if field_policy.action == 'remove':
continue # 直接跳过,不包含在结果里
elif field_policy.action == 'keep':
result[key] = value
elif field_policy.action == 'hash':
result[key] = hashlib.sha256(str(value).encode()).hexdigest()[:16]
elif field_policy.action == 'mask':
result[key] = apply_mask(value, field_policy.mask_pattern)
return result
# 使用示例
raw_customer = {
'customer_id': 'C001234',
'name': '张三',
'id_card': '310110199001011234',
'phone': '13812345678',
'email': 'zhangsan@company.com',
'address': '上海市浦东新区某街道某号',
'balance': 50000.00,
'created_at': '2023-01-15',
'status': 'active',
'notes': '高价值客户,对产品A感兴趣',
}
safe_customer = sanitize_record(raw_customer, CUSTOMER_FIELD_POLICY)
print(json.dumps(safe_customer, ensure_ascii=False, indent=2))
# 输出里没有 id_card,其他敏感字段被脱敏什么场景下必须用本地模型
脱敏是一种保护方式,但有些场景,脱敏也不够,必须上本地模型。
法律文件的全文分析:合同、诉状、判决书的完整内容,脱敏后可能破坏语义,影响分析质量。这种场景建议用本地部署的模型(Qwen、Yi、ChatGLM 等)。
医疗病历处理:病历里的信息高度关联,脱敏可能让分析失去意义。医疗行业监管明确要求数据不出院内网络。
金融交易分析:具体的交易金额、时间、交易对手,是金融机构的核心资产,监管要求数据不离境。
未发布产品的设计文档:一旦发到云端,商业秘密的法律保护就很麻烦了。
政府或军工相关项目:没什么好说的,本地模型。
本地模型的部署成本在降低。一台配备 A100 的服务器运行 Qwen-14B 或 70B 量化版本,对于大多数企业 AI 场景完全够用。把这个成本和一次数据泄露的代价比,账很好算。
一个我见过的反面教材
做完这个法律项目没多久,我听说另一家公司的事:他们的程序员为了快速验证效果,把一批含有真实客户信息的合同直接扔给了 GPT-4 的 API。
效果验证成功了。
但数据出去了。
他们没签企业版协议,用的是普通 API key。这批数据有没有进训练集他们不知道,但他们知道的是,当他们意识到问题、想追溯的时候,已经无法追溯了。
这件事没有后续——至少没有公开的后续。但这家公司的技术团队内部开了很长时间的会。
如果你正在做企业 AI 项目,花一个下午把数据流梳理一遍:哪些数据会流经 LLM?流的是哪个 LLM?有没有协议保障?有没有脱敏处理?
这件事不难,就是没人主动去做。
