4、脱敏算法详解(上):替换、遮蔽与混洗

脱敏算法,说白了就是给敏感数据“换脸”的技术。你想想看,数据湖里存着各种用户信息——姓名、手机号、邮箱、身份证号……这些数据如果直接暴露给开发测试环境,风险太大了。

我个人习惯把脱敏算法分成三大类:替换、遮蔽和混洗。今天咱们先聊这三类。它们各有各的适用场景,也各有各的坑。

核心观点: 没有万能的脱敏算法。选择哪种算法,取决于你的数据特征、业务需求和安全等级。

4.1 替换算法:给敏感数据“换个名字”

替换算法,就是用预设的假数据,把真实数据替换掉。比如把“张三”换成“李四”,把“zhangsan@example.com”换成“test@test.com”。

我在项目中遇到过,很多团队觉得替换算法最简单,直接上。结果呢?替换后的数据失去了业务关联性,测试用例跑不通。嗯,这里要注意:替换算法不是简单的“找本字典随机替换”。

4.1.1 姓名替换

姓名替换,常见做法是维护一个“假名库”。比如从百家姓里随机取姓,再从常用名字库里随机取名。

// 伪代码示例:姓名替换
function replaceName(realName) {
    // 从假名库中随机选取
    const fakeSurname = ['张', '李', '王', '赵', '刘'][randomIndex];
    const fakeGivenName = ['伟', '芳', '娜', '秀英', '敏'][randomIndex];
    return fakeSurname + fakeGivenName;
}
我的经验: 假名库至少要准备 1000 个以上不重复的假名。否则,数据量大时会出现大量重复,一眼就能看出是脱敏数据。

4.1.2 邮箱替换

邮箱替换,不能只替换@前面的部分。我见过有人只把用户名替换成随机字符串,邮箱域名保持不变。结果呢?攻击者通过域名就能反推用户所属公司。

正确的做法是:用户名和域名都替换。而且,替换后的邮箱要符合邮箱格式规范。

// 伪代码示例:邮箱替换
function replaceEmail(realEmail) {
    const fakeUser = 'user' + randomString(8);
    const fakeDomain = ['test.com', 'example.org', 'demo.net'][randomIndex];
    return fakeUser + '@' + fakeDomain;
}
避坑指南: 我曾经在项目中,因为邮箱替换后没有保留“@”符号,导致下游系统解析失败。记住:替换后的数据必须保持原始格式。

4.2 遮蔽算法:只让你看到“该看的部分”

遮蔽算法,就是把敏感数据的部分字符用特殊符号(比如*)代替。你想想看,客服系统里,客服只需要看到用户手机号的前三位和后四位,中间四位用*号遮蔽,就足够了。

遮蔽算法的核心是:保留业务所需的最小信息量。

4.2.1 手机号中间四位遮蔽

这是最常见的遮蔽场景。手机号 11 位,前三位是运营商号段,后四位是用户编号。中间四位遮蔽后,既能识别用户,又不会泄露完整号码。

// 伪代码示例:手机号遮蔽
function maskPhone(phone) {
    // 保留前3位和后4位,中间用****代替
    return phone.substring(0, 3) + '****' + phone.substring(7);
}

// 输入:13812345678
// 输出:138****5678
关键点: 遮蔽位置的选择,要基于业务需求。不是所有字段都遮蔽中间。比如身份证号,通常遮蔽出生日期部分(第7-14位)。

4.2.2 其他常见遮蔽场景

数据类型 遮蔽规则 示例
身份证号 保留前6位和后4位,中间遮蔽 110101******1234
银行卡号 保留后4位,其余遮蔽 **** **** **** 5678
IP地址 遮蔽最后一段 192.168.1.***
地址信息 保留到街道/乡镇级别,遮蔽具体门牌号 北京市海淀区***
避坑指南: 我曾经在项目中,对身份证号只遮蔽了中间8位,结果发现前6位(地区码)和后4位(顺序码+校验码)组合起来,仍然能唯一确定一个人。后来我改成了保留前3位和后4位,中间全部遮蔽。

4.3 混洗算法:打乱数据,但保持统计特征

混洗算法,就是把同一列的数据随机打乱顺序。比如,有一列“年龄”数据,混洗后,每个人的年龄还是原来的那些值,只是分配给了不同的人。

混洗算法的好处是:保持了数据的统计分布特征。比如年龄的均值、方差、最大值、最小值,混洗前后完全一致。这对于数据分析场景非常有用。

4.3.1 混洗的实现方式

混洗的核心是“随机置换”。但要注意:不能简单地对整列进行随机排序。为什么呢?因为如果多列数据之间存在关联关系(比如“年龄”和“收入”),单独混洗某一列会破坏这种关联。

// 伪代码示例:单列混洗
function shuffleColumn(dataArray) {
    // Fisher-Yates 洗牌算法
    for (let i = dataArray.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [dataArray[i], dataArray[j]] = [dataArray[j], dataArray[i]];
    }
    return dataArray;
}
我的经验: 混洗算法最适合“单列独立使用”的场景。如果多列之间有强关联(比如“城市”和“邮编”),建议使用“分组混洗”——先按关联字段分组,再在组内混洗。

4.3.2 混洗 vs 替换:怎么选?

对比维度 混洗算法 替换算法
数据分布 保持原始分布 可能改变分布
关联关系 破坏列间关联 可保持列间关联(通过映射表)
可逆性 不可逆(除非记录映射) 可逆(通过映射表)
适用场景 数据分析、机器学习 功能测试、开发环境
避坑指南: 我曾经在项目中,对用户表做了混洗脱敏。结果测试人员发现,同一个用户的“订单记录”和“用户信息”对不上了。原因是我只混洗了用户表,没有混洗订单表。记住:混洗必须保持跨表的外键关联一致性。

4.4 三种算法的选择策略

说了这么多,到底怎么选?我个人的经验是:

  • 需要保持数据原始格式和业务含义 → 用替换算法。比如测试环境需要“看起来像真实数据”的姓名和邮箱。
  • 只需要部分信息,且要保留识别能力 → 用遮蔽算法。比如客服系统、日志脱敏。
  • 需要保持数据统计特征,用于分析 → 用混洗算法。比如数据湖里的分析数据集。

当然,实际项目中往往是组合使用。比如:姓名用替换,手机号用遮蔽,年龄用混洗。嗯,这里没有标准答案,只有最适合你场景的方案。

脱敏算法分类与选择策略 脱敏算法 替换算法 遮蔽算法 混洗算法 姓名替换 邮箱替换 手机号遮蔽 身份证遮蔽 单列混洗 分组混洗 选择建议 • 需要保持数据格式和业务含义 → 替换算法 • 只需要部分信息,保留识别能力 → 遮蔽算法 • 需要保持统计特征用于分析 → 混洗算法