DEBI PRAHARADIKA
← Back to Blog Index
System Design2026-06-2510 min read

Membangun PII Encryption at Rest dengan Algoritma AES-256

Panduan praktis implementasi enkripsi PII (Personally Identifiable Information) menggunakan AES-256 pada stack React, PHP, dan PostgreSQL.

Di era keterbukaan data dan ancaman siber yang terus meningkat, melindungi Personally Identifiable Information (PII) bukan lagi sekadar pelengkap fitur, melainkan kewajiban hukum (seperti UU PDP di Indonesia atau GDPR). PII mencakup data apa pun yang dapat digunakan untuk mengidentifikasi individu secara spesifik seperti Nomor KTP (NIK), nomor telepon, alamat rumah, hingga data medis.

Salah satu lapis pertahanan terpenting adalah dengan mengimplementasikan Encryption at Rest. Artinya, data tidak hanya dienkripsi saat sedang dikirim melalui internet (HTTPS/TLS), tetapi juga dienkripsi ketika sedang "beristirahat" atau disimpan di dalam hard drive database.

Dalam artikel ini, kita akan membahas bagaimana membangun sistem Encryption at Rest untuk data PII menggunakan algoritma standar AES-256, menggabungkan backend PHP, database PostgreSQL, dan antarmuka frontend React.


Daftar Isi

  1. System Thinking & Arsitektur Enkripsi
  2. Persiapan Database (PostgreSQL)
  3. Backend Implementation (PHP & AES-256)
  4. Frontend Implementation (React + Tailwind)
  5. Kesimpulan & Best Practices

1. System Thinking & Arsitektur Enkripsi

Sebelum masuk ke kode, mari kita samakan pemahaman (System Thinking). Prinsip utama dari Encryption at Rest di tingkat aplikasi adalah: Database tidak boleh tahu cara mendekripsi data yang disimpannya.

Jika hacker berhasil meretas dan mengunduh seluruh isi PostgreSQL Anda (misalnya melalui injeksi SQL atau pencurian backup), mereka hanya akan melihat teks acak (ciphertext) yang tidak berguna. "Kunci" (Encryption Key) yang sesungguhnya disimpan di lokasi terpisah yang dijaga ketat oleh aplikasi backend (PHP).

Arsitektur PII AES-256

Alur Kerja (Workflow):

  1. Write (Simpan): User mengisi NIK di frontend (React). Data dikirim ke server (PHP). PHP menggunakan Master Key dan membuat IV (Initialization Vector) unik untuk mengenkripsi NIK menggunakan algoritma AES-256-CBC. Teks acak dan IV ini disimpan di PostgreSQL.
  2. Read (Baca): User membuka halaman profil. PHP mengambil teks acak dan IV dari PostgreSQL. Karena PHP memiliki Master Key, PHP mendekripsi teks tersebut kembali menjadi NIK asli dan mengirimkannya ke React.

[!CATATAN] Apa itu IV (Initialization Vector)? IV adalah sekumpulan karakter acak yang ditambahkan pada proses enkripsi agar hasil enkripsi untuk kata yang sama (misal "Fulan") akan menghasilkan ciphertext yang selalu berbeda. Ini sangat penting agar hacker tidak bisa menebak pola data. IV tidak bersifat rahasia dan harus disimpan berdampingan dengan data yang dienkripsi.


2. Persiapan Database (PostgreSQL)

Pada contoh ini, kita akan membuat sebuah tabel users sederhana. Karena hasil enkripsi AES-256 (jika di-encode ke base64) bisa cukup panjang, kita tidak akan menggunakan VARCHAR(50), melainkan TEXT.

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    full_name VARCHAR(100) NOT NULL, -- Nama biasanya tidak dienkripsi agar bisa disearch
    email VARCHAR(100) UNIQUE NOT NULL, -- Sama, sering dijadikan login identifier
    
    -- PII Data (Encrypted)
    national_id_encrypted TEXT, 
    national_id_iv VARCHAR(32), -- Panjang hex/base64 dari IV (16 bytes)
    
    phone_number_encrypted TEXT,
    phone_number_iv VARCHAR(32),
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Perhatikan bahwa untuk setiap field yang dienkripsi, kita menyediakan satu kolom khusus untuk menampung IV-nya.


