Faker Python Series 8: Anonimisasi Data Asli secara Konsisten

Hai, teman-teman! Kali ini masalahnya kebalikan dari bagian-bagian sebelumnya. Kalau di bagian 7 kita mengisi database toko dengan data palsu dari nol, kali ini datanya sudah asli. Ada laporan penjualan yang angkanya aneh, dan tim yang memperbaikinya butuh salinan data pelanggan untuk mencari penyebabnya. Masalahnya, data itu berisi nama, email, dan nomor telepon orang sungguhan, dan tidak boleh keluar begitu saja.

Ide pertama yang terpikir: ganti saja semua nama dan email dengan data palsu dari Faker. Setelah diganti, laporannya dijalankan ulang:

Sebelum disamarkan, transaksi tersambung: 5000 dari 5000
Sesudah disamarkan, transaksi tersambung: 28 dari 5000

Datanya memang sudah tersamar, tapi 4.972 transaksi kehilangan pemiliknya, dan laporannya jadi tidak bisa dipakai sama sekali. Di bagian terakhir seri ini, kita belajar anonimisasi data dengan Python dan Faker yang konsisten: nilai asli yang sama selalu berubah menjadi nilai palsu yang sama, sehingga relasi antartabel tetap utuh.

Sampul Faker Python Series bagian 8: Anonimisasi Data Asli secara Konsisten, dengan logo Python di atas latar sirkuit biru tua
Faker Python Series bagian 8, bagian terakhir: menyamarkan data asli tanpa memutus relasi

TL;DR: Mengganti setiap sel dengan nilai acak baru memutus relasi antartabel. Kamus pengganti menjaga relasi, tapi hasilnya berubah setiap kali dijalankan. Cara yang konsisten adalah menurunkan seed Faker dari HMAC-SHA256 nilai asli dengan kunci rahasia, lalu memanggil fake.seed_instance(). Jangan pakai hash() bawaan Python, karena hasilnya berbeda di setiap proses. Hasilnya secara teknis adalah pseudonimisasi, bukan anonimisasi penuh, jadi tetap perlu diperlakukan sebagai data pribadi.

Kartu istilah Pseudonimisasi atau penyamaran identitas: Mengganti identitas di data dengan samaran, yang hanya bisa dikembalikan dengan kunci rahasia
Teknik di artikel ini termasuk pseudonimisasi: samaran masih bisa dikaitkan ke orangnya lewat kunci rahasia

Yang Sudah Kita Pelajari

Bagian terakhir ini memakai bekal dari tiga bagian sebelumnya:

  • Bagian 4: fake.unique untuk mencegah nilai kembar.
  • Bagian 6: seed_instance() untuk mengatur seed satu object Faker.
  • Bagian 7: mengisi database toko dengan dua tabel berelasi, pelanggan dan transaksi.

Tujuan Artikel Ini

Di sini kamu akan belajar:

  • Membedakan anonimisasi dan pseudonimisasi.
  • Melihat kenapa penggantian acak dan kamus pengganti belum cukup.
  • Membuat nilai samaran yang konsisten dengan seed dari HMAC-SHA256.
  • Menyamarkan file CSV dan memastikan relasinya tetap utuh.
  • Mengenali batasan teknik ini.

Kenalan Dulu: Anonimisasi atau Pseudonimisasi?

Anonimisasi berarti data diubah sampai orangnya tidak bisa dikenali lagi dengan cara apa pun. Pseudonimisasi berarti data identitas diganti dengan nilai lain, tapi masih bisa dikembalikan ke orangnya dengan bantuan informasi tambahan, misalnya kunci rahasia. Teknik di artikel ini termasuk pseudonimisasi.

Bedanya penting. Regulasi perlindungan data Uni Eropa, GDPR, mendefinisikan pseudonymisation di Pasal 4 angka 5 sebagai pemrosesan data pribadi sehingga data itu tidak bisa lagi dikaitkan ke orang tertentu tanpa informasi tambahan, dengan syarat informasi tambahan itu disimpan terpisah dan dilindungi.

