脱敏必须在迭代器内部完成,而非外部后处理;每次next()返回已掩码结果,原始数据全程不暴露于迭代流。核心是将掩码规则嵌入迭代器状态机,确保敏感字段在返回前即时转换,原始值仅存于私有缓冲区或只读引用中。

直接在迭代过程中对敏感字段做掩码,关键不是“改输出”,而是“不暴露原始值”。必须让每次 next() 返回的都是已脱敏结果,且原始数据全程不参与公开遍历流——这要求迭代器本身承担脱敏逻辑,而非靠外部函数后处理。
核心原则:脱敏必须发生在迭代器内部
常见错误是先用普通迭代器取出原始字符串,再调用 mask_phone("13812345678")。这种方式风险高:原始值已在内存中暴露,可能被日志、调试器或异常堆栈捕获。正确做法是把脱敏规则内嵌进迭代器状态机:
- 迭代器构造时接收字段名列表(如
["phone", "id_card", "email"])和对应掩码策略(如partial(3, "***", 4)) -
next()每次返回前,检查当前字段是否在敏感名单中;若是,立即按策略生成掩码后值,仅返回该结果 - 原始数据保留在私有缓冲区或只读引用中,永不通过
Iterator::Item类型向外传递
Python 示例:带字段感知的字典行迭代器
假设你从 CSV 或数据库逐行读取 dict 数据,希望对指定键自动脱敏:
class MaskedDictIterator:
def __init__(self, data_iter, mask_rules):
# mask_rules: {"phone": lambda s: s[:3] + "*" * 7 + s[-4:], "email": lambda s: s[0] + "*" * (s.find("@")-1) + s[s.find("@"):]}
self._iter = iter(data_iter)
self._mask_rules = mask_rules
<pre class='brush:java;toolbar:false;'>def __iter__(self):
return self
def __next__(self):
row = next(self._iter)
masked = {}
for k, v in row.items():
if k in self._mask_rules and isinstance(v, str):
try:
masked[k] = self._mask_rules[k](v)
except Exception:
masked[k] = "[MASK_ERROR]"
else:
masked[k] = v
return masked使用
rules = { "phone": lambda s: s[:3] + "*" + s[-4:] if len(s) >= 11 else s, "email": lambda s: s.split("@")[0][0] + "@" + s.split("@")[1] if "@" in s else s } for row in MaskedDictIterator(db_query(), rules): print(row) # 输出已是脱敏结果,无原始手机号/邮箱
Rust 实现要点:零拷贝 + 生命周期安全
Rust 中更需警惕内存暴露。不能把原始字符串 &str 直接塞进 Iterator::Item,否则调用方可能意外保留引用。推荐方案:
- 用
enum Item { Raw(&'a str), Masked(String) }明确区分,强制消费方处理掩码态 - 对敏感字段,用
String::from("****")分配新字符串,绝不复用原 slice - 若需高性能,可预分配掩码缓冲区(如固定长手机号统一转为
"***-****-****"),避免运行时拼接
不推荐的“伪安全”模式
以下方式看似简洁,实则破坏脱敏完整性:
-
map(|row| mask_fields(row)):上游迭代器仍暴露原始值,mask_fields只是后置转换 - 在
__repr__或模板渲染层做掩码:原始值已进入 Python 对象图,可能被pickle、logging.debug或 IDE 变量面板捕获 - 依赖数据库动态掩码(如 Azure DDM):应用层代码仍能通过
UNMASK权限读到明文,未切断信任链


