3. Backend Implementation (PHP & AES-256)

Di sisi server, PHP akan bertindak sebagai "penjaga gerbang". Anda memerlukan Encryption Key sepanjang 256-bit (32 bytes). Kunci ini HARUS disimpan di file environment (.env) dan jangan pernah di-commit ke repositori Git.

3.1. Konfigurasi Key di .env

# Jalankan `openssl rand -base64 32` di terminal untuk membuat key ini
APP_ENCRYPTION_KEY="VjF2S3I4WHNlY3JldEtleVRoYXRJczMyQnl0ZXNM" 

3.2. Fungsi Enkripsi & Dekripsi

Berikut adalah layanan bantu (helper) sederhana di PHP menggunakan fungsi bawaan openssl_encrypt:

<?php

class PiiEncryptionService {
    private string $key;
    private string $cipherAlgo = 'aes-256-cbc';

    public function __construct() {
        // Ambil key dari environment variable
        $this->key = base64_decode($_ENV['APP_ENCRYPTION_KEY']);
        
        if (strlen($this->key) !== 32) {
            throw new Exception("Encryption key harus berukuran persis 32 bytes.");
        }
    }

    /**
     * Enkripsi data PII
     * Mengembalikan Array berisi data terenkripsi dan IV yang digunakan.
     */
    public function encrypt(string $plaintext): array {
        // 1. Generate IV acak (16 bytes untuk AES-CBC)
        $ivLength = openssl_cipher_iv_length($this->cipherAlgo);
        $iv = openssl_random_pseudo_bytes($ivLength);

        // 2. Lakukan enkripsi
        $ciphertext = openssl_encrypt(
            $plaintext, 
            $this->cipherAlgo, 
            $this->key, 
            OPENSSL_RAW_DATA, 
            $iv
        );

        // 3. Kembalikan ciphertext dan IV dalam format Base64 agar aman disimpan di DB (teks)
        return [
            'encrypted' => base64_encode($ciphertext),
            'iv' => base64_encode($iv)
        ];
    }

    /**
     * Dekripsi data PII
     */
    public function decrypt(string $base64Ciphertext, string $base64Iv): string|false {
        $ciphertext = base64_decode($base64Ciphertext);
        $iv = base64_decode($base64Iv);

        return openssl_decrypt(
            $ciphertext, 
            $this->cipherAlgo, 
            $this->key, 
            OPENSSL_RAW_DATA, 
            $iv
        );
    }
}

3.3. Contoh Penggunaan Saat Menyimpan Data

Saat API menerima request registrasi, Anda cukup memanggil service tersebut sebelum menyimpannya ke database via PDO/ORM:

$piiService = new PiiEncryptionService();

$nikData = $piiService->encrypt($_POST['national_id']);
$phoneData = $piiService->encrypt($_POST['phone_number']);

