身份证号脱敏最稳妥方式是用Substring截取前后位数再拼接星号:先校验长度为18,再取前6位+8个星号+后4位,null安全且避免正则误匹配。

身份证号脱敏:用 Substring + 固定掩码最稳妥
直接截取前后几位再拼接星号是最常用、最可控的方式,避免正则误匹配或长度判断出错。中国身份证号固定18位,前6位是地址码、后4位是校验和与顺序码,中间8位(出生日期+顺序码)最需隐藏。
实操建议:
- 用
str.Length == 18先校验长度,非18位不脱敏(防止把错误格式的字符串也“处理”了) - 脱敏逻辑写成独立方法,别硬编码在业务逻辑里:
public static string MaskIdCard(string id) => id?.Length == 18 ? id.Substring(0, 6) + "******" + id.Substring(14) : id;
- 注意 null 和空字符串,
?.Length比id != null && id.Length == 18更简洁安全 - 别用正则替换(如
Regex.Replace(id, @"(?),正则在长文本中可能回溯爆炸,且对非标准输入(如带X的末位、空格)容易失效
手机号脱敏:区分运营商号段不如直接按位操作
国内手机号都是11位,前3位是网络识别号(如139、151、177),后4位常用于验证,中间4位是真正需要隐藏的用户标识段。按位操作比查号段更稳定、无维护成本。
实操建议:
- 统一用
Substring(0, 3) + "****" + Substring(7),不依赖StartsWith判断号段 - 先清理空白符和短横线:
phone?.Replace(" ", "").Replace("-", ""),再判断长度是否为11 - 如果接口接收的是国际格式(如+8613912345678),得先去掉+86前缀再处理,否则长度判断会失败
- 别用
string.Format("{0}****{1}", phone[..3], phone[7..])—— C# 8 的范围语法在 .NET Framework 项目里不支持,兼容性风险高
银行卡号脱敏:用 PadLeft 处理不定长卡号
银行卡号长度不固定(16–19位都有),但通用规则是保留前6位(BIN号)和后4位,中间全掩码。不能假设都是16位然后硬切,否则19位卡会漏掉1位。
实操建议:
- 先用
card?.Trim().Where(char.IsDigit).ToArray()提纯数字,过滤空格、字母、Luhn校验位等干扰 - 再用
digits.Length >= 10做最低长度保护(少于10位基本不是有效卡号) - 脱敏表达式:
var d = digits; return d.Take(6).Concat(Enumerable.Repeat('*', Math.Max(0, d.Length - 10))).Concat(d.Skip(d.Length - 4)).ToArray();这样无论16、17、18、19位都只留首6尾4 - 别用
Regex.Replace(card, @"(? new string('*', m.Value.Length))—— 正则在超长字符串(如日志拼接字段)中可能栈溢出,且无法控制 * 的数量精度
脱敏逻辑放哪?别塞进实体类的 ToString 或属性 getter
脱敏是视图层/输出层职责,不是数据模型本身的行为。把脱敏逻辑混进 domain model 会导致:序列化 JSON 时自动脱敏(API 返回异常)、日志记录时写入星号(排查问题没原始值)、单元测试难 mock。
实操建议:
- 建一个
MaskingService类,所有脱敏方法集中管理,方便统一加审计日志或开关控制 - DTO 层做映射时调用,比如用 AutoMapper 配置:
ForMember(d => d.Phone, opt => opt.MapFrom(s => MaskingService.MaskPhone(s.Phone))) - Web API 返回前用 ActionFilter 做全局脱敏?不推荐——太隐式,后续加例外字段(如客服后台需看明文)时难控制;显式调用更清晰
- 数据库字段加密是另一回事,脱敏 ≠ 加密。别以为给数据库字段加了 AES 就不用脱敏前端显示——解密后的内存对象仍需按需脱敏
脱敏看似简单,但真实系统里最容易翻车的是边界场景:带符号的手机号、带校验位的旧身份证、BIN号后紧跟字母的测试卡号、以及——开发忘了在日志打印前手动脱敏,结果整条敏感信息被记进 ELK。


