Recital 26 GDPR lalu menegaskan bahwa data hasil pseudonimisasi tetap dianggap data pribadi. Hanya data yang benar-benar anonim, yang orangnya tidak bisa lagi dikenali, yang berada di luar aturan perlindungan data.

Di Indonesia, data pribadi diatur dalam Undang-Undang Nomor 27 Tahun 2022 tentang Pelindungan Data Pribadi. Menurut ringkasan di katalog peraturan BPK, data pribadi yang bersifat umum meliputi nama lengkap, jenis kelamin, kewarganegaraan, agama, status perkawinan, dan data pribadi yang dikombinasikan untuk mengidentifikasi seseorang.

Jadi file hasil artikel ini memang jauh lebih aman untuk dibagikan ke tim, tapi bukan berarti boleh disebar bebas. Artikel ini membahas tekniknya, bukan nasihat hukum. Untuk keputusan berbagi data di organisasimu, tanyakan ke bagian yang mengurus kepatuhan atau perlindungan data.

Data Contoh yang Dipakai

Supaya aman dicoba, "data asli" di artikel ini juga buatan Faker, dengan seed yang berbeda dari data samarannya. Ada dua file: pelanggan.csv berisi 1.000 pelanggan, dan transaksi.csv berisi 5.000 transaksi. Keduanya dihubungkan lewat email, seperti yang sering ditemukan di file ekspor:

{'id': 1, 'nama': 'Kezia Pangestu', 'email': 'jagaragaprayoga@gmail.com', 'telepon': '081468819736', 'kota': 'Surabaya'}
{'id': 1, 'email_pelanggan': 'bsafitri@gmail.com', 'nominal': 172000}

Kolom nama, email, dan telepon adalah identitas yang harus disamarkan. Kolom kota dan nominal dibiarkan, karena justru itu yang dibutuhkan untuk analisis.

Cara Naif: Ganti Setiap Sel dengan Nilai Acak

Cara paling sederhana adalah memanggil Faker untuk setiap sel:

for p in pelanggan:
    p["nama"] = fake.name()
    p["email"] = fake.free_email()
for t in transaksi:
    t["email_pelanggan"] = fake.free_email()

Hasilnya sudah kita lihat di pembuka: hanya 28 dari 5.000 transaksi yang masih tersambung ke pelanggan, dan 28 itu pun hanya kebetulan email acaknya sama. Email bsafitri@gmail.com di tabel pelanggan berubah menjadi satu email palsu, sedangkan email yang sama di tabel transaksi berubah menjadi email palsu yang lain. Faker tidak tahu bahwa keduanya sebenarnya orang yang sama.

Kamus Pengganti: Konsisten, Tapi Hanya Sekali Jalan

Supaya orang yang sama selalu mendapat samaran yang sama, Faker perlu "mengingat". Cara paling langsung adalah kamus: setiap email asli dicatat bersama samarannya, dan kalau email itu muncul lagi, samaran yang sama dipakai ulang.

def buat_penyamar():
    fake = Faker("id_ID")
    kamus = {}

    def samarkan_email(email):
        if email not in kamus:
            kamus[email] = fake.unique.free_email()
        return kamus[email]

    return samarkan_email, kamus

fake.unique dari bagian 4 dipakai supaya dua pelanggan berbeda tidak mendapat samaran yang sama. Hasilnya:

Transaksi tersambung: 5000 dari 5000
Isi kamus: 1000 pasangan

Relasinya utuh. Tapi coba lihat apa yang terjadi kalau penyamarnya dibuat ulang, seperti saat skrip dijalankan minggu depan untuk data baru:

jagaragaprayoga@gmail.com -> simanjuntakcindy@hotmail.com (jalan pertama)
jagaragaprayoga@gmail.com -> maulanaumay@hotmail.com (jalan kedua)

Pelanggan yang sama mendapat dua samaran yang berbeda. Data minggu ini dan minggu depan jadi tidak bisa digabung. Agar konsisten, kamusnya harus disimpan, dan di situ masalah baru muncul: kamus itu berisi pasangan email asli dan samarannya. Siapa pun yang memegang file kamus bisa membuka semua samaran.

