Dua insiden klasik sering lolos dari gerbang RBAC yang kita bangun pada chapter sebelumnya. Pertama, disk bekas cluster warehouse TokoKita dikirim keluar untuk perbaikan — siapa pun yang memegang disk tersebut dapat membaca berkas Parquet di dalamnya. Kedua, pipeline production menarik data pelanggan lewat jaringan kantor — pihak yang menyadap jaringan dapat membaca lalu lintas yang tidak dienkripsi. Encryption at rest menutup jalur pertama, encryption in transit menutup jalur kedua, dan keduanya memakai kunci yang harus dikelola dengan disiplin. Chapter ini membuktikan mekanismenya lewat pustaka cryptography: roundtrip Fernet, deteksi manipulasi ciphertext, dan rotasi kunci tanpa menghentikan pipeline.

Dua Jenis Enkripsi, Dua Ancaman Berbeda #

AspekEncryption at RestEncryption in Transit
Melindungi daripencurian disk, akses langsung ke berkaspenyadapan lalu lintas jaringan
Mekanisme umumAES pada berkas, objek, atau volumeTLS antara klien dan server
Kunci yang dipakaidata key per objek atau per volumesession key hasil handshake TLS
Contoh TokoKitaParquet berisi NIK di object storagekoneksi psycopg ke PostgreSQL dengan sslmode=verify-full

Perbedaan pentingnya: data at rest yang dienkripsi tetap berada di satu tempat, sedangkan data in transit melewati banyak perangkat perantara — router, load balancer, proxy. Encryption in transit karena itu wajib bahkan di dalam jaringan internal, bukan hanya saat menyeberang internet. Keduanya bukan pilihan salah satu: data yang dienkripsi saat disimpan tetap terbaca bila dikirim tanpa TLS, dan sebaliknya.

Di platform cloud, encryption at rest sering tersedia sebagai pengaturan bawaan: server-side encryption pada object storage, transparent data encryption pada database, dan disk terenkripsi pada virtual machine. Mengaktifkan lapisan tersebut murah dan tidak mengubah kode. Tugas kita adalah memastikan pengaturan tersebut menyala di setiap environment baru yang dibuat, bukan hanya di production yang diperiksa saat audit.

Fernet untuk Encryption at Rest #

Fernet dari pustaka cryptography menyatukan dua mekanisme: AES-128-CBC untuk merahasiakan isi dan HMAC-SHA256 untuk memastikan ciphertext tidak diubah — pola yang disebut authenticated encryption. Gabungan tersebut penting karena enkripsi tanpa autentikasi masih bisa diganggu: ciphertext yang diubah sedikit bisa didekripsi menjadi teks rusak tanpa terdeteksi. Kita pakai pesan berisi NIK Siti sebagai data yang dilindungi:

python
"""Demo ch4: Fernet encrypt/decrypt, deteksi manipulasi, rotasi MultiFernet."""
import base64
import hashlib

from cryptography.fernet import Fernet, InvalidToken, MultiFernet


# Kunci demo diturunkan secara deterministik agar latihan dapat direproduksi.
# Di production: Fernet.generate_key() menghasilkan kunci acak per environment.
def kunci_dari(label: str) -> bytes:
    return base64.urlsafe_b64encode(hashlib.sha256(label.encode()).digest())


kunci_2025 = kunci_dari("demo-fernet-tokokita-2025")
kunci_2026 = kunci_dari("demo-fernet-tokokita-2026")

# 1) Enkripsi data at rest: pesan rahasia disimpan sebagai token
f_2025 = Fernet(kunci_2025)
pesan = "NIK Siti: 3201234567890001"
token = f_2025.encrypt(pesan.encode())
print("token Fernet:", token[:6].decode(), "... (panjang", len(token), "karakter)")

# 2) Dekripsi roundtrip dengan kunci yang sama
print("hasil dekripsi:", f_2025.decrypt(token).decode())

# 3) Deteksi manipulasi: satu byte terakhir token diubah
token_rusak = token[:-1] + bytes([token[-1] ^ 1])
try:
    f_2025.decrypt(token_rusak)
except InvalidToken:
    print("InvalidToken: HMAC tidak cocok, data yang diubah ditolak keras")

# 4) Rotasi kunci: tulis dengan kunci 2026, baca dengan keduanya
rotator = MultiFernet([Fernet(kunci_2026), Fernet(kunci_2025)])
print("data lama tetap terbaca setelah rotasi:", rotator.decrypt(token).decode())
token_baru = rotator.encrypt(pesan.encode())
print("data baru ditulis dengan kunci 2026:", Fernet(kunci_2026).decrypt(token_baru).decode())
try:
    Fernet(kunci_2025).decrypt(token_baru)
