敏感数据处理规范
手机号、身份证等脱敏
[Requirement] 所有数据字段必须标注敏感级别,按级别采取不同的防护措施。
| 级别 | 名称 | 示例 | 防护要求 |
|---|---|---|---|
| L4 | 极敏感 | 密码、银行卡 CVV、密钥 | 不可逆哈希 / 加密存储,禁止明文出现 |
| L3 | 高敏感 | 身份证号、银行卡号、手机号、家庭住址 | 加密存储 + 展示脱敏 + 访问审计 |
| L2 | 中敏感 | 邮箱、企业名称、订单金额 | 传输加密 + 最小化展示 |
| L1 | 低敏感 | 用户名、头像、昵称 | 正常存储,传输加密 |
[Standard] 在数据模型 / DTO 中用注解或常量标注字段敏感级别,便于自动化扫描和审计。
from enum import IntEnum
class Sensitivity(IntEnum):
L1 = 1 # 低敏感
L2 = 2 # 中敏感
L3 = 3 # 高敏感
L4 = 4 # 极敏感
# 数据库表字段注释标注敏感级别
# phone VARCHAR(20) COMMENT '手机号 [SENSITIVITY=L3]'
# id_card VARCHAR(32) COMMENT '身份证号 [SENSITIVITY=L3]'[Requirement] 敏感数据展示时必须脱敏,脱敏后保留可识别度(如手机号保留前 3 后 4),禁止完全替换为 ***。
| 数据类型 | 脱敏规则 | 示例 |
|---|---|---|
| 手机号 | 前 3 + **** + 后 4 | 138****5678 |
| 身份证号 | 前 6 + ******** + 后 4 | 110101********1234 |
| 银行卡号 | 前 4 + **** + 后 4 | 6222****1234 |
| 邮箱 | 首字符 + *** + @ 后域名 | z***@example.com |
| 姓名 | 保留首字,其余 * | 张*、欧阳** |
| 地址 | 保留省市区,详细地址 * | 北京市朝阳区*** |
def mask_phone(phone: str) -> str:
if len(phone) < 7:
return phone
return phone[:3] + "****" + phone[-4:]
def mask_id_card(id_card: str) -> str:
if len(id_card) < 10:
return id_card
return id_card[:6] + "*" * 8 + id_card[-4:]
def mask_name(name: str) -> str:
if not name:
return name
return name[0] + "*" * (len(name) - 1)
def mask_email(email: str) -> str:
if "@" not in email:
return email
user, domain = email.split("@", 1)
return user[0] + "***@" + domain[Requirement] 脱敏在数据出口(返回前端、写日志、导出报表)时进行,数据库中存原始值(加密后),不存脱敏值。
[Standard] 通过统一的序列化器 / 拦截器在响应出口自动脱敏,避免业务代码遗漏。
# FastAPI 响应出口自动脱敏示例
from fastapi.responses import JSONResponse
@app.middleware("http")
async def desensitize_response(request, call_next):
response = await call_next(request)
if response.media_type == "application/json":
# 对响应体中标记为敏感的字段做脱敏
body = await response.body()
# ... 脱敏处理 ...
return response[Caution] 部分业务需要展示部分明文又能还原(如客服查看完整手机号),应采用可逆加密(AES)而非哈希,且查看操作需二次审批并记录审计日志。
[Requirement] L3 及以上敏感字段必须加密存储,禁止明文落库。加密算法使用 AES-256-GCM(认证加密,防篡改)。
[Standard] 加密密钥与数据分离存储:数据存数据库,密钥存 KMS / 密钥管理服务,禁止把密钥硬编码在代码或配置文件中。
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
class FieldEncryptor:
def __init__(self, key: bytes): # key 从 KMS 获取,32 bytes
self.aes = AESGCM(key)
def encrypt(self, plaintext: str) -> str:
nonce = os.urandom(12)
ct = self.aes.encrypt(nonce, plaintext.encode(), None)
# 存储格式:base64(nonce + ciphertext)
import base64
return base64.b64encode(nonce + ct).decode()
def decrypt(self, ciphertext: str) -> str:
raw = base64.b64decode(ciphertext)
nonce, ct = raw[:12], raw[12:]
return self.aes.decrypt(nonce, ct, None).decode()[Standard] 加密字段无法直接做 SQL 模糊查询。如需按手机号查询,可额外存储哈希值(SHA-256 + 盐)用于精确匹配,或使用保序加密 / 可搜索加密方案。
import hashlib
def phone_index(phone: str, salt: str) -> str:
# 用固定盐做哈希,存储到 phone_hash 字段用于精确查询
return hashlib.sha256((phone + salt).encode()).hexdigest()[Caution] 密码(L4)使用不可逆哈希(bcrypt / argon2),不使用加密(加密可还原 = 风险)。详见《密码与密钥管理规范》。
[Requirement] 生产环境强制 HTTPS(TLS 1.2+,建议 1.3),所有 HTTP 请求 301 重定向到 HTTPS。
[Requirement] 设置 Strict-Transport-Security: max-age=31536000; includeSubDomains,禁止降级到 HTTP。
[Standard] 敏感数据即使在 HTTPS 下也建议做应用层加密(如支付密码用 RSA 加密后传输),防止中间环节(网关、日志)泄露。
[Requirement] API 响应中不返回密码、密钥、完整身份证号等 L4 字段;L3 字段默认脱敏,需明文时需明确授权并记录。
[Requirement] 日志中禁止打印密码、密钥、Token、完整身份证号、银行卡号等敏感数据明文。
[Standard] 通过日志框架的过滤器 / 脱敏组件,对日志中的敏感字段自动脱敏。
import logging
import re
class SensitiveFilter(logging.Filter):
PATTERNS = {
"phone": (r'1[3-9]\d{9}', mask_phone),
"id_card": (r'\d{17}[\dXx]', mask_id_card),
}
def filter(self, record):
msg = record.getMessage()
for pattern, func in self.PATTERNS.values():
msg = re.sub(pattern, lambda m: func(m.group()), msg)
record.msg = msg
return True
# 注册到日志处理器
handler.addFilter(SensitiveFilter())[Requirement] 包含敏感信息的日志加密存储,访问需审计;日志留存周期按合规要求(一般 6 个月~3 年),到期自动清理。
[Caution] 禁止把包含敏感数据的日志导出到公共渠道(微信、邮件、公开网盘)。
[Caution] 本地调试日志在提交代码前必须删除或降级,禁止在生产环境开启 DEBUG 级别日志打印请求体。
[Standard] 测试环境使用脱敏后的测试数据,禁止用生产真实敏感数据做测试。