Kodeloka
  1. Kodeloka
  2. Kelas Keamanan Aplikasi
  3. Materi

4 · Rahasia & Data · Modul 6 dari 8

Pengelolaan Rahasia & Enkripsi

Menyimpan kunci dan kredensial tanpa menaruhnya di repositori, dan mengenkripsi data dengan benar.

Rahasia Tidak Pernah Masuk Repositori

# .gitignore — perlu, tapi jauh dari cukup
.env
*.pem
*.key
🚫 Menghapus rahasia dengan commit baru tidak menghapusnya

Riwayat Git menyimpan segalanya. Kunci API yang pernah ter-commit tetap dapat diambil dari commit lama, dari fork, dari klon lokal siapa pun, dan dari layanan yang mengindeks repositori publik dalam hitungan menit. Satu-satunya tindakan yang benar adalah mencabut dan mengganti rahasia itu. Membersihkan riwayat berguna sebagai kebersihan, tetapi tidak pernah menjadi pengganti rotasi.

gitleaks detect --source . --verbose
trufflehog git file://. --only-verified

Urutan Pilihan Penyimpanan

1. Penyimpanan rahasia terkelola   Vault, AWS Secrets Manager, GCP Secret Manager
2. Rahasia platform                 Kubernetes Secret + KMS, Docker secret
3. Variabel lingkungan              dari sistem CI, bukan dari berkas ter-commit
4. Berkas terenkripsi di repo       SOPS + age/KMS — masih dapat diterima
5. Teks apa adanya di repo          tidak pernah
⚠️ Kubernetes Secret hanya dikodekan base64, bukan dienkripsi

Base64 sekadar pengodean — siapa pun yang dapat membaca objek Secret dapat membacanya kembali seketika. Tanpa mengaktifkan enkripsi di lapisan penyimpanan, isinya juga tersimpan apa adanya di etcd. Aktifkan EncryptionConfiguration dengan penyedia KMS, dan batasi hak get secrets lewat RBAC.

kubectl get secret db -o jsonpath='{.data.sandi}' | base64 -d

Enkripsi Data Diam

import { randomBytes, createCipheriv, createDecipheriv } from "crypto";

function enkripsi(teks, kunci) {
    const iv = randomBytes(12);                       // BARU setiap kali
    const c  = createCipheriv("aes-256-gcm", kunci, iv);
    const data = Buffer.concat([c.update(teks, "utf8"), c.final()]);
    const tag  = c.getAuthTag();
    return Buffer.concat([iv, tag, data]).toString("base64");
}
🚫 Menggunakan ulang IV pada GCM menghancurkan keamanannya

Ini bukan pelemahan bertahap melainkan kegagalan total. Dua pesan yang dienkripsi dengan pasangan kunci dan IV yang sama memungkinkan penyerang memulihkan hasil XOR kedua teks asli, dan pada mode GCM juga membuka jalan memalsukan tanda autentikasi. IV harus dihasilkan acak untuk setiap operasi enkripsi — tidak pernah dari nilai tetap, penghitung yang di-reset, atau konstanta di kode.

Pilih Mode Terautentikasi

AES-256-GCM        enkripsi + autentikasi   ← pilih ini
ChaCha20-Poly1305  alternatif setara, cepat tanpa akselerasi perangkat keras
AES-256-CBC        tanpa autentikasi         ← perlu HMAC terpisah
ECB                jangan pernah             ← pola data tetap terlihat

Tanpa autentikasi, penyerang dapat mengubah teks tersandi dan dekripsi tetap berjalan menghasilkan data yang berubah. Mode GCM menolak data yang telah diubah sejak awal.

Rotasi Kunci

{
  "kunci_aktif": "v3",
  "kunci": {
    "v1": "...",     // masih dipakai untuk dekripsi data lama
    "v2": "...",
    "v3": "..."      // dipakai untuk enkripsi baru
  }
}

Simpan pengenal versi kunci bersama setiap data terenkripsi. Tanpa itu, rotasi menuntut enkripsi ulang seluruh data secara serentak — yang di lapangan berarti rotasi tidak pernah dilakukan sama sekali.

Rahasia dalam Log dan Pesan Galat

const REDAKSI = ["sandi", "token", "otorisasi", "kartu", "rahasia", "kunci"];

function bersihkan(obj) {
    const salinan = { ...obj };
    for (const k of Object.keys(salinan)) {
        if (REDAKSI.some(r => k.toLowerCase().includes(r))) salinan[k] = "[disunting]";
    }
    return salinan;
}

log.info({ permintaan: bersihkan(req.body) }, "permintaan masuk");
⚠️ Log terkirim ke tempat dengan kendali akses berbeda

Sandi yang tercatat di log berpindah ke sistem agregasi log, cadangan, dan sering kali ke layanan pihak ketiga — dengan daftar orang yang dapat mengaksesnya jauh lebih luas daripada basis data produksi. Menyunting field sensitif di titik pencatatan lebih andal daripada berharap tidak ada yang mencatatnya.

Kredensial pada Image Kontainer

# Rentan — rahasia tetap ada di lapisan image
COPY .env /app/.env
RUN echo "$TOKEN_NPM" > ~/.npmrc && npm ci && rm ~/.npmrc

# Aman — mount rahasia hanya selama build
RUN --mount=type=secret,id=npm \
    NPM_TOKEN=$(cat /run/secrets/npm) npm ci

Menghapus berkas pada RUN berikutnya tidak menghilangkannya — lapisan sebelumnya tetap tersimpan di image dan dapat dibaca dengan docker history atau dengan mengekstrak lapisan.