Files
BE_IBL/scripts/mask_patient_plaintext.php
sas.fajri 065e3ebb34 FHM31052601IBL - pdp masking & enkripsi patient di controller dan SP mcu
- mask_name nama satu kata: tampil 2 char + bintang sisanya
- masking + enkripsi insert/update m_patient di Registrationv3, ibl_registration/Patient, Patientv4, setupmcuoffline-ibl/Preregister, mcuoffline/Preregisterapp
- masking insert ke mcu_preregister_patients (PatientName, KTP, NIK, Email, Hp)
- search patient pakai bidx, decrypt setelah query di mcuoffline/Preregisterapp
- matching existing patient ganti LIKE ke bidx search
- SP sp_upsert_mcu_patient_by_preregister_id & sp_upsert_mcu_patient_by_mgm_mcuid JOIN m_patient ambil _enc, simpan ke one_lab_dashboard.mcu_patient
- ALTER mcu_patient.Mcu_PatientName dan Mcu_PatientDOB ke TEXT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 20:10:15 +07:00

185 lines
5.7 KiB
PHP

<?php
/**
* Masking kolom plaintext PII di m_patient dan m_patientaddress
* Data asli tetap aman di kolom _enc
* Jalankan via: php scripts/mask_patient_plaintext.php
* Aman dijalankan berulang: skip row yang sudah termasking
*/
define('BASEPATH', true);
foreach (file(__DIR__ . '/../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $l) {
if (strpos(trim($l), '#') === 0) continue;
[$k, $v] = array_map('trim', explode('=', $l, 2));
if ($k !== '') $_ENV[$k] = $v;
}
include __DIR__ . '/../application/config/database.php';
$cfg = $db['default'];
$pdo = new PDO(
"mysql:host={$cfg['hostname']};dbname={$cfg['database']};charset=utf8",
$cfg['username'],
$cfg['password'],
[PDO::ATTR_ERRMODE => 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);
$words = preg_split('/\s+/', $v);
if (count($words) === 1) {
$l = mb_strlen($v, 'UTF-8');
if ($l <= 2) return $v;
return mb_substr($v, 0, 2, 'UTF-8') . str_repeat('*', $l - 2);
}
$first = $words[0];
$rest = array_slice($words, 1);
$masked = array_map(function($w) {
if (!$w) return '';
$init = mb_substr($w, 0, 1, 'UTF-8');
return $init . str_repeat('*', max(3, mb_strlen($w, 'UTF-8') - 1));
}, $rest);
return $first . ' ' . implode(' ', $masked);
}
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;
$last_id = 0;
// ============================================================
// m_patient — HP, Email, Phone, POB, IDNumber, NIK, NIP
// Nama TIDAK dimasking di sini — pakai remask_patient_name.php
// (cursor-based, decrypt dari _enc, format "NAMA DEPAN I*****")
// ============================================================
echo "=== Masking m_patient (field selain nama) ===\n";
$total = 0;
$stmt = $pdo->prepare("UPDATE m_patient SET
M_PatientHP = ?,
M_PatientPhone = ?,
M_PatientEmail = ?,
M_PatientPOB = ?,
M_PatientIDNumber = ?,
M_PatientNIK = ?,
M_PatientNIP = ?
WHERE M_PatientID = ?");
while (true) {
$rows = $pdo->query(
"SELECT M_PatientID, 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_PatientID > {$last_id}
ORDER BY M_PatientID ASC
LIMIT {$batch}"
)->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)) break;
foreach ($rows as $row) {
$stmt->execute([
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'],
]);
$last_id = $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;
$last_id = 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_PatientAddressID > {$last_id}
ORDER BY M_PatientAddressID ASC
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'],
]);
$last_id = $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";