Seed dari HMAC: Konsisten Tanpa Menyimpan Kamus

Ide yang lebih rapi: jangan simpan pasangan nilai asli dan samarannya, tapi turunkan seed Faker langsung dari nilai asli. Nilai asli yang sama selalu menghasilkan seed yang sama, dan Faker yang di-seed sama selalu menghasilkan nilai palsu yang sama. Kamusnya tidak perlu disimpan, karena hasilnya bisa dihitung ulang kapan saja.

Jebakan: hash() Bawaan Python Berubah Setiap Proses

Fungsi pertama yang terpikir untuk mengubah teks menjadi angka adalah hash(). Coba jalankan untuk email yang sama di dua proses Python terpisah:

hash() proses 1: 721925512769610246
hash() proses 2: 4248353738700463404

Hasilnya berbeda. Ini disengaja: sejak lama, Python mengacak hash() untuk teks di setiap proses demi keamanan, seperti dijelaskan di dokumentasi variabel PYTHONHASHSEED. Kalau kamu menjalankan skripnya, angkanya akan berbeda lagi. Jadi hash() tidak bisa dipakai sebagai dasar samaran yang konsisten.

Kenapa HMAC, Bukan SHA-256 Biasa?

SHA-256 dari modul hashlib selalu menghasilkan angka yang sama untuk teks yang sama. Tapi ada lubang. Orang yang punya daftar email kandidat bisa menghitung SHA-256 setiap email itu sendiri, lalu mencocokkannya dengan data samaran. Samarannya jadi bisa ditebak tanpa kunci apa pun.

HMAC menutup lubang itu dengan mencampurkan kunci rahasia ke dalam perhitungan. Tanpa kunci, hasil HMAC tidak bisa dihitung ulang. Kunci inilah "informasi tambahan" dalam definisi pseudonimisasi GDPR tadi, yang harus disimpan terpisah dari data samaran.

import hashlib
import hmac
import os

from faker import Faker

# kunci rahasia dibaca dari environment variable, jangan ditulis di kode
KUNCI = os.environ.get("KUNCI_SAMARAN", "contoh-kunci-jangan-dipakai").encode()

fake = Faker("id_ID")


def sidik(nilai, jenis):
    """HMAC-SHA256 dari nilai asli. `jenis` membuat nama dan email yang sama tetap beda hasil."""
    return hmac.new(KUNCI, f"{jenis}:{nilai}".encode(), hashlib.sha256).digest()


def pakai_seed_dari(nilai, jenis):
    fake.seed_instance(int.from_bytes(sidik(nilai, jenis)[:8], "big"))


def samarkan_nama(nama):
    pakai_seed_dari(nama, "nama")
    return fake.name()


def samarkan_email(email):
    pakai_seed_dari(email, "email")
    # 6 karakter sidik ditempel supaya dua email asli tidak jatuh ke email palsu yang sama
    return f"{fake.user_name()}.{sidik(email, 'email').hex()[:6]}@example.com"


def samarkan_telepon(telepon):
    pakai_seed_dari(telepon, "telepon")
    return fake.numerify("08##########")

Ada beberapa hal yang perlu diperhatikan:

  • fake.seed_instance(), bukan Faker.seed(). Faker.seed() mengatur pengacak yang dipakai bersama semua object Faker di program, sedangkan seed_instance() hanya mengatur object fake ini. Penjelasan seed ada di dokumentasi Faker bagian Seeding the Generator.
  • jenis ikut dihitung. Tanpa itu, teks yang sama di kolom berbeda akan mendapat seed yang sama.
  • 8 byte pertama HMAC diubah menjadi angka bulat untuk seed. Itu sudah lebih dari cukup untuk membedakan nilai.
  • Email samaran memakai domain example.com. Domain ini dicadangkan khusus untuk contoh dan dokumentasi oleh RFC 2606, jadi tidak akan pernah terkirim ke kotak masuk orang sungguhan kalau ada sistem yang tidak sengaja mengirim email.

Hasil dua kali pemanggilan untuk nilai yang sama:

R.A. Jamalia Agustina | raharjamandasari.a505f1@example.com
R.A. Jamalia Agustina | raharjamandasari.a505f1@example.com

Sama persis, dan akan tetap sama di proses berikutnya, minggu depan, atau di komputer lain, selama kuncinya sama.

Kenapa Email Butuh Akhiran Sidik

Seed yang berbeda tetap bisa menghasilkan nama pengguna yang sama, karena daftar nama di Faker terbatas. Ini masalah yang sama dengan email kembar di bagian 4. Kita hitung untuk 1.000 email pelanggan:

Tanpa akhiran: 990 email berbeda dari 1000
Dengan akhiran: 1000 email berbeda dari 1000

Tanpa akhiran, 10 pasang pelanggan berbagi email samaran, dan transaksi mereka akan tercampur. fake.unique tidak bisa menolong di sini, karena unique bergantung pada urutan pemanggilan, sedangkan kita butuh hasil yang hanya bergantung pada nilai asli. Akhiran 6 karakter heksadesimal dari HMAC memberi lebih dari 16 juta kombinasi tambahan, dan nilainya tetap konsisten.

Menyamarkan File CSV dan Memeriksa Hasilnya

Sekarang penyamar dipakai untuk kedua file. Kolom id, kota, dan nominal tidak disentuh:

for p in pelanggan:
    p["nama"] = samarkan_nama(p["nama"])
    p["email"] = samarkan_email(p["email"])
    p["telepon"] = samarkan_telepon(p["telepon"])
    # id dan kota dibiarkan, karena dipakai untuk analisis
for t in transaksi:
    t["email_pelanggan"] = samarkan_email(t["email_pelanggan"])

tulis("pelanggan_samaran.csv", pelanggan)
tulis("transaksi_samaran.csv", transaksi)

Menyamarkan tanpa memeriksa sama saja dengan berharap. Jadi hasilnya langsung diperiksa dari empat sisi:

{'id': '1', 'nama': 'Tgk. Prabowo Prakasa', 'email': 'waskitairwan.d61d5c@example.com', 'telepon': '086296319402', 'kota': 'Surabaya'}
Transaksi tersambung: 5000 dari 5000
Email samaran berbeda: 1000 dari 1000
Email asli yang tersisa: 0
Total nominal sama: True

Relasinya utuh, tidak ada email samaran yang kembar, tidak ada email asli yang tertinggal, dan angka yang dipakai untuk analisis tidak berubah. Laporan yang aneh tadi sekarang bisa diperiksa dengan data samaran, dan hasil analisisnya akan sama dengan data asli.

Batasan yang Perlu Diingat

Nama dan email yang diganti belum tentu membuat orangnya tidak bisa dikenali. Ada empat hal yang tetap perlu diwaspadai:

  • Kombinasi kolom yang tersisa. Kota, pola belanja, dan tanggal transaksi, kalau digabung, kadang cukup untuk menebak seseorang. Pelanggan satu-satunya di kota kecil yang selalu belanja besar setiap Jumat tetap mudah dikenali, walaupun namanya sudah diganti. Ini sebabnya ringkasan UU PDP menyebut "data pribadi yang dikombinasikan untuk mengidentifikasi seseorang".
  • Kunci rahasia adalah segalanya. Siapa pun yang memegang kunci dan daftar email kandidat bisa mencocokkan samarannya. Simpan kunci di tempat terpisah dari data samaran, jangan ditulis di kode, dan jangan ikut dikirim bersama file.
  • Kolom teks bebas. Kolom catatan atau alamat bisa saja berisi nama atau nomor telepon yang diketik manual. Penyamar per kolom seperti di atas tidak akan menemukannya.
  • Samaran bisa kebetulan sama dengan nama orang lain. Ini dibahas di tantangan nomor 3.

Error yang Sering Muncul Saat Menyamarkan Data dengan Faker

Kesalahan di penyamaran data jarang menampilkan error. Yang muncul justru relasi yang putus diam-diam, atau samaran yang berubah tanpa ada yang sadar.

Gejala Penyebab Perbaikan
Transaksi kehilangan pelanggannya setelah disamarkan Setiap sel diganti nilai acak baru Turunkan seed dari nilai asli, supaya nilai yang sama selalu mendapat samaran yang sama
Samaran berubah setiap kali skrip dijalankan Seed memakai hash() bawaan, atau penyamar memakai kamus yang tidak disimpan Pakai HMAC-SHA256 dengan kunci tetap
Dua pelanggan berbagi email samaran Nama pengguna dari seed berbeda kebetulan sama Tempel beberapa karakter HMAC ke email samaran
Samaran berbeda di komputer lain Nilai KUNCI_SAMARAN di environment berbeda, atau tidak diisi sehingga memakai kunci contoh Pastikan semua yang menjalankan skrip memakai kunci yang sama dari tempat penyimpanan rahasia
Data dummy lain di program ikut berubah urutannya Penyamar memakai Faker.seed() yang mengatur pengacak bersama Pakai fake.seed_instance() pada object Faker khusus penyamar

Tantangan untuk Kamu

Coba kerjakan dulu sebelum membuka jawabannya. Jalankan 1_buat_data_contoh.py dan 5_samarkan_csv.py dulu supaya semua file CSV tersedia.

Tantangan 1. Buktikan bahwa kunci yang berbeda menghasilkan samaran yang berbeda untuk email yang sama.

import importlib
import os

import penyamar

print("Kunci contoh :", penyamar.samarkan_email("dlestari@hotmail.com"))
os.environ["KUNCI_SAMARAN"] = "kunci-lain"
importlib.reload(penyamar)
print("Kunci lain   :", penyamar.samarkan_email("dlestari@hotmail.com"))
Kunci contoh : raharjamandasari.a505f1@example.com
Kunci lain   : fputra.768e42@example.com

KUNCI dibaca sekali saat modul diimpor, jadi modulnya perlu dimuat ulang dengan importlib.reload() setelah environment variable diubah. Ini juga menunjukkan kenapa kunci harus dijaga: dengan kunci lain, seluruh samaran berubah total.

Tantangan 2. Buktikan bahwa omzet per kota dari data samaran sama persis dengan data asli.

def omzet_per_kota(file_pelanggan, file_transaksi):
    kota_per_email = {p["email"]: p["kota"] for p in baca(file_pelanggan)}
    omzet = Counter()
    for t in baca(file_transaksi):
        omzet[kota_per_email[t["email_pelanggan"]]] += int(t["nominal"])
    return omzet


asli = omzet_per_kota("pelanggan.csv", "transaksi.csv")
samaran = omzet_per_kota("pelanggan_samaran.csv", "transaksi_samaran.csv")
print("Omzet per kota sama:", asli == samaran, dict(asli.most_common(2)))
Omzet per kota sama: True {'Jakarta': 320399000, 'Bandung': 150487500}

Fungsi yang sama dipakai untuk kedua pasang file. Kalau relasi email di data samaran putus, baris kota_per_email[...] akan langsung melempar KeyError.

Tantangan 3. Pastikan tidak ada nama, email, atau telepon asli yang tertinggal di file samaran.

asli_per_id = {p["id"]: p for p in baca("pelanggan.csv")}
bocor = [
    (s["id"], kolom)
    for s in baca("pelanggan_samaran.csv")
    for kolom in ("nama", "email", "telepon")
    if s[kolom] == asli_per_id[s["id"]][kolom]
]
print("Nilai asli yang tertinggal di barisnya sendiri:", len(bocor))

# kecocokan lintas baris: nilai samaran kebetulan sama dengan nilai asli milik orang lain
nilai_asli = {p[kolom]: p["id"] for p in asli_per_id.values() for kolom in ("nama", "email", "telepon")}
for s in baca("pelanggan_samaran.csv"):
    for kolom in ("nama", "email", "telepon"):
        if s[kolom] in nilai_asli:
            print(f"Kebetulan sama: {kolom} samaran id {s['id']} = {s[kolom]!r}, nilai asli milik id {nilai_asli[s[kolom]]}")
