最稳方案是先清洗、再校验、最后替换;手机号需去除非数字字符并验证11位及号段,身份证须分15/18位处理且保留末位x大小写,导出前必须真实替换而非样式隐藏,通用函数应仅负责切片不介入清洗校验。

直接用 substr_replace() 最稳,但必须先清洗、再校验、最后替换——跳过任意一步都可能在空值、脏数据或旧证上翻车。
手机号脱敏:别信“11位就安全”,先清再判再切
常见错误是拿到字符串就 substr_replace($phone, '****', 3, 4),结果遇到 "138-1234-5678" 或 " 13812345678"(带全角空格)直接切出错位;更糟的是 null 或空字符串,strlen() 返回 0,substr_replace() 不报错但返回空串,前端显示异常。
- 清洗:用
preg_replace('/\D/', '', $phone)去掉所有非数字字符 - 校验:清洗后
strlen($clean) === 11 && preg_match('/^1[3-9]\d{9}$/', $clean)双重确认 - 替换:通过则
substr_replace($clean, '****', 3, 4);否则原样返回或返回null(按业务定) - 固话不混入:如需支持
"010-12345678",得单独分支处理,不能塞进同一逻辑
身份证脱敏:15位和18位不能一锅炖,末位X大小写要留原样
直接 substr($id, 0, 6) . '******' . substr($id, -4) 看似简单,但遇上 "11010119900307271x"(小写 x),substr() 截出来还是小写,后续若被拿去调老接口做校验比对,可能因大小写不一致失败;15位老证若也按18位切,会漏掉末3位变成 "110101******71x"(错成7位掩码+错位截取)。
- 粗筛格式:
preg_match('/^\d{17}[\dXx]$/', $id)或strlen($id) === 15 - 保留原始末位:不做
strtoupper()统一,直接substr($id, -4)拿最后4个字符(含 x/X) - 分情况替换:
- 18位:
substr_replace($id, '******', 6, 8) - 15位:
substr_replace($id, '******', 6, 6)(中间掩6位,保留前6+后3)
- 18位:
- 不匹配的字符串(如含字母、长度异常)直接跳过脱敏,不强行处理
导出场景下脱敏:别在Excel里“假装隐藏”,要在写入前改值
有人在 PHP 导出 Excel 时用样式把字体设为白色、或用条件格式“隐藏”单元格内容,这是无效的——用户复制粘贴、解压 .xlsx 文件看 xl/sharedStrings.xml 就能拿到原文。真正安全的做法,是在数据写入 Excel 对象前,就把敏感字段值替掉。
- 用
array_map()+ 预定义规则数组统一清洗,比如:['phone' => 'mask_phone', 'id_card' => 'mask_id_card'] - 避免在 SQL 层用
CONCAT()拼接脱敏结果,会导致字段不可索引、无法参与 WHERE 查询 - 导出 CSV 前加
mb_convert_encoding($val, 'UTF-8', 'auto')防中文乱码,再清理 BOM 和不可见控制字符 - 大数量导出时,优先用
substr_replace()而非preg_replace(),前者性能高 3–5 倍(实测 10 万条)
封装通用函数:别把掩码逻辑和业务逻辑耦死
很多人写一个 mask_id()、一个 mask_phone()、再写个 mask_email(),看着清晰,但字段一多就维护爆炸。更麻烦的是,一旦某天要改成“手机号保留前4后4”,得改三处函数+所有调用点。
- 推荐封装
mask_string($str, $start, $end, $mask = '*'),核心逻辑只有一行:substr($str, 0, $start) . str_repeat($mask, max(0, strlen($str) - $start - $end)) . substr($str, -$end) - 但注意:该函数不校验、不清洗、不区分类型——它只是“安全切片器”,前置校验仍需各自完成
- 调用示例:
mask_string($phone, 3, 4, '*')、mask_string($id, 6, 4, '*')(18位)或mask_string($id, 6, 3, '*')(15位) - 千万别在函数里自动清洗或抛异常——脱敏是展示层行为,不是数据校验入口
最易被忽略的点:脱敏后的字符串不能再用于任何校验逻辑(比如身份证号脱敏后传给第三方接口做实名核验),也不该存入日志——哪怕你写了 error_log($masked_id),日志系统若没脱敏配置,照样泄露。脱敏只发生在最终输出那一刻,且仅限该上下文。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











