首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 敏感資料遮罩完整指南:用戶名、身份證、IP、手機號、銀行卡星號隱藏實作

     編輯:PHP 相關知識

資安法規 (GDPR、個資法) 要求前端顯示時對敏感資料進行去識別化。本文提供用戶名、身份證、IP、手機號、銀行卡、郵箱等 6 類常見敏感資料的遮罩函數,含正則與非正則兩種實作,可直接投入生產環境。

核心遮罩原則

資料類型遮罩規則範例
中文用戶名保留首尾字,中間 *張*三、王**五
英文用戶名首字母 + * + 尾字母j***n、a***k
身份證字號 (台)顯示首碼 + 後 4 碼A123******5678
身份證號碼 (陸)隱藏出生日期段 (7-14 位)110101********1234
IPv4隱藏最後一段192.168.1.*
IPv6隱藏後 64 位2001:db8::/64
手機號 (台)09XX-XXX-XXX → 09**-XXX-XXX0912-345-678 → 09**-345-678
手機號 (陸)隱藏中間 4 碼138****1234
銀行卡顯示首 6 尾 4 (BIN + 校驗碼)6225 88****** 1234
Email本地部首字母 + * + @domainj***@example.com

通用遮罩工具類

<?php
class DataMasker {
    /**
     * 中文用戶名遮罩 (支援多字節)
     */
    public static function chineseName($name, $maskChar = '*') {
        $len = mb_strlen($name, 'UTF-8');
        if ($len <= 1) return $name;
        if ($len === 2) return mb_substr($name, 0, 1) . $maskChar;
        
        return mb_substr($name, 0, 1, 'UTF-8') 
            . str_repeat($maskChar, $len - 2) 
            . mb_substr($name, -1, 1, 'UTF-8');
    }
    
    /**
     * 英文用戶名遮罩
     */
    public static function englishName($name, $maskChar = '*') {
        $len = strlen($name);
        if ($len <= 2) return $name[0] . $maskChar;
        return $name[0] . str_repeat($maskChar, $len - 2) . $name[$len - 1];
    }
    
    /**
     * 通用:保留首 N 尾 M 字元
     */
    public static function keepStartEnd($str, $start = 1, $end = 1, $maskChar = '*') {
        $len = strlen($str);
        if ($len <= $start + $end) return $str;
        return substr($str, 0, $start) 
            . str_repeat($maskChar, $len - $start - $end) 
            . substr($str, -$end);
    }
    
    // === 身份證遮罩 ===
    public static function taiwanId($id) {
        // 格式: A123456789 (1 字母 + 9 數字)
        if (!preg_match('/^[A-Z][12]\d{8}$/i', $id)) return $id;
        return substr($id, 0, 1) . str_repeat('*', 6) . substr($id, -3);
    }
    
    public static function chinaId($id) {
        // 18 位: 6地址 + 8出生 + 3順序 + 1校驗
        if (strlen($id) === 18) {
            return substr($id, 0, 6) . '********' . substr($id, -4);
        }
        // 15 位舊版
        if (strlen($id) === 15) {
            return substr($id, 0, 6) . '*****' . substr($id, -4);
        }
        return $id;
    }
    
    // === IP 遮罩 ===
    public static function ipv4($ip) {
        $parts = explode('.', $ip);
        if (count($parts) !== 4) return $ip;
        $parts[3] = '*';
        return implode('.', $parts);
    }
    
    public static function ipv6($ip) {
        // 簡化:顯示前綴 /64
        if (strpos($ip, ':') === false) return $ip;
        return preg_replace('/(:[0-9a-fA-F]{1,4}){4}$/', '::/64', $ip);
    }
    
    // === 手機遮罩 ===
    public static function taiwanMobile($mobile) {
        // 09XXXXXXXX 或 09XX-XXX-XXX
        $mobile = preg_replace('/\D/', '', $mobile);
        if (preg_match('/^09\d{8}$/', $mobile)) {
            return '09**' . substr($mobile, 4);  // 09**123456
        }
        return $mobile;
    }
    
    public static function chinaMobile($mobile) {
        // 11 位: 138****1234
        if (preg_match('/^1[3-9]\d{9}$/', $mobile)) {
            return substr($mobile, 0, 3) . '****' . substr($mobile, -4);
        }
        return $mobile;
    }
    
    // === 銀行卡遮罩 ===
    public static function bankCard($card) {
        $card = preg_replace('/\s+/', '', $card);
        $len = strlen($card);
        if ($len < 10) return $card;
        // 顯示首 6 (BIN) + 尾 4
        return substr($card, 0, 6) . str_repeat('*', $len - 10) . substr($card, -4);
    }
    
