本文目录导读:

在 PHP 中进行数据静态脱敏,主要有以下几种常用方法,我会从简单到复杂依次介绍:
基础字符串函数脱敏(最简单)
使用 substr_replace、str_repeat 等函数实现基本脱敏:
<?php
// 手机号脱敏:保留前3后4
function maskMobile($mobile) {
return substr_replace($mobile, '****', 3, 4);
}
// 身份证号脱敏:保留前6后4
function maskIdCard($idCard) {
return substr_replace($idCard, '********', 6, 8);
}
// 邮箱脱敏
function maskEmail($email) {
$parts = explode('@', $email);
$name = $parts[0];
$maskedName = substr($name, 0, 2) . str_repeat('*', strlen($name) - 2);
return $maskedName . '@' . $parts[1];
}
// 测试
echo maskMobile('13812345678'); // 138****5678
echo "\n";
echo maskIdCard('110101199001011234'); // 110101********1234
echo "\n";
echo maskEmail('zhangsan@example.com'); // zh*****@example.com
使用正则表达式脱敏
适合更复杂的脱敏规则:
<?php
function maskWithRegex($data, $type) {
switch ($type) {
case 'bank_card': // 银行卡号
return preg_replace('/(\d{4})\d{8}(\d{4})/', '$1********$2', $data);
case 'name': // 姓名:保留姓
return preg_replace('/([\x{4e00}-\x{9fa5}])./u', '$1*', $data);
case 'address': // 地址:保留前6个字符
return mb_substr($data, 0, 6) . str_repeat('*', mb_strlen($data) - 6);
}
return $data;
}
echo maskWithRegex('6222020200012345678', 'bank_card'); // 6222********4567
echo "\n";
echo maskWithRegex('张三', 'name'); // 张*
echo "\n";
echo maskWithRegex('北京市朝阳区某某街道', 'address'); // 北京市朝阳*****
数组批量脱敏
当需要处理多条记录时:
<?php
class DataMasker {
// 脱敏规则映射
private static $rules = [
'mobile' => ['type' => 'fixed', 'start' => 3, 'length' => 4],
'id_card' => ['type' => 'fixed', 'start' => 6, 'length' => 8],
'real_name' => ['type' => 'name'],
'email' => ['type' => 'email'],
'bank_card' => ['type' => 'regex', 'pattern' => '/(\d{4})\d{8}(\d{4})/', 'replacement' => '$1********$2'],
];
// 批量脱敏
public static function maskArray(array $data, array $fields) {
foreach ($data as &$row) {
foreach ($fields as $field => $ruleName) {
if (isset($row[$field])) {
$row[$field] = self::mask($row[$field], $ruleName);
}
}
}
return $data;
}
// 单个字段脱敏
public static function mask($value, $ruleName) {
if (empty($value)) return $value;
$rule = self::$rules[$ruleName] ?? null;
if (!$rule) return $value;
switch ($ruleName) {
case 'mobile':
case 'id_card':
return substr_replace(
$value,
str_repeat('*', $rule['length']),
$rule['start'],
$rule['length']
);
case 'real_name':
return mb_substr($value, 0, 1) . str_repeat('*', mb_strlen($value) - 1);
case 'email':
$parts = explode('@', $value);
return substr($parts[0], 0, 2) . '***@' . $parts[1];
case 'bank_card':
return preg_replace($rule['pattern'], $rule['replacement'], $value);
}
return $value;
}
}
// 使用示例
$users = [
[
'id' => 1,
'name' => '张三',
'mobile' => '13812345678',
'email' => 'zhangsan@example.com',
'id_card' => '110101199001011234'
],
[
'id' => 2,
'name' => '李四',
'mobile' => '13987654321',
'email' => 'lisi@example.com',
'id_card' => '110101199202022345'
]
];
$masked = DataMasker::maskArray($users, [
'name' => 'real_name',
'mobile' => 'mobile',
'email' => 'email',
'id_card' => 'id_card'
]);
print_r($masked);
结合数据库查询的脱敏
处理从数据库查询出来的数据:
<?php
class UserService {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
// 获取用户列表(已脱敏)
public function getUsersWithMask(): array {
$stmt = $this->db->query("SELECT id, name, mobile, email FROM users");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 应用脱敏规则
foreach ($users as &$user) {
$user['name'] = $this->maskName($user['name']);
$user['mobile'] = $this->maskMobile($user['mobile']);
$user['email'] = $this->maskEmail($user['email']);
}
return $users;
}
// 获取敏感数据(不脱敏,内部使用)
public function getRawUser($id): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
使用第三方库(推荐)
可以使用 fakerphp/faker 或自定义的脱敏类库:
composer require fakerphp/faker
<?php
require_once 'vendor/autoload.php';
use Faker\Factory;
class DataDesensitizer {
private $faker;
public function __construct() {
$this->faker = Factory::create('zh_CN');
}
// 生成脱敏数据
public function generateMaskedData() {
return [
'name' => $this->maskName(),
'mobile' => $this->maskMobile(),
'id_card' => $this->maskIdCard(),
];
}
private function maskName() {
// 生成近似人名
$lastNames = ['张', '李', '王', '刘', '陈'];
return $lastNames[array_rand($lastNames)] . '某';
}
private function maskMobile() {
return '138****' . $this->faker->numerify('####');
}
private function maskIdCard() {
return $this->faker->numerify('########') . '********' . $this->faker->numerify('####');
}
}
完整的脱敏工具类
<?php
class SensitiveDataMasker {
// 支持的脱敏类型
const MASK_FIXED = 'fixed'; // 固定位置脱敏
const MASK_REGEX = 'regex'; // 正则脱敏
const MASK_NAME = 'name'; // 姓名脱敏
const MASK_EMAIL = 'email'; // 邮箱脱敏
const MASK_ADDRESS = 'address'; // 地址脱敏
// 通用脱敏方法
public static function mask($value, $type, $options = []) {
switch ($type) {
case self::MASK_FIXED:
return self::maskFixed($value, $options['start'] ?? 3,
$options['length'] ?? 4,
$options['char'] ?? '*');
case self::MASK_REGEX:
return self::maskRegex($value, $options['pattern'],
$options['replacement']);
case self::MASK_NAME:
return self::maskName($value);
case self::MASK_EMAIL:
return self::maskEmail($value);
case self::MASK_ADDRESS:
$keep = $options['keep'] ?? 6;
return self::maskAddress($value, $keep);
default:
return $value;
}
}
private static function maskFixed($value, $start, $length, $char) {
return substr_replace($value, str_repeat($char, $length), $start, $length);
}
private static function maskRegex($value, $pattern, $replacement) {
return preg_replace($pattern, $replacement, $value);
}
private static function maskName($value) {
return mb_substr($value, 0, 1) . str_repeat('*', mb_strlen($value) - 1);
}
private static function maskEmail($value) {
$parts = explode('@', $value);
$len = strlen($parts[0]);
$keep = max(2, $len - 3);
return substr($parts[0], 0, $keep) . str_repeat('*', $len - $keep) . '@' . $parts[1];
}
private static function maskAddress($value, $keep) {
return mb_substr($value, 0, $keep) . str_repeat('*', mb_strlen($value) - $keep);
}
}
// 使用示例
echo SensitiveDataMasker::mask('13812345678', 'fixed', ['start' => 3, 'length' => 4]);
// 138****5678
总结建议
- 生产环境:建议使用专门的脱敏类库或框架
- 性能考虑:大批量数据时使用 SQL 层脱敏(如
CONCAT、SUBSTRING) - 安全性:脱敏不能完全替代权限控制
- 测试环境:可以使用
faker生成真实度高的测试数据
选择哪种方式取决于你的具体需求、数据量和性能要求。