TLS, enkripsi & rahasia
Kriptografi di Go tersedia lengkap di pustaka standar dan defaultnya sudah aman. Yang menghasilkan kerentanan bukan algoritmanya, melainkan memilih alat yang salah untuk pekerjaan yang salah.
Intisari
- Hash untuk yang tidak perlu dibaca lagi (sandi). Enkripsi untuk yang perlu dibaca (nomor rekening). Tanda tangan untuk yang perlu dibuktikan asalnya (webhook).
- Default TLS Go sudah aman. Jangan menyetel
CipherSuitessendiri kecuali kamu benar-benar tahu alasannya. - Jangan pernah
InsecureSkipVerify: truedi produksi โ itu membatalkan seluruh manfaat TLS. - Untuk enkripsi simetris: AES-GCM lewat
crypto/cipher, dengan nonce unik per pesan. - Simpan ID kunci bersama ciphertext supaya rotasi kunci bisa dilakukan tanpa mendekripsi ulang seluruh tabel.
Memilih alatnya
| Kebutuhan | Alat | Paket |
|---|---|---|
| Sandi | bcrypt / Argon2id | x/crypto |
| Data sensitif yang harus dibaca lagi | AES-GCM | crypto/aes, crypto/cipher |
| Membuktikan asal pesan | HMAC-SHA256 | crypto/hmac |
| Sidik jari berkas | SHA-256 | crypto/sha256 |
| Token, ID sesi, salt | Byte acak | crypto/rand |
| Kunci pengelolaan terpusat | AWS KMS | SDK AWS (Fase 10) |
Tiga hal yang sering tertukar: encoding (base64) bukan enkripsi โ siapa pun bisa membalikkannya. Hash tidak bisa dibalik, jadi jangan dipakai untuk data yang perlu ditampilkan lagi. Dan enkripsi tanpa autentikasi (mode CBC polos) bisa dimodifikasi penyerang tanpa ketahuan โ karena itu selalu mode AEAD seperti GCM.
Enkripsi yang benar
func Enkripsi(kunci, teks []byte) ([]byte, error) {
blok, err := aes.NewCipher(kunci) // kunci 32 byte = AES-256
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blok)
if err != nil {
return nil, err
}
// Nonce WAJIB unik per pesan untuk kunci yang sama. Memakai ulang
// nonce di GCM membocorkan plaintext โ ini bukan kelemahan teoretis.
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
// Nonce disimpan di depan ciphertext โ ia tidak rahasia.
return gcm.Seal(nonce, nonce, teks, nil), nil
}
func Dekripsi(kunci, data []byte) ([]byte, error) {
blok, err := aes.NewCipher(kunci)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blok)
if err != nil {
return nil, err
}
if len(data) < gcm.NonceSize() {
return nil, errors.New("ciphertext terlalu pendek")
}
nonce, isi := data[:gcm.NonceSize()], data[gcm.NonceSize():]
// Open sekaligus MEMVERIFIKASI: data yang diubah penyerang gagal di sini.
return gcm.Open(nil, nonce, isi, nil)
}
Rotasi kunci
// Simpan ID kunci bersama ciphertext. Tanpa ini, mengganti kunci berarti
// harus mendekripsi dan mengenkripsi ulang seluruh tabel dalam satu operasi.
type Terenkripsi struct {
KunciID string `json:"k"`
Data []byte `json:"d"`
}
type Kotak struct {
aktif string
kunci map[string][]byte // id โ kunci; yang lama tetap ada untuk dekripsi
}
func (k *Kotak) Enkripsi(teks []byte) (Terenkripsi, error) {
c, err := Enkripsi(k.kunci[k.aktif], teks)
return Terenkripsi{KunciID: k.aktif, Data: c}, err
}
func (k *Kotak) Dekripsi(t Terenkripsi) ([]byte, error) {
kunci, ada := k.kunci[t.KunciID]
if !ada {
return nil, fmt.Errorf("kunci %q tidak dikenal", t.KunciID)
}
return Dekripsi(kunci, t.Data)
}
Rotasi jadi operasi bertahap: tambahkan kunci baru sebagai aktif, biarkan yang
lama tetap terdaftar untuk dekripsi, lalu enkripsi ulang baris lama secara bertahap di latar. Tanpa ID
kunci, satu-satunya cara mengganti kunci adalah operasi besar yang berisiko โ dan karena itu tidak
pernah dilakukan.
TLS di sisi klien
// Default Go sudah aman: TLS 1.2+ dengan suite modern, verifikasi penuh.
klien := &http.Client{Timeout: 10 * time.Second}
// Kalau memang perlu memperketat:
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS13,
RootCAs: caKhusus, // mis. sertifikat CA RDS
}
// โ JANGAN PERNAH di produksi
&tls.Config{InsecureSkipVerify: true}
InsecureSkipVerify: true mematikan verifikasi sertifikat sepenuhnya โ siapa pun
yang bisa menyisip di jalur jaringan bisa membaca dan mengubah lalu lintasmu. Ia biasanya masuk sebagai
"solusi sementara" untuk sertifikat self-signed di staging, lalu ikut ke produksi. Solusi yang benar:
tambahkan CA-nya ke RootCAs. Linter gosec menandainya (G402).
Di mana TLS diterminasi
Browser โโTLSโโโบ CloudFront โโTLSโโโบ ALB โโHTTPโโโบ ECS task (aplikasi Go)
โโ di dalam VPC, jaringan privat
| Segmen | Perlu TLS? |
|---|---|
| Browser โ CloudFront | Wajib, dengan HSTS |
| CloudFront โ ALB | Wajib โ lalu lintas melewati internet |
| ALB โ task | Opsional di dalam VPC; wajib kalau ada kepatuhan yang menuntut |
| Task โ RDS | Wajib โ sslmode=require minimum |
| Task โ ElastiCache | Nyalakan enkripsi saat transit |
Rahasia: aturan yang tidak bisa ditawar
- Jangan pernah masuk git โ pasang pre-commit hook pemindai rahasia.
- Jangan pernah masuk image Docker โ lapisan image bertahan selamanya.
- Jangan pernah masuk log โ pakai tipe yang menyensor dirinya (Fase 5).
- Disuntikkan saat runtime dari Secrets Manager lewat
secretsECS. - Diputar berkala, dan aplikasinya harus tahan saat rotasi terjadi.
- Berbeda per lingkungan โ rahasia staging tidak pernah sama dengan produksi.
# Kalau rahasia terlanjur ter-commit, menghapusnya dari riwayat TIDAK CUKUP.
# Ia sudah ada di klon orang lain, di cache CI, dan mungkin sudah terindeks.
# Urutan yang benar: PUTAR RAHASIANYA DULU, baru bersihkan riwayat.
Latihan: enkripsi satu kolom sensitif dengan AES-GCM, simpan bersama KunciID-nya,
lalu tambahkan kunci kedua dan buktikan data lama masih bisa dibaca sementara data baru memakai kunci
baru. Terakhir, ubah satu byte di ciphertext dan buktikan gcm.Open menolaknya โ itu bagian
"autentikasi" dari AEAD yang bekerja.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.