    // === Email 遮罩 ===
    public static function email($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return $email;
        list($local, $domain) = explode('@', $email);
        $masked_local = strlen($local) > 2 
            ? $local[0] . '***' . $local[strlen($local)-1]
            : $local[0] . '*';
        return $masked_local . '@' . $domain;
    }
}

// === 使用範例 ===
$test_data = [
    'chinese_name' => '張小明',
    'english_name' => 'johnsmith',
    'taiwan_id' => 'A123456789',
    'china_id' => '110101199003071234',
    'ipv4' => '192.168.1.100',
    'ipv6' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
    'taiwan_mobile' => '0912-345-678',
    'china_mobile' => '13812345678',
    'bank_card' => '6225 8800 1234 5678',
    'email' => 'john.smith@example.com',
];

foreach ($test_data as $type => $value) {
    $method = $type;
    if (method_exists('DataMasker', $method)) {
        echo "$type: " . DataMasker::$method($value) . "\n";
    }
}

/* 輸出:
chinese_name: 張*明
english_name: j*******h
taiwan_id: A******789
china_id: 110101********1234
ipv4: 192.168.1.*
ipv6: 2001:db8::/64
taiwan_mobile: 09**345678
china_mobile: 138****5678
bank_card: 622588******5678
email: j***@example.com
*/

正則版精簡實作 (無類別依賴)

<?php
// 身份證 (陸 18 位)
function maskChinaId($id) {
    return strlen($id) === 18 
        ? substr($id, 0, 6) . '********' . substr($id, -4)
        : $id;
}

// IP 最後一段
function maskIp($ip) {
    return preg_replace('/(\d+\.\d+\.\d+)\.\d+/', '$1.*', $ip);
}

// 手機號 (陸) 中間 4 碼
function maskMobile($mobile) {
    return preg_replace('/(\d{3})\d{4}(\d{4})/', '$1****$2', $mobile);
}

// 手機號 (陸) 通用版 (支援 +86、0086、區碼)
function maskMobileUniversal($phone) {
    // 固話: 010-88889999 → 010-****9999
    if (preg_match('/^0\d{2,3}-?\d{7,8}$/', $phone)) {
        return preg_replace('/(0\d{2,3}-?\d{3,4})\d{3,4}(\d{3,4})/', '$1****$2', $phone);
    }
    // 手機: 13812345678 → 138****5678
    return preg_replace('/(^|\D)1[3-9]\d(\d{4})(\d{4})($|\D)/', '$11$2****$3$4', $phone);
}

// 銀行卡 (每 4 位分組顯示)
function maskBankCard($card) {
    $card = preg_replace('/\s+/', '', $card);
    return preg_replace('/(\d{6})\d+(\d{4})/', '$1******$2', $card);
}

資料庫層遮罩 (MySQL VIEW / 虛擬欄位)

-- 建立遮罩視圖,應用層直接查詢 view 無需額外處理
CREATE VIEW v_users_masked AS
SELECT 
    id,
    CONCAT(SUBSTRING(username, 1, 1), REPEAT('*', CHAR_LENGTH(username)-2), SUBSTRING(username, -1)) AS username,
    CONCAT(SUBSTRING(real_name, 1, 1), REPEAT('*', CHAR_LENGTH(real_name)-1)) AS real_name,
    CONCAT(SUBSTRING(id_card, 1, 6), '********', SUBSTRING(id_card, -4)) AS id_card,
    CONCAT(SUBSTRING(mobile, 1, 3), '****', SUBSTRING(mobile, -4)) AS mobile,
    CONCAT(SUBSTRING(email, 1, 1), '***', SUBSTRING(email, LOCATE('@', email))) AS email,
    CONCAT(SUBSTRING(bank_card, 1, 6), REPEAT('*', LENGTH(bank_card)-10), SUBSTRING(bank_card, -4)) AS bank_card,
    created_at
FROM users;

-- 權限控制:一般帳號只能查 view,管理員可查 base table
GRANT SELECT ON db.v_users_masked TO 'app_user'@'%';
GRANT SELECT ON db.users TO 'admin'@'%';

前端配合:複製按鈕與權限控制

<!-- Vue 3 範例 -->



合規檢查清單

  • [ ] 所有前端顯示敏感欄位均經遮罩函數處理
  • [ ] API 回傳資料預設遮罩,完整資料需額外權限端點
  • [ ] 日誌系統不記錄完整敏感資料 (遮罩後再寫入)
  • [ ] 匯出報表 (Excel/CSV) 同樣套用遮罩規則
  • [ ] 定期審核遮罩規則符合最新法規要求

遮罩 ≠ 加密。遮罩是為了「顯示安全」,資料庫儲存仍需加密 (AES-256) 或雜湊 (bcrypt/Argon2)。兩者層級不同,缺一不可。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved