Enkripsi Data Pribadi Usermu

Wed. Aug 26th, 2026 10:59 PM6 mins read
Enkripsi Data Pribadi Usermu
Source: Bing Image Creator - PII encryption

Data pribadi user dikenal dengan istilah PII (Personal Identifiable Information). Ini merupakan kumpulan daftar data yang diwajibkan atau dianjurkan untuk dienkripsi saat disimpan di dalam system🔒. PII terbagi dua, ada PII yang sensitif, yaitu data pribadi yang wajib dienkripsi. Ada juga PII yang non-sensitif, yaitu data pribadi yang ga wajib dienkripsi, tapi dianjurkan untuk ikut dienkripsi jika diperlukan. Daftar PII yang sensitif diantaranya:

  • NIK;
  • Dokumen-dokumen dari pemerintahan seperti KTP, KK, SIM, Ijazah, Paspor, dan sejenisnya;
  • Data biometrik seperti sidik jari atau retinal scan;
  • Data finansial seperti Nomor Kartu Kredit, Nomor Rekening, dan sejenisnya;
  • Rekam Medis;

Sementara daftar PII yang bukan sensitif diantaranya:

  • Nama Lengkap;
  • Nama Ibu;
  • Nomor Telepon;
  • IP Address;
  • Tempat Lahir;
  • Tanggal Lahir;
  • Alamat Lengkap;
  • Data Kepegawaian;
  • Email;
  • Ras/Suku;
  • Agama;

Meskipun data PII yang bukan sensitif ga diwajibkan dienkripsi, tapi sebaiknya ini juga ikut dienkripsi untuk mencegah tindak kiriminal. Terutama pada data seperti Nomor Telepon Pribadi, Email Pribadi, Alamat Lengkap, Nama Ibu, dan Tanggal Lahir. Jaman sekarang mulai banyak kasus penipuan, doxing, teror, hingga spam telepon dari nomor yang ga dikenal. Sindikat ini biasanya punya data kita dari system yang jebol yang ga mengenkripsi data pribadi user🫣. Sebagai orang yang membangun system, ini merupakan tanggung jawab kita untuk memberikan rasa aman pada user yang menggunakan system kita.

Encrypt Data

Untuk mengenkripsi data kita perlu simpan Secret Key yang aman🔑. Jika memungkinkan bisa gunakan Vault atau gunakan Secret Manager lainnya yang aman. Jangan disimpan di source code atau di file mentah seperti file config. Tapi sebagai contoh, kali ini gw bikin hardcode aja😁.

Encrypt Data
Copy
public class CryptoService{
	private static final String TRANSFORMATION = "AES/GCM/NoPadding";
	private static final String KEY_ALGORITHM = "AES";
	private static final int NONCE_LENGTH = 12;
	private static final int TAG_LENGTH_BITS = 128;
	private static final SecureRandom RANDOM = new SecureRandom();

	private static byte[] getSecret(){
		String secretStr = "1feBurhI/fmcAIP007stMsxU/0pjtOx7FTugrrjhIYw";
		return Base64.getDecoder().decode(secretStr);
	}

	public String encrypt(String plaintext){
		if(plaintext == null || plaintext.isBlank()){
			return plaintext;
		}
		byte[] nonce = new byte[NONCE_LENGTH];
		RANDOM.nextBytes(nonce);
		byte[] secret = getSecret();
		try{
			Cipher cipher = Cipher.getInstance(TRANSFORMATION);
			SecretKeySpec key = new SecretKeySpec(secret, KEY_ALGORITHM);
			cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_LENGTH_BITS, nonce));
			byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
			byte[] wire = new byte[nonce.length + encrypted.length];
			System.arraycopy(nonce, 0, wire, 0, nonce.length);
			System.arraycopy(encrypted, 0, wire, nonce.length, encrypted.length);
			return Base64.getUrlEncoder().withoutPadding().encodeToString(wire);
		}catch(GeneralSecurityException e){
			throw new RuntimeException("Failed to encrypt value", e);
		}
	}

	public String decrypt(String ciphertext){
		if(ciphertext == null || ciphertext.isBlank()){
			return ciphertext;
		}
		byte[] key = getSecret();
		try{
			byte[] wire = Base64.getUrlDecoder().decode(ciphertext);
			Cipher cipher = Cipher.getInstance(TRANSFORMATION);
			cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, KEY_ALGORITHM),
					new GCMParameterSpec(TAG_LENGTH_BITS, wire, 0, NONCE_LENGTH));
			byte[] decrypted = cipher.doFinal(wire, NONCE_LENGTH, wire.length - NONCE_LENGTH);
			return new String(decrypted, StandardCharsets.UTF_8);
		}catch(GeneralSecurityException | IllegalArgumentException e){
			throw new RuntimeException("Failed to decrypt value", e);
		}
	}

	public static void main(){
		CryptoService cryptoService = new CryptoService();
		String encrypted = cryptoService.encrypt("0823123456789");
		System.out.println(encrypted);
		System.out.println(cryptoService.decrypt(encrypted));
	}

}