except InvalidToken:
    print("kunci 2025 tidak dapat membaca data yang ditulis kunci 2026")

Output yang Dihasilkan:

text
token Fernet: gAAAAA ... (panjang 120 karakter)
hasil dekripsi: NIK Siti: 3201234567890001
InvalidToken: HMAC tidak cocok, data yang diubah ditolak keras
data lama tetap terbaca setelah rotasi: NIK Siti: 3201234567890001
data baru ditulis dengan kunci 2026: NIK Siti: 3201234567890001
kunci 2025 tidak dapat membaca data yang ditulis kunci 2026

Mari kita telaah baris-baris kode di atas:

  1. Baris 10-11 (kunci_dari): kunci demo diturunkan dari SHA-256 agar hasil latihan dapat direproduksi; production memakai Fernet.generate_key() yang acak, karena kunci yang bisa ditebak sama berbahayanya dengan tidak ada kunci.
  2. Baris 20 (encrypt): setiap pemanggilan menghasilkan token berbeda berkat IV acak, walau pesannya sama — token selalu berawalan gAAAAA karena byte versi dan stempel waktu berada paling depan.
  3. Baris 27-31 (manipulasi): satu byte terakhir diubah lewat operasi XOR; proses dekripsi menolak dengan InvalidToken karena HMAC yang dihitung ulang tidak lagi cocok — ciphertext yang diubah tidak pernah dipercaya.
  4. Baris 34-35 (MultiFernet): objek rotator memegang daftar kunci; penulisan memakai kunci pertama (2026), sedangkan pembacaan mencoba satu per satu sehingga data lama ber-kunci 2025 tetap hidup selama masa transisi.
  5. Baris 38-41: kebalikannya terbukti — kunci lama tidak dapat membaca tulisan kunci baru, menandakan data yang sudah dirotasi benar-benar berpindah perlindungan.

TLS untuk Encryption in Transit #

Saat pipeline membuka koneksi ke PostgreSQL, driver psycopg menerima parameter sslmode: require mengenkripsi lalu lintas, dan verify-full sekaligus memeriksa bahwa sertifikat server memang diterbitkan untuk host tersebut. Tanpa verifikasi sertifikat, pihak penyadap dapat menyusup di tengah dengan sertifikat palsu sambil tetap menampilkan koneksi “ber-TLS”. Praktik yang layak untuk data TokoKita: verify-full untuk semua koneksi database production, HTTPS untuk setiap API eksternal, dan TLS juga antar layanan internal — pola yang umum disebut mTLS (mutual TLS) karena kedua sisi saling memeriksa sertifikat.

TLS tidak mengubah cara pipeline ditulis — parameter sslmode atau awalan https:// sudah cukup — tetapi verifikasi sertifikat tetap harus diuji. Kesalahan yang sering terjadi adalah menonaktifkan verifikasi (misalnya sslmode=require tanpa CA terpasang, atau verify=False pada pustaka HTTP) saat sertifikat internal bermasalah, lalu lupa menyalakannya kembali. Konfigurasi semacam itu sebaiknya langsung ditolak saat code review karena meniadakan tujuan TLS itu sendiri.

Envelope Encryption untuk Skala Cloud #

Mengenkripsi setiap objek langsung dengan satu kunci utama menimbulkan masalah: rotasi kunci berarti mendekripsi lalu mengenkripsi ulang seluruh data. Envelope encryption memecahnya dengan dua lapis kunci — data encryption key (DEK) acak per objek mengenkripsi data, sedangkan kunci utama di KMS hanya mengekripsi DEK itu sendiri:

Rotasi kunci utama cukup membungkus ulang DEK — operasi kecil dalam hitungan byte — tanpa menyentuh data di object storage. Pola ini adalah dasar layanan seperti AWS KMS , dan Fernet yang kita pakai di atas adalah versi sederhananya: satu kunci menjaga banyak token. Detail lengkap format token Fernet terdokumentasi di dokumentasi cryptography .

Kadensi rotasi yang lazim di industri mengikuti pola yang sama dengan MultiFernet pada simulasi kita:

  • baca dengan daftar kunci baru dan lama, tulis hanya dengan kunci termutakhir;
  • kunci utama diganti setiap tiga sampai dua belas bulan;
  • data lama bermigrasi saat pipeline menulis ulang secara alami, atau saat jadwal re-enkripsi berjalan di luar jam sibuk.

Kita kini punya data yang tidak terbaca dari disk dan lalu lintas yang tidak bisa disadap. Pertanyaan berikutnya menyangkut kunci itu sendiri: di mana disimpan, bagaimana diganti secara berkala, dan bagaimana tiap environment memegang kunci berbeda — semuanya adalah bidang Chapter Secrets Management.