$stmt = $pdo->prepare("
    INSERT INTO users (full_name, email, national_id_encrypted, national_id_iv, phone_number_encrypted, phone_number_iv)
    VALUES (?, ?, ?, ?, ?, ?)
");

$stmt->execute([
    $_POST['full_name'],
    $_POST['email'],
    $nikData['encrypted'],
    $nikData['iv'],
    $phoneData['encrypted'],
    $phoneData['iv']
]);

4. Frontend Implementation (React + Tailwind)

Di sisi antarmuka, best practice keamanan modern (khususnya untuk dashboard admin atau portal layanan pelanggan) adalah tidak langsung menampilkan PII. Data PII harus disensor secara bawaan dan hanya dimunculkan jika admin secara eksplisit mengklik tombol "Reveal" atau "Lihat Data". Ini mencegah shoulder surfing (orang mengintip layar) dan membatasi eksposur data sensitif.

Berikut adalah contoh rancangan komponen UI menggunakan React dan Tailwind CSS untuk menangani hal tersebut.

UI System Design PII Dashboard

import React, { useState } from 'react';

// Tipe data representasi response dari API PHP (setelah didekripsi di backend)
interface UserData {
  id: string;
  fullName: string;
  email: string;
  nationalId: string; // Sudah didekripsi server, tapi kita sensor di client
}

export default function PiiDataRow({ user }: { user: UserData }) {
  const [isRevealed, setIsRevealed] = useState(false);

  // Fungsi untuk mensensor (masking) NIK, sisakan 4 digit terakhir
  const maskNationalId = (nik: string) => {
    if (!nik) return '***';
    return `•••• •••• •••• ${nik.slice(-4)}`;
  };

  return (
    <div className="grid grid-cols-4 gap-4 p-4 border-b border-gray-200 items-center bg-white font-sans text-sm">
      <div className="text-gray-900 font-medium">{user.fullName}</div>
      <div className="text-gray-500">{user.email}</div>
      
      <div className={`font-mono ${isRevealed ? 'text-black' : 'text-gray-400'}`}>
        {isRevealed ? user.nationalId : maskNationalId(user.nationalId)}
      </div>
      
      <div className="text-right">
        <button 
          onClick={() => setIsRevealed(!isRevealed)}
          className="px-4 py-2 text-xs font-semibold uppercase tracking-wider border border-gray-300 rounded hover:bg-gray-50 transition-colors"
        >
          {isRevealed ? 'Hide PII' : 'Reveal PII'}
        </button>
      </div>
    </div>
  );
}

Dalam skenario yang lebih aman (Zero-Trust), data PII bahkan tidak dikirim oleh server pada request awal. Tombol "Reveal PII" akan melakukan pemanggilan API tambahan (GET /users/{id}/pii) yang mungkin mensyaratkan autentikasi 2FA ulang sebelum server melakukan decrypt dan mengembalikan data tersebut.


5. Kesimpulan & Best Practices

Menerapkan AES-256 untuk Encryption at Rest adalah salah satu standar dalam melindungi integritas data pengguna. Namun, enkripsi hanyalah salah satu bagian dari arsitektur keamanan yang kokoh.

Berikut beberapa best practices tingkat lanjut yang wajib diperhatikan:

  1. Gunakan KMS (Key Management Service): Jangan simpan key sembarangan. Di lingkungan produksi enterprise, gunakan layanan seperti AWS KMS, Google Cloud KMS, atau HashiCorp Vault. PHP hanya meminta instruksi enkripsi/dekripsi ke KMS tersebut, sehingga key fisik tidak pernah menyentuh server code Anda.
  2. Key Rotation: Kunci enkripsi harus diubah (dirotasi) secara berkala (misal setiap 6 atau 12 bulan). Skema database yang baik harus menyertakan versi kunci (misal kolom encryption_key_version = 'v1') untuk mengetahui key mana yang digunakan saat mendekripsi baris data tersebut.
  3. Pencarian Terbatas (Blind Index): Karena data PII dienkripsi menggunakan IV acak, Anda tidak bisa mencari pengguna berdasarkan NIK menggunakan klausa WHERE national_id_encrypted = '...'. Jika searchability dibutuhkan, Anda harus membuat sebuah Blind Index (misalnya HMAC hash berkecepatan lambat dari NIK) dan menyimpannya di kolom terpisah yang digunakan eksklusif untuk indeks pencarian.

Dengan pendekatan terstruktur dan pemilihan alat yang tepat seperti di atas, aplikasi web Anda tidak hanya memiliki performa tinggi, tetapi juga menaati standar kepatuhan regulasi privasi global yang ketat.