Pada code di atas kita juga menambahkan nonce berukuran 12 bytes. Nonce adalah byte acak yang ditambahkan pada value yang dienkripsi. Fungsinya agar hasil enkripsinya ga bisa ditebak. Ini semacam salt pada hash password🧂. Kita menggunakan algoritma AES/GCM/NoPadding untuk enkripsi. Ini adalah kriptografi dua arah, jadi datanya bisa dibaca lagi lewat dekripsi. Agar bisa didekripsi, nonce tersebut juga harus ditambahkan sebagai prefix pada output.

Swap Data Problem

Setelah dienkripsi data pribadi user jadi aman. Selama Secret Key di production ga bocor, ga ada yang bisa melakukan dekripsi🔐. Meskipun suatu saat DB bocor, data pribadi user ga akan bisa dibaca⛔. Satu masalah yang muncul adalah data tersebut tetap bisa dibaca oleh admin DB👀. Tanpa Secret Key mereka bisa mengakalinya🧠. Misalnya mereka ingin mengetahui nomor hp pribadi salah satu idolanya yang terdaftar di DB yang terenkripsi. Mereka tinggal bikin akun dummy lalu lakukan sql update terhadap nomor hp di akun dummy dengan nomor hp idolanya🤓.

Swap Data
Copy
UPDATE user_phone
SET phone_no = (SELECT phone_no FROM user_phone WHERE id = 'idol')
WHERE
    id = 'dummy_account'
;

Data enkripsi nomor hp di akun palsunya sekarang sama dengan data enkripsi nomor hp idolanya. Setelah itu mereka tinggal cek nomor hp akun dummy di halaman kontak. Nomor hp sang idola akan terbaca di sana🔓. Swap Data seperti ini juga bisa dilakukan antar tabel karena pengamannya Secret Key doang sehingga semua data bisa didekripsi pakai algoritma yang sama. Admin DB tinggal menukar data untuk membacanya.

Encrypt With AAD

Untuk mengatasi masalah di atas, kita perlu tambahkan AAD (Additional Authentication Data), yaitu data tambahan untuk mengunci data🗝️. Kita bisa menggunakan Primary Key dari masing-masing tabel sebagai AAD. Jadi nantinya nomor hp sang idola hanya bisa dibaca oleh id user mereka sendiri. Begitu juga dengan akun dummy, sekarang jadi cuma bisa membaca nomor hp dengan id mereka sendiri.

Encrypt With AAD
Copy
public String encrypt(String plaintext, String aad){
	if(plaintext == null || plaintext.isBlank()){
		return plaintext;
	}
	byte[] nonce = new byte[NONCE_LENGTH];
	RANDOM.nextBytes(nonce);
	byte[] secret = getSecret();
	try{
		Cipher cipher = Cipher.getInstance(TRANSFORMATION);
		SecretKeySpec key = new SecretKeySpec(secret, KEY_ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(TAG_LENGTH_BITS, nonce));
		cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8));
		byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
		byte[] wire = new byte[nonce.length + encrypted.length];
		System.arraycopy(nonce, 0, wire, 0, nonce.length);
		System.arraycopy(encrypted, 0, wire, nonce.length, encrypted.length);
		return Base64.getUrlEncoder().withoutPadding().encodeToString(wire);
	}catch(GeneralSecurityException e){
		throw new RuntimeException("Failed to encrypt value", e);
	}
}

public String decrypt(String ciphertext, String aad){
	if(ciphertext == null || ciphertext.isBlank()){
		return ciphertext;
	}
	byte[] key = getSecret();
	try{
		byte[] wire = Base64.getUrlDecoder().decode(ciphertext);
		Cipher cipher = Cipher.getInstance(TRANSFORMATION);
		cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, KEY_ALGORITHM),
				new GCMParameterSpec(TAG_LENGTH_BITS, wire, 0, NONCE_LENGTH));
		cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8));
		byte[] decrypted = cipher.doFinal(wire, NONCE_LENGTH, wire.length - NONCE_LENGTH);
		return new String(decrypted, StandardCharsets.UTF_8);
	}catch(GeneralSecurityException | IllegalArgumentException e){
		throw new RuntimeException("Failed to decrypt value", e);
	}
}

