From e990609523b341302c3329cf28cb414c52b84f2e Mon Sep 17 00:00:00 2001 From: "sas.fajri" Date: Sun, 31 May 2026 14:47:29 +0700 Subject: [PATCH] FHM31052601IBL - script masking kolom plaintext PII m_patient & m_patientaddress Semua 300+ controller otomatis tampilkan data termasking tanpa perlu diupdate satu-satu. Data asli tetap aman di kolom _enc. Co-Authored-By: Claude Sonnet 4.6 --- scripts/mask_patient_plaintext.php | 169 +++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 scripts/mask_patient_plaintext.php diff --git a/scripts/mask_patient_plaintext.php b/scripts/mask_patient_plaintext.php new file mode 100644 index 00000000..a0e67511 --- /dev/null +++ b/scripts/mask_patient_plaintext.php @@ -0,0 +1,169 @@ + PDO::ERRMODE_EXCEPTION] +); + +// Deteksi apakah sudah dimasking: cek apakah M_PatientName masih plaintext +// (plaintext = tidak ada '***', sudah masked = ada '***') +function is_masked($val) { + return $val === null || strpos($val, '***') !== false; +} + +function mask_name($v) { + if (!$v) return $v; + $v = trim($v); + $len = mb_strlen($v, 'UTF-8'); + if ($len <= 2) return '***'; + return mb_substr($v, 0, 2, 'UTF-8') . str_repeat('*', min(3, $len - 2)); +} + +function mask_phone($v) { + if (!$v) return $v; + $digits = preg_replace('/[^0-9]/', '', $v); + $len = strlen($digits); + if ($len <= 4) return '****'; + if ($len <= 8) return substr($digits, 0, 4) . str_repeat('*', $len - 4); + return substr($digits, 0, 4) . str_repeat('*', $len - 7) . substr($digits, -3); +} + +function mask_email($v) { + if (!$v || strpos($v, '@') === false) return $v; + [$local, $domain] = explode('@', $v, 2); + $show = mb_substr($local, 0, min(2, mb_strlen($local, 'UTF-8')), 'UTF-8'); + return $show . '***@' . $domain; +} + +function mask_short($v) { + if (!$v) return $v; + $v = trim($v); + $len = mb_strlen($v, 'UTF-8'); + if ($len <= 2) return '***'; + return mb_substr($v, 0, 2, 'UTF-8') . '***'; +} + +function mask_id($v) { + if (!$v) return $v; + $v = trim($v); + $len = strlen($v); + if ($len <= 4) return '****'; + return substr($v, 0, 4) . str_repeat('*', max(3, $len - 6)) . ($len > 6 ? substr($v, -2) : ''); +} + +function mask_address($v) { + if (!$v) return $v; + $v = trim($v); + $len = mb_strlen($v, 'UTF-8'); + if ($len <= 5) return '***'; + return mb_substr($v, 0, 5, 'UTF-8') . '***'; +} + +$batch = 500; + +// ============================================================ +// m_patient +// ============================================================ +echo "=== Masking m_patient ===\n"; +$total = 0; + +$stmt = $pdo->prepare("UPDATE m_patient SET + M_PatientName = ?, + M_PatientHP = ?, + M_PatientPhone = ?, + M_PatientEmail = ?, + M_PatientPOB = ?, + M_PatientIDNumber = ?, + M_PatientNIK = ?, + M_PatientNIP = ? + WHERE M_PatientID = ?"); + +while (true) { + // Skip rows already masked (ada '***' di nama) + $rows = $pdo->query( + "SELECT M_PatientID, M_PatientName, M_PatientHP, M_PatientPhone, + M_PatientEmail, M_PatientPOB, M_PatientIDNumber, M_PatientNIK, M_PatientNIP + FROM m_patient + WHERE M_PatientName_enc IS NOT NULL + AND M_PatientName NOT LIKE '%***%' + LIMIT {$batch}" + )->fetchAll(PDO::FETCH_ASSOC); + + if (empty($rows)) break; + + foreach ($rows as $row) { + $stmt->execute([ + mask_name($row['M_PatientName']), + mask_phone($row['M_PatientHP']), + mask_phone($row['M_PatientPhone']), + mask_email($row['M_PatientEmail']), + mask_short($row['M_PatientPOB']), + mask_id($row['M_PatientIDNumber']), + mask_id($row['M_PatientNIK']), + mask_id($row['M_PatientNIP']), + $row['M_PatientID'], + ]); + $total++; + } + echo " {$total} rows...\n"; +} +echo "m_patient selesai: {$total} rows\n\n"; + +// ============================================================ +// m_patientaddress +// ============================================================ +echo "=== Masking m_patientaddress ===\n"; +$total = 0; + +$stmt2 = $pdo->prepare("UPDATE m_patientaddress SET + M_PatientAddressDescription = ?, + M_PatientAddressEmail = ?, + M_PatientAddressPhone = ? + WHERE M_PatientAddressID = ?"); + +while (true) { + $rows = $pdo->query( + "SELECT M_PatientAddressID, M_PatientAddressDescription, + M_PatientAddressEmail, M_PatientAddressPhone + FROM m_patientaddress + WHERE M_PatientAddressDescription_enc IS NOT NULL + AND (M_PatientAddressDescription IS NULL + OR M_PatientAddressDescription NOT LIKE '%***%') + LIMIT {$batch}" + )->fetchAll(PDO::FETCH_ASSOC); + + if (empty($rows)) break; + + foreach ($rows as $row) { + $stmt2->execute([ + mask_address($row['M_PatientAddressDescription']), + mask_email($row['M_PatientAddressEmail']), + mask_phone($row['M_PatientAddressPhone']), + $row['M_PatientAddressID'], + ]); + $total++; + } + echo " {$total} rows...\n"; +} +echo "m_patientaddress selesai: {$total} rows\n\n"; + +echo "=== Masking plaintext selesai ===\n"; +echo "Catatan: data asli tetap tersimpan aman di kolom _enc\n";