如何自动清除日志中的护照数据和银行卡号
最近我们在调查一个生产环境中的事件,一个崩溃的服务将完整的 JSON 格式的用户请求体转储到了 ELK。其中包含了信用卡号码、家庭住址和电话号码。为了调试目的记录所有内容是常见做法,但当这些转储数据进入存储时,安全团队会提出一些非常棘手的问题。为每件小事编写正则表达式很快就会变得乏味。它们在复杂格式下会失效,遗漏姓名,还会将随机数字序列与真实标识符混淆。
解决这个问题有多种方法。最成熟的开源工具之一是 Presidio,由 data-privacy-stack 组织下的社区开发。
核心原理及必要性
Presidio 帮助在文本、电子表格甚至图像中查找和掩码个人身份信息(PII)。该项目最初由 Microsoft 工程师创建为开源库,后来迁移到一个独立组织,在 GitHub 上获得了超过 10,000 颗星。
其核心思想很简单:将隐私保护任务拆分为两个独立的步骤——分析和匿名化。
你不再需要编写一个庞大的脚本,而是获得两个独立的模块:
presidio-analyzer— 检查传入的文本,查找护照号码、电话号码、电子邮件地址或姓名等实体,并返回发现位置的坐标以及置信度分数。presidio-anonymizer— 接收分析器的标记,并对其应用转换规则:用占位符替换、加密、用星号掩码,或直接删除。
这种分离在以下场景中非常方便:只需要记录数据检测的事实而不修改文本本身,或者希望为不同字段类型灵活配置掩码方法。
实体检测的工作原理
如果你尝试过仅用正则表达式解析文本,你就会知道它们的主要缺点:正则表达式不理解上下文。它会找到一串 16 位数字,但无法判断这是卡号还是仓库中的产品 SKU。
Presidio 同时结合了多种方法:
- 正则表达式和校验和(例如银行卡号的 Luhn 算法或 IBAN 验证器)。
- 基于 spaCy 或 Hugging Face Transformers 的预训练 NLP 模型,用于命名实体识别(NER)——人物、地点、组织。
- 上下文词语分析。如果一个数字序列前面有 "phone"、"tel" 或 "call" 等词,检测器的置信度分数会提高。
- 词典和停用词列表。
Python 示例
让我们看看基本的 Python 流水线是什么样的。首先,安装软件包:
pip install presidio-analyzer presidio-anonymizer
python -m spacy download en_core_web_lg
首先,运行分析器在字符串中查找易受攻击的位置:
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
text = "Привет, меня зовут John Doe. Мой рабочий email [email protected], а телефон +1-555-0199."
analyzer = AnalyzerEngine()
results = analyzer.analyze(text=text, entities=["EMAIL_ADDRESS", "PHONE_NUMBER", "PERSON"], language='en')
print("Найденные сущности:")
for res in results:
print(f"Тип: {res.entity_type}, позиция: {res.start}:{res.end}, уверенность: {res.score:.2f}")
在分析器返回偏移量和实体类型后,连接匿名化模块:
anonymizer = AnonymizerEngine()
anonymized_result = anonymizer.anonymize(
text=text,
analyzer_results=results
)
print("\nРезультат обработки:")
print(anonymized_result.text)
输出是一个字符串,其中姓名被替换为 <PERSON>,联系方式被替换为 <EMAIL_ADDRESS> 和 <PHONE_NUMBER>。如果需要,你可以配置哈希、部分字符掩码或通过 Faker 用假数据替换。
添加自定义规则
开箱即用,该项目针对国际格式进行了良好的调优:美国社会安全号码、Visa 和 Mastercard 卡、国际电话号码、电子邮件。对于本地数据(如俄罗斯护照、INN 或 SNILS),你需要编写自己的识别器。
使用内置类实现这一点非常简单:
from presidio_analyzer import Pattern, PatternRecognizer
# Паттерн для поиска СНИЛС (формат XXX-XXX-XXX YY)
snils_pattern = Pattern(
name="snils_pattern",
regex=r"\b\d{3}-\d{3}-\d{3}\s\d{2}\b",
score=0.85
)
snils_recognizer = PatternRecognizer(
supported_entity="RU_SNILS",
patterns=[snils_pattern],
context=["снилс", "страховой", "пенсионный"]
)
analyzer.registry.add_recognizer(snils_recognizer)
当分析器看到与正则表达式匹配的项,并在附近找到 context 列表中的词时,它会将置信度分数提高到最高。
其他库功能
除了处理纯文本外,该仓库还包含用于相关任务的辅助模块:
presidio-image-redactor— 通过 OCR(Tesseract)在图像和 PDF 扫描件中查找文本,并在像素层面用黑色方块覆盖检测到的个人数据。适用于在发送到外部服务之前处理护照扫描件。- LLM 流水线集成。Presidio 通常用作在将提示发送给 OpenAI 或 Anthropic 之前的中间件:你从请求中清除用户的个人数据,将匿名化的文本发送给模型,然后在返回时根据需要将数据恢复到原位。
- 基于 FastAPI 的 REST API,封装在 Docker 容器中。你不必用 Python 编写整个服务——可以将分析器部署为微服务,并从 Go、Java 或 Node.js 后端调用它。
潜在挑战
第一个细节是资源消耗。如果你连接像 RoBERTa 或 BERT 这样的大型 transformers 以获得更准确的实体识别,推理将需要内存并减慢数据流处理速度。在数十万 RPS 上同步运行完整的 NLP 流水线会非常重——你需要将掩码任务卸载到异步 Kafka 或 Celery worker。
第二点是俄语模型。基本的 en_core_web_lg(来自 spaCy)在英语处理方面表现出色,但对于俄语,你需要连接 ru_core_news_lg 模型,或者针对你的文本特点微调自己的 NER 识别器。
实际应用
当公司需要遵守联邦法律 152、GDPR 或 HIPAA,但从头重写现有基础设施成本太高时,Presidio 就派上用场了。
该库非常适合以下三种场景:
- 在发送到监控系统(Sentry、OpenTelemetry、ELK)之前清除日志和追踪。
- 在使用真实用户数据为 ML 模型训练或分析准备数据集。
- 在基于大型语言模型的聊天机器人和 RAG 系统中过滤传入和传出的消息。
如果你正在寻找一个现成的数据掩码工具包,而不必从头编写解析器,这个仓库绝对值得收藏并用你自己的示例在本地测试。
相关项目