本文目录导读:

我来介绍几种PHP数据脱敏显示的方案,包括常见的数据类型脱敏方法。
通用脱敏函数
<?php
class DataMasking
{
/**
* 手机号脱敏
* 保留前3位和后4位
*/
public static function mobile(string $mobile): string
{
if (strlen($mobile) !== 11) {
return $mobile;
}
return substr_replace($mobile, '****', 3, 4);
}
/**
* 身份证号脱敏
* 保留前4位和后4位
*/
public static function idCard(string $idCard): string
{
if (strlen($idCard) < 8) {
return $idCard;
}
return substr_replace($idCard, '**********', 4, 10);
}
/**
* 姓名脱敏
* 保留姓,名字用*代替
*/
public static function name(string $name): string
{
if (mb_strlen($name) <= 1) {
return $name;
}
$first = mb_substr($name, 0, 1);
$rest = str_repeat('*', mb_strlen($name) - 1);
return $first . $rest;
}
/**
* 邮箱脱敏
* 保留前3位和域名
*/
public static function email(string $email): string
{
$atPos = strpos($email, '@');
if ($atPos === false) {
return $email;
}
$username = substr($email, 0, $atPos);
$domain = substr($email, $atPos);
if (strlen($username) <= 3) {
return '***' . $domain;
}
return substr($username, 0, 3) . str_repeat('*', strlen($username) - 3) . $domain;
}
/**
* 银行卡号脱敏
* 保留前4位和后4位
*/
public static function bankCard(string $cardNo): string
{
if (strlen($cardNo) <= 8) {
return $cardNo;
}
$masked = str_repeat('*', strlen($cardNo) - 8);
return substr($cardNo, 0, 4) . $masked . substr($cardNo, -4);
}
/**
* 地址脱敏
* 保留前6个字符
*/
public static function address(string $address): string
{
if (mb_strlen($address) <= 6) {
return $address;
}
return mb_substr($address, 0, 6) . str_repeat('*', mb_strlen($address) - 6);
}
/**
* IP地址脱敏
*/
public static function ip(string $ip): string
{
$parts = explode('.', $ip);
if (count($parts) !== 4) {
return $ip;
}
return $parts[0] . '.*.*.' . $parts[3];
}
/**
* 通用脱敏方法
* @param string $value 原始值
* @param string $type 类型:mobile, email, name, id_card, bank_card, address, ip
*/
public static function mask(string $value, string $type): string
{
switch ($type) {
case 'mobile':
return self::mobile($value);
case 'email':
return self::email($value);
case 'name':
return self::name($value);
case 'id_card':
return self::idCard($value);
case 'bank_card':
return self::bankCard($value);
case 'address':
return self::address($value);
case 'ip':
return self::ip($value);
default:
return $value;
}
}
}
// 使用示例
echo DataMasking::mobile('13812345678'); // 138****5678
echo DataMasking::name('张三丰'); // 张**
echo DataMasking::email('test@example.com'); // tes*****@example.com
基于注解的脱敏方案
<?php
/**
* 脱敏注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class MaskAttribute
{
public function __construct(
public string $type = 'default', // 脱敏类型
public ?int $start = null, // 开始位置
public ?int $length = null, // 脱敏长度
public string $maskChar = '*', // 掩码字符
public bool $keepEnd = true // 保留末尾字符
) {}
}
/**
* 脱敏处理器
*/
class MaskHandler
{
public static function handle(object $object)
{
$reflection = new \ReflectionClass($object);
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(MaskAttribute::class);
if (empty($attributes)) {
continue;
}
$maskAttribute = $attributes[0]->newInstance();
$property->setAccessible(true);
$value = $property->getValue($object);
if ($value !== null) {
$maskedValue = self::maskValue($value, $maskAttribute);
$property->setValue($object, $maskedValue);
}
}
}
private static function maskValue($value, MaskAttribute $attr): string
{
if ($attr->start !== null) {
return self::maskPart($value, $attr->start, $attr->length, $attr->maskChar, $attr->keepEnd);
}
// 根据类型脱敏
switch ($attr->type) {
case 'mobile':
return DataMasking::mobile($value);
case 'name':
return DataMasking::name($value);
case 'email':
return DataMasking::email($value);
case 'id_card':
return DataMasking::idCard($value);
default:
return (string)$value;
}
}
private static function maskPart($value, int $start, ?int $length, string $maskChar, bool $keepEnd): string
{
$str = (string)$value;
$len = strlen($str);
if ($start >= $len) {
return $str;
}
$maskLen = $length ?? ($keepEnd ? $len - $start - 1 : $len - $start);
if ($maskLen <= 0) {
return $str;
}
$mask = str_repeat($maskChar, $maskLen);
if ($keepEnd) {
return substr($str, 0, $start) . $mask . substr($str, $start + $maskLen, 1);
}
return substr($str, 0, $start) . $mask . substr($str, $start + $maskLen);
}
}
// 使用示例
class User
{
#[MaskAttribute(type: 'name')]
public string $name;
#[MaskAttribute(type: 'mobile')]
public string $phone;
#[MaskAttribute(type: 'email')]
public string $email;
#[MaskAttribute(start: 4, length: 4, keepEnd: true)]
public string $address;
public function __construct(string $name, string $phone, string $email, string $address)
{
$this->name = $name;
$this->phone = $phone;
$this->email = $email;
$this->address = $address;
}
}
// 使用
$user = new User('张三', '13812345678', 'test@example.com', '北京市朝阳区某某街道');
MaskHandler::handle($user);
print_r($user);
中间件脱敏方案(Laravel示例)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class DataMaskingMiddleware
{
protected $maskingRules = [
'users' => [
'phone' => 'mobile',
'email' => 'email',
'id_card' => 'id_card',
'name' => 'name'
],
'orders' => [
'address' => 'address',
'contact_phone' => 'mobile'
]
];
public function handle(Request $request, Closure $next)
{
$response = $next($request);
if ($response instanceof \Illuminate\Http\JsonResponse) {
$data = json_decode($response->getContent(), true);
if ($this->shouldMask($request)) {
$maskedData = $this->applyMasking($data);
$response->setData($maskedData);
}
}
return $response;
}
protected function shouldMask(Request $request): bool
{
// 如果不是浏览器请求或者需要脱敏的环境
if ($request->is('api/*')) {
return true;
}
return false;
}
protected function applyMasking(array $data): array
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = $this->applyMasking($value);
} else {
// 检查是否有对应的脱敏规则
$model = $this->getModelName($data);
if (isset($this->maskingRules[$model][$key])) {
$data[$key] = DataMasking::mask($value, $this->maskingRules[$model][$key]);
}
}
}
return $data;
}
protected function getModelName(array $data): string
{
// 根据数据特征判断模型名称
if (isset($data['user_id'])) {
return 'users';
}
if (isset($data['order_id'])) {
return 'orders';
}
return 'default';
}
}
数据库查询时脱敏(SQL方案)
<?php
class SqlMaskingBuilder
{
/**
* 生成SQL脱敏表达式
*/
public static function buildMaskExpression(string $column, string $type): string
{
switch ($type) {
case 'mobile':
return "CONCAT(LEFT($column, 3), '****', RIGHT($column, 4))";
case 'id_card':
return "CONCAT(LEFT($column, 4), '**********', RIGHT($column, 4))";
case 'name':
return "CONCAT(LEFT($column, 1), REPEAT('*', CHAR_LENGTH($column) - 1))";
case 'email':
return "CONCAT(LEFT($column, 3), '***', SUBSTRING_INDEX($column, '@', -1))";
default:
return $column;
}
}
/**
* 生成脱敏SELECT语句
*/
public static function maskedSelect(string $table, array $columns, array $maskingRules): string
{
$selectColumns = [];
foreach ($columns as $column) {
$selectColumns[] = self::buildMaskExpression($column, $maskingRules[$column] ?? 'default');
}
return "SELECT " . implode(', ', $selectColumns) . " FROM $table";
}
}
// 使用示例
$sql = SqlMaskingBuilder::maskedSelect(
'users',
['id', 'name', 'phone', 'email'],
['name' => 'name', 'phone' => 'mobile', 'email' => 'email']
);
自定义脱敏配置(配置文件方式)
<?php
// config/masking.php
return [
'users' => [
'name' => [
'type' => 'name',
'enabled' => true
],
'phone' => [
'type' => 'mobile',
'enabled' => true
],
'email' => [
'type' => 'email',
'enabled' => true,
'case_sensitive' => false
],
'address' => [
'type' => 'custom',
'start' => 6,
'maskChar' => '*',
'keepEnd' => false
]
],
'settings' => [
'default_mask_char' => '*',
'mask_percentage' => 70
]
];
class MaskingService
{
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function maskField(string $model, string $field, $value)
{
if (!isset($this->config[$model][$field])) {
return $value;
}
$rule = $this->config[$model][$field];
if (isset($rule['enabled']) && !$rule['enabled']) {
return $value;
}
if ($rule['type'] === 'custom') {
return $this->customMask($value, $rule);
}
return DataMasking::mask($value, $rule['type']);
}
private function customMask($value, array $rule): string
{
$start = $rule['start'] ?? 0;
$maskChar = $rule['maskChar'] ?? '*';
$keepEnd = $rule['keepEnd'] ?? true;
// 自定义脱敏逻辑
$str = (string)$value;
$len = strlen($str);
if ($start >= $len) {
return $str;
}
// 计算脱敏长度
$maskLen = ceil($len * ($this->config['settings']['mask_percentage'] ?? 70) / 100);
$maskLen = min($maskLen, $len - $start);
$mask = str_repeat($maskChar, $maskLen);
if ($keepEnd) {
return substr($str, 0, $start) . $mask . substr($str, $start + $maskLen);
}
return substr($str, 0, $start) . $mask;
}
}
// 使用示例
$maskingService = new MaskingService(require 'config/masking.php');
echo $maskingService->maskField('users', 'name', '张三丰');
完整的脱敏工具类(包含所有常用类型)
<?php
class ComprehensiveMasker
{
// 定义脱敏类型常量
const TYPE_MOBILE = 'mobile';
const TYPE_PHONE = 'phone';
const TYPE_EMAIL = 'email';
const TYPE_ID_CARD = 'id_card';
const TYPE_BANK_CARD = 'bank_card';
const TYPE_NAME = 'name';
const TYPE_ADDRESS = 'address';
const TYPE_IP = 'ip';
const TYPE_PASSWORD = 'password';
const TYPE_TAX_NO = 'tax_no';
const TYPE_LICENSE = 'license';
// 掩码字符映射
private static $maskChar = '*';
/**
* 统一脱敏入口
*/
public static function mask($value, string $type): string
{
if ($value === null || $value === '') {
return $value ?? '';
}
switch ($type) {
case self::TYPE_MOBILE:
return self::maskMobile($value);
case self::TYPE_PHONE:
return self::maskPhone($value);
case self::TYPE_EMAIL:
return self::maskEmail($value);
case self::TYPE_ID_CARD:
return self::maskIdCard($value);
case self::TYPE_BANK_CARD:
return self::maskBankCard($value);
case self::TYPE_NAME:
return self::maskName($value);
case self::TYPE_ADDRESS:
return self::maskAddress($value);
case self::TYPE_IP:
return self::maskIp($value);
case self::TYPE_PASSWORD:
return self::maskPassword($value);
case self::TYPE_TAX_NO:
return self::maskTaxNo($value);
case self::TYPE_LICENSE:
return self::maskLicense($value);
default:
return self::maskDefault($value);
}
}
private static function maskMobile($mobile): string
{
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $mobile);
}
private static function maskPhone($phone): string
{
// 支持座机号码脱敏
return preg_replace('/^(\d{3,4})(\d{4})(\d{4})$/', '$1****$3', $phone);
}
private static function maskEmail($email): string
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $email;
}
list($username, $domain) = explode('@', $email);
$maskedUsername = substr($username, 0, 3) . str_repeat(self::$maskChar, strlen($username) - 3);
return $maskedUsername . '@' . $domain;
}
private static function maskIdCard($idCard): string
{
if (strlen($idCard) === 18) {
return substr($idCard, 0, 4) . str_repeat(self::$maskChar, 10) . substr($idCard, 14);
}
if (strlen($idCard) === 15) {
return substr($idCard, 0, 4) . str_repeat(self::$maskChar, 7) . substr($idCard, 11);
}
return $idCard;
}
private static function maskBankCard($cardNo): string
{
return substr($cardNo, 0, 4) . str_repeat(self::$maskChar, 8) . substr($cardNo, -4);
}
private static function maskName($name): string
{
if (mb_strlen($name) <= 1) {
return $name;
}
// 不同类型姓名处理
if (mb_strlen($name) === 2) {
// 两个字:保留姓氏
return mb_substr($name, 0, 1) . self::$maskChar;
}
return mb_substr($name, 0, 1) . str_repeat(self::$maskChar, mb_strlen($name) - 2) . mb_substr($name, -1);
}
private static function maskAddress($address): string
{
$length = mb_strlen($address);
if ($length <= 4) {
return $address;
}
// 保留前4个字符,脱敏中间部分,保留后2个字符
$maskCount = $length - 6;
return mb_substr($address, 0, 4) .
str_repeat(self::$maskChar, max(0, $maskCount)) .
mb_substr($address, -2);
}
private static function maskIp($ip): string
{
$parts = explode('.', $ip);
if (count($parts) != 4) {
return $ip;
}
return $parts[0] . '.' . self::$maskChar . '.' . self::$maskChar . '.' . $parts[3];
}
private static function maskPassword($password): string
{
return str_repeat(self::$maskChar, strlen($password));
}
private static function maskTaxNo($taxNo): string
{
if (strlen($taxNo) === 15 || strlen($taxNo) === 18) {
return substr($taxNo, 0, 2) . str_repeat(self::$maskChar, strlen($taxNo) - 6) . substr($taxNo, -4);
}
return $taxNo;
}
private static function maskLicense($license): string
{
// 营业执照号脱敏
if (strlen($license) === 18) {
return '913***********' . substr($license, -4);
}
return $license;
}
private static function maskDefault($value): string
{
if (strlen($value) <= 3) {
return self::$maskChar;
}
return substr($value, 0, 3) . str_repeat(self::$maskChar, strlen($value) - 3);
}
}
// 批量脱敏函数
function maskArray(array $data, array $rules): array
{
foreach ($rules as $field => $type) {
if (isset($data[$field])) {
$data[$field] = ComprehensiveMasker::mask($data[$field], $type);
}
}
return $data;
}
// 使用示例
$userData = [
'name' => '张三丰',
'phone' => '13812345678',
'email' => 'test@example.com',
'id_card' => '110101199001011234',
'address' => '北京市朝阳区某某街道123号',
'ip' => '192.168.1.1'
];
$rules = [
'name' => ComprehensiveMasker::TYPE_NAME,
'phone' => ComprehensiveMasker::TYPE_MOBILE,
'email' => ComprehensiveMasker::TYPE_EMAIL,
'id_card' => ComprehensiveMasker::TYPE_ID_CARD,
'address' => ComprehensiveMasker::TYPE_ADDRESS,
'ip' => ComprehensiveMasker::TYPE_IP
];
$maskedData = maskArray($userData, $rules);
print_r($maskedData);
总结与建议
脱敏方案选择建议:
- 性能要求高:使用SQL层面的脱敏
- 可维护性要求高:使用注解或配置驱动的方式
- 安全要求高:在后端强制脱敏,前端不接收敏感数据
- 数据量大:采用分批脱敏,避免一次性处理
最佳实践:
- ✅ 在生产环境默认启用脱敏
- ✅ 提供脱敏开关,用于测试环境
- ✅ 脱敏规则集中管理
- ✅ 敏感数据加密存储
- ✅ 日志中也进行脱敏
完整示例下载地址:
- GitHub: PHP Data Masking Solution