โ† Semua pembelajaran / Go Nol โ†’ Enterprise
Fase 6 ยท Keamanan

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.

Sumber asli pkg.go.dev Resmi Rangkuman ~7 menit baca

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 CipherSuites sendiri kecuali kamu benar-benar tahu alasannya.
  • Jangan pernah InsecureSkipVerify: true di 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

KebutuhanAlatPaket
Sandibcrypt / Argon2idx/crypto
Data sensitif yang harus dibaca lagiAES-GCMcrypto/aes, crypto/cipher
Membuktikan asal pesanHMAC-SHA256crypto/hmac
Sidik jari berkasSHA-256crypto/sha256
Token, ID sesi, saltByte acakcrypto/rand
Kunci pengelolaan terpusatAWS KMSSDK 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
SegmenPerlu TLS?
Browser โ†’ CloudFrontWajib, dengan HSTS
CloudFront โ†’ ALBWajib โ€” lalu lintas melewati internet
ALB โ†’ taskOpsional di dalam VPC; wajib kalau ada kepatuhan yang menuntut
Task โ†’ RDSWajib โ€” sslmode=require minimum
Task โ†’ ElastiCacheNyalakan enkripsi saat transit

Rahasia: aturan yang tidak bisa ditawar

  1. Jangan pernah masuk git โ€” pasang pre-commit hook pemindai rahasia.
  2. Jangan pernah masuk image Docker โ€” lapisan image bertahan selamanya.
  3. Jangan pernah masuk log โ€” pakai tipe yang menyensor dirinya (Fase 5).
  4. Disuntikkan saat runtime dari Secrets Manager lewat secrets ECS.
  5. Diputar berkala, dan aplikasinya harus tahan saat rotasi terjadi.
  6. 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.