sql 实现数据脱敏

近日,公司要对相关查询页面中的用户名、手机号码、生日、地址等敏感信息进行脱敏处理,所谓脱敏也就是隐去其中的一部分,使之不完整,以保护用户个人信息。通过 sql 实现最为简单,在此总结整理一下。以下语句只保证在 Postgresql、MySQL、SQL Server 库中有效。

手机号码脱敏

手机号码脱敏要求中间四位以****代替。

PostgreSQL 方案

使用拼接

使用 left 和 right 拼接这个方法简单好用,易于维护。

select left('17817812598',3)||'****'||right('17817812598',4); --结果为 178****2598
使用 regexp_replace

regexp_replace 函数支持正则表达式,regexp_replace 的第一个参数是需要做脱敏处理的手机号码,第二个参数是一个正则表达式,第三个字段指定替换文本,即:替换对匹配到的子串进行替换。这是将手机号码分成了三个子串,其中第二个子串(中间 4 位数字)用****替换。

select regexp_replace('17817812598','(\d{3})(\d{4})(\d{4})','\1****\3'); --结果为 178****2598

regexp_replace 不会改变原始字段值,只是简单地返回替换后的副本。这里的正则表达式按数字长度将手机号码的前三位、中间四位和后四位分为 3 个部分,替换时首尾原样引用,中间部分则替换为****

MySQL 方案

使用拼接
select concat(left('17817812598',3), '****', right('17817812598',4)); --结果为 178****2598
使用 regexp_replace

regexp_replace 函数需要 MySQL 8.0 或更高版本。第一个参数是原始字符串,也就是需要脱敏的手机号码;第二个参数是匹配模式,这里是指定的4位数字;第三个参数指定替换字符串;第四个参数4指定模式匹配的开始位置(4即从参数1的第4个字符串开始应用模式匹配);第五个参数1用于指定需要用第三个参数替换的匹配项,1即仅替换第一个匹配项,指定0或省略此参数则替换调所有匹配项。

SELECT REGEXP_REPLACE('17817812598', '[0-9]{4}', '****', 4, 1); --结果为 178****2598

SQL Server 方案

select concat(left('17817812598',3), '****', right('17817812598',4)); --结果为 178****2598

邮箱脱敏

PostgreSQL 方案

邮箱脱敏要求 @前 1 位左边的部分以*代替,由于邮箱格式的特殊性:有且只能有一个 @符号,以及 regexp_replace 只替换最先匹配到的字符的特性。可以直接使用 regexp_replace 的基本形式,第一个参数指定邮箱,第二个参数指定需要替换的字符,第三个参数指定替换结果字符。

select regexp_replace('abc@123.com', left('abc@123.com',position('@' in 'abc@123.com')-2), '*'); --结果为 *c@123.com

也可以使用正则表达式实现

--处理方式与对手机号码使用的方式相同
select regexp_replace('abc@123.com','(\w*)(\w{1}@)(\w*)','*\2\3'); --结果为 *c@123.com

MySQL 方案

MySQL 使用以下两种 regexp_replace 都可以达到同样目的,第二种方式通过 concat 动态拼接正则表达式。

select regexp_replace('abc@123.com', left('abc@123.com',position('@' in 'abc@123.com')-2), '*'); --结果为 *c@123.com

SQL Server 方案

select '*' + SUBSTRING('abc@123.com', CHARINDEX('@', 'abc@123.com') - 1, len('abc@123.com')); --结果为 *c@123.com

生日的脱敏

PostgreSQL 方案

生日要求月份位和日期位分别以**代替,由于公司产品尊重个人选择,生日中的年份是可选的,因此脱敏后的结果可能出形如:1990/**/** 或 /**/** 两种形式。这里直接用 regexp_replace 配合正则表达式实现(也可以通过条件判断加字符串拼接来实现)。

select regexp_replace('12/30','(/{0,1}\d{1,2}/\d{1,2})\Z', '/**/**'); --显示/**/**
select regexp_replace('1990/12/30','(/{0,1}\d{1,2}/\d{1,2})\Z', '/**/**'); --显示 1990/**/**

这里的正则表达式是将月份和日期拆出来,月份的开头有个可选的/,结尾的\Z 标志用于告诉程序只在末尾部分匹配得到结果字符串(即这个正则表达式只取出来符合条件的月份和日期,接着用 regexp_replace 的第三个参数完成替换)。

MySQL 方案

select regexp_replace('12/30','(/{0,1}[0-9]{1,2}/[0-9]{1,2})$', '/**/**', 1, 1); -- 显示/**/**
select regexp_replace('1990/12/30','(/{0,1}[0-9]{1,2}/[0-9]{1,2})$', '/**/**', 1, 1); -- 显示 1990/**/**

SQL Server 方案

select replace('12/30' , right('12/30', 5), '**/**'); -- 显示**/**
select replace('1999/12/30' , right('1999/12/30', 5), '**/**'); -- 显示 1990/**/**

用户名和地址的脱敏比较好处理,直接使用 left 拼接实现反而更方便。

发表评论