跳转到内容

敏感数据处理规范

手机号、身份证等脱敏


[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 + **** + 后 4138****5678
身份证号前 6 + ******** + 后 4110101********1234
银行卡号前 4 + **** + 后 46222****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] 测试环境使用脱敏后的测试数据,禁止用生产真实敏感数据做测试。