Nilai asli yang tertinggal di barisnya sendiri: 0
Kebetulan sama: nama samaran id 880 = 'Amelia Sihombing', nilai asli milik id 593

Versi pertama jawaban ini mencari nilai asli di semua sel tanpa melihat barisnya, dan menemukan 1 kecocokan. Setelah ditelusuri, itu bukan kebocoran. Nama samaran pelanggan 880 kebetulan sama dengan nama asli pelanggan 593, karena keduanya diambil dari daftar nama yang terbatas. Data pelanggan 593 sendiri tetap tersamar. Karena itu, pemeriksaan kebocoran yang benar membandingkan per baris, sedangkan kecocokan lintas baris cukup dicatat sebagai kebetulan.

Kode lengkap ketiga jawaban ada di 6_tantangan.py di GitHub.

Penutup Seri

Delapan bagian seri ini sebenarnya bergerak ke satu arah. Di awal, Faker dipakai untuk membuat data palsu sebanyak mungkin. Di akhir, data palsu itu harus mirip data asli: punya kolom yang unik, kolom yang kosong, pola yang berbobot, NIK yang lolos validasi, relasi yang utuh, sampai bisa menggantikan identitas di data asli tanpa merusak analisisnya.

Di setiap bagian, langkah terpenting selalu sama: periksa hasilnya dengan menghitung. Email kembar, name_male() yang separuhnya perempuan, toko yang ramai jam 3 pagi, dan nama samaran yang kebetulan sama dengan pelanggan lain. Semuanya baru ketahuan setelah datanya dihitung, bukan setelah dilihat sekilas.

Daftar semua artikel seri ini ada di label Faker Python. Kalau kamu pernah harus membagikan data pelanggan ke tim lain, kolom mana yang paling sulit disamarkan: nama, alamat, atau catatan teks bebas? Ceritakan di kolom komentar, tanpa menyebut data aslinya tentu saja.

Source code lengkap artikel ini bisa dilihat di GitHub: 08-anonimisasi-data-faker

FAQ

Apa beda anonimisasi dan pseudonimisasi data? Anonimisasi mengubah data sampai orangnya tidak bisa dikenali lagi dengan cara apa pun. Pseudonimisasi mengganti identitas dengan nilai lain yang masih bisa dikembalikan dengan informasi tambahan, misalnya kunci rahasia. Menurut Recital 26 GDPR, data hasil pseudonimisasi tetap dianggap data pribadi.

Bagaimana cara menyamarkan data dengan Faker tanpa memutus relasi antartabel? Turunkan seed Faker dari nilai asli, misalnya dengan HMAC-SHA256 dan kunci rahasia, lalu panggil fake.seed_instance() sebelum membuat nilai palsu. Nilai asli yang sama selalu menghasilkan samaran yang sama, jadi email yang sama di tabel pelanggan dan transaksi tetap tersambung.

Kenapa tidak memakai fungsi hash() bawaan Python untuk seed? Python mengacak hasil hash() untuk teks di setiap proses demi keamanan, jadi email yang sama menghasilkan angka yang berbeda setiap kali program dijalankan. Gunakan hmac atau hashlib yang hasilnya selalu sama untuk masukan yang sama.

Kenapa harus memakai kunci rahasia saat menyamarkan data? Tanpa kunci, siapa pun yang punya daftar email kandidat bisa menghitung hash-nya sendiri dan mencocokkannya dengan data samaran. Dengan HMAC dan kunci rahasia, hasilnya tidak bisa dihitung ulang tanpa kunci. Simpan kunci terpisah dari data samaran, dan jangan menuliskannya di kode.

Apakah data yang sudah disamarkan dengan Faker boleh dibagikan bebas? Tidak otomatis. Kolom yang tersisa, seperti kota dan pola belanja, kadang masih bisa dipakai untuk mengenali seseorang kalau digabung, dan data hasil pseudonimisasi tetap tergolong data pribadi. Pastikan pembagiannya sesuai kebijakan perlindungan data di organisasimu.

Referensi

Posting Komentar untuk "Faker Python Series 8: Anonimisasi Data Asli secara Konsisten"