public static void main(){
	CryptoService cryptoService = new CryptoService();
	User user = getUser();
	String phoneNoAad = "user_phone.phone_no." + user.getId();
	String encrypted = cryptoService.encrypt(user.getPhoneNo(), phoneNoAad);
	System.out.println(encrypted);
	System.out.println(cryptoService.decrypt(encrypted, phoneNoAad));
}

Kita perlu tambahkan satu parameter lagi untuk value AAD. Lalu tambahkan updateAAD() menggunakan bytes dari data AAD. Agar lebih aman dari swap antar tabel kita bisa buat format AAD dengan menyertakan nama tabel, nama kolom, dan id dari data tersebut. Sekarang data nomor hp hanya bisa didekripsi lewat AAD dari tabel user_phone, kolom phone_no, dan id dari user yang sama. Admin DB sekarang udah ga bisa lagi memanfaatkan celah sebelumnya karena id akun dia udah pasti beda dengan id akun idolanya sehingga akan gagal saat didekripsi😎.

Search Encrypted Data

Masalah selanjutnya adalah user jadi ga bisa melakukan pencarian berdasarkan data pribadi🔍. Datanya udah dienkripsi, jadi ga bisa dibaca seperti sebelumnya. Menurut gw sebisa mungkin jangan ada fitur pencarian menggunakan data pribadi jika ga perlu-perlu amat, apalagi secara publik. Data pribadi memang seharusnya private untuk kebutuhan yang benar-benar penting🔏. Tapi memang ada beberapa kasus yang memang butuh pencarian ke database menggunakan data pribadi user, misalnya fitur login menggunakan nomor hp. Untuk hal ini kita butuh satu kolom tambahan, yaitu phone_hash yang berisi hash dari nomor hp. Untuk mempercepat pencarian, kolom ini perlu di-index. Berbeda dengan enkripsi, hash ini kriptografi satu arah, jadi udah pasti ga akan bisa didekripsi. User juga ga perlu takut data pribadi ikut bocor saat DB bocor.

Hash Index
Copy
private static final String HMAC_ALGORITHM = "HmacSHA256";

public String hash(String value){
	if(value == null || value.isBlank()){
		return value;
	}
	byte[] key = getSecret();
	try{
		Mac mac = Mac.getInstance(HMAC_ALGORITHM);
		mac.init(new SecretKeySpec(key, HMAC_ALGORITHM));
		byte[] hash = mac.doFinal(value.getBytes(StandardCharsets.UTF_8));
		return HexFormat.of().formatHex(hash);
	}catch(GeneralSecurityException e){
		throw new RuntimeException("Failed to compute hash index", e);
	}
}

public static void main(){
	CryptoService cryptoService = new CryptoService();
	User user = getUser();
	System.out.println(cryptoService.hash(user.getPhoneNo()));
}

Untuk hash seperti ini kita menggunakan algoritma HmacSHA256. Dengan begini saat melakukan pencarian maka kita perlu hash dulu inputnya sebelum dilakukan pencarian ke database. Pencarian hanya bisa dilakukan menggunakan operator = karena data disimpan dalam bentuk hash. Kita ga bisa menggunakan operator LIKE atau menggunakan range. Ini trade off yang harus dimaklumi😐.

Verdict

Untuk melindungi data pribadi user kita harus menjaga data mereka dengan melakukan enkripsi pada data pribadi mereka. Terutama pada data di mana mereka bisa dijadikan target kriminal atau doxing. Jangan sampai itu terjadi. Enkripsi saja ga cukup karena admin DB tetap bisa mengintipnya. Tambahkan AAD biar keamanannya berlapis. Kekurangannya adalah pencarian jadi ga semudah sebelumnya. Kita perlu tambahkan kolom baru berisi hash dari data tersebut untuk fitur pencarian🔎. Ini hanya berlaku pada kolom yang memang dibutuhkan pencarian, kalau ga perlu-perlu banget menurut gw ga usah ada fitur pencarian sekalian biar ga usah nambah kolom.

© 2026 · Ferry Suhandri