Crypto #

Kriptografi adalah fondasi keamanan aplikasi modern — verifikasi integritas data, hashing password, token yang tidak bisa diprediksi, dan tanda tangan digital semuanya bergantung pada primitif kriptografi yang diimplementasikan dengan benar. Go menyediakan ekosistem crypto yang lengkap dalam standard library: crypto/sha256 dan crypto/md5 untuk hashing, crypto/rand untuk random number yang aman secara kriptografis, crypto/hmac untuk message authentication, dan crypto/subtle untuk operasi yang aman dari timing attack. Yang membuat package crypto di Go istimewa adalah desainnya yang mendorong penggunaan yang benar — misalnya crypto/rand yang secara eksplisit berbeda dari math/rand yang tidak aman untuk kriptografi. Artikel ini membahas semua yang perlu diketahui untuk mengimplementasikan kriptografi yang benar di aplikasi Go produksi.

Gambaran Besar Package crypto #

flowchart TD
    Crypto["package crypto\n(dan sub-package)"] --> Hash["Hashing"]
    Crypto --> Rand["Random"]
    Crypto --> Auth["Autentikasi"]
    Crypto --> Sym["Enkripsi Simetris"]
    Crypto --> Asym["Enkripsi Asimetris"]

    Hash --> SHA256["crypto/sha256\nSHA-256, SHA-224"]
    Hash --> SHA512["crypto/sha512\nSHA-512, SHA-384"]
    Hash --> MD5["crypto/md5\nMD5 (JANGAN untuk keamanan)"]
    Hash --> SHA1["crypto/sha1\nSHA-1 (deprecated untuk keamanan)"]

    Rand --> CR["crypto/rand\nRandom aman kriptografis\npakai ini, bukan math/rand"]

    Auth --> HMAC["crypto/hmac\nHMAC untuk verifikasi integritas"]
    Auth --> BCrypt["golang.org/x/crypto/bcrypt\nhashing password"]
    Auth --> Subtle["crypto/subtle\nconstant-time comparison"]

    Sym --> AES["crypto/aes\nAES cipher"]
    Sym --> Cipher["crypto/cipher\nGCM, CBC, CTR mode"]

    Asym --> RSA["crypto/rsa\nRSA enkripsi & tanda tangan"]
    Asym --> ECDSA["crypto/ecdsa\nElliptic Curve DSA"]
    Asym --> ED["crypto/ed25519\nEdDSA (modern, cepat)"]

    style Crypto fill:#4f86c6,color:#fff
    style Hash fill:#e8f5e9
    style Rand fill:#e3f2fd
    style Auth fill:#fff3e0
    style Sym fill:#f3e5f5
    style Asym fill:#fce4ec

crypto/sha256 — Hashing yang Aman #

SHA-256 adalah fungsi hash kriptografis yang paling umum digunakan saat ini — menghasilkan digest 32 byte (256 bit) yang deterministik dan tidak bisa dibalik. Digunakan untuk verifikasi integritas file, fingerprint data, dan sebagai komponen dalam sistem kriptografi yang lebih besar.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "os"
)

func main() {
    // Cara 1: hash string pendek sekaligus
    data := []byte("Halo, Dunia!")
    hash := sha256.Sum256(data)
    // hash adalah [32]byte — array, bukan slice
    fmt.Printf("SHA-256: %x\n", hash)
    // SHA-256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

    // Konversi ke hex string
    hashHex := hex.EncodeToString(hash[:])
    fmt.Println(hashHex)

    // Cara 2: hash data besar secara streaming (hemat memori)
    f, err := os.Open("file-besar.bin")
    if err == nil {
        defer f.Close()

        h := sha256.New() // buat hasher baru
        if _, err := io.Copy(h, f); err != nil {
            fmt.Println("error hashing file:", err)
        }
        hashFile := h.Sum(nil) // Sum(nil) mengembalikan hash sebagai []byte
        fmt.Printf("Hash file: %x\n", hashFile)
    }

    // Cara 3: hash incremental — tambahkan data sedikit demi sedikit
    h := sha256.New()
    h.Write([]byte("bagian pertama"))
    h.Write([]byte(" "))
    h.Write([]byte("bagian kedua"))
    hasil := h.Sum(nil)
    fmt.Printf("Hash incremental: %x\n", hasil)

    // Hasilnya sama dengan:
    gabungan := sha256.Sum256([]byte("bagian pertama bagian kedua"))
    fmt.Printf("Hash gabungan:    %x\n", gabungan)
    fmt.Println("Sama?", hasil != nil &&
        hex.EncodeToString(hasil) == hex.EncodeToString(gabungan[:]))
}

SHA-224, SHA-384, SHA-512 #

import (
    "crypto/sha256"
    "crypto/sha512"
)

// SHA-224 (output 28 byte)
hash224 := sha256.New224()
hash224.Write([]byte("data"))
fmt.Printf("SHA-224: %x\n", hash224.Sum(nil))

// SHA-384 (output 48 byte)
hash384 := sha512.New384()
hash384.Write([]byte("data"))
fmt.Printf("SHA-384: %x\n", hash384.Sum(nil))

// SHA-512 (output 64 byte)
hash512 := sha512.New()
hash512.Write([]byte("data"))
fmt.Printf("SHA-512: %x\n", hash512.Sum(nil))

// SHA-512/256 — SHA-512 dipotong ke 256 bit (lebih cepat di 64-bit CPU)
hash512_256 := sha512.New512_256()
hash512_256.Write([]byte("data"))
fmt.Printf("SHA-512/256: %x\n", hash512_256.Sum(nil))

crypto/md5 — Hanya untuk Non-Keamanan #

MD5 menghasilkan hash 16 byte (128 bit). Ia tidak aman untuk kriptografi karena rentan terhadap collision attack — dua input berbeda bisa menghasilkan hash yang sama. Gunakan MD5 hanya untuk checksum non-keamanan seperti deteksi perubahan file atau cache key:

flowchart LR
    subgraph Aman["✓ Gunakan SHA-256"]
        S1["Verifikasi integritas\ndata penting"]
        S2["Fingerprint untuk\nkeamanan"]
        S3["Komponen dalam\nsistem kriptografi"]
        S4["Token dan ID\nyang harus unik dan aman"]
    end

    subgraph TidakAman["⚠ MD5 — hanya untuk ini"]
        T1["Cache key\n(bukan untuk keamanan)"]
        T2["ETag HTTP\n(identifikasi resource)"]
        T3["Checksum sederhana\n(bukan untuk keamanan)"]
        T4["Deduplication ID\n(bukan untuk keamanan)"]
    end

    subgraph Jangan["✗ JANGAN gunakan MD5 untuk"]
        J1["Hashing password"]
        J2["Verifikasi identitas"]
        J3["Tanda tangan digital"]
        J4["Token autentikasi"]
    end

    style Aman fill:#e8f5e9
    style TidakAman fill:#fff3e0
    style Jangan fill:#fce4ec
import "crypto/md5"

// MD5 untuk cache key — bukan untuk keamanan!
func buatCacheKey(data []byte) string {
    hash := md5.Sum(data)
    return hex.EncodeToString(hash[:])
}

// MD5 untuk ETag HTTP
func hitungETag(konten []byte) string {
    hash := md5.Sum(konten)
    return fmt.Sprintf(`"%x"`, hash)
}

// Streaming MD5 untuk file besar
func md5File(path string) (string, error) {
    f, err := os.Open(path)
    if err != nil {
        return "", err
    }
    defer f.Close()

    h := md5.New()
    if _, err := io.Copy(h, f); err != nil {
        return "", err
    }
    return hex.EncodeToString(h.Sum(nil)), nil
}

crypto/rand — Random yang Aman #

crypto/rand menghasilkan data acak yang aman secara kriptografis — menggunakan sumber entropi dari sistem operasi (/dev/urandom di Linux, CryptGenRandom di Windows). Ini berbeda dari math/rand yang menggunakan PRNG (Pseudo-Random Number Generator) yang hasilnya bisa diprediksi:

flowchart LR
    subgraph MathRand["math/rand — JANGAN untuk crypto"]
        M1["Seeded dari nilai tetap\natau time.Now()"]
        M2["Output bisa diprediksi\njika seed diketahui"]
        M3["Cepat — cocok untuk\nsimulasi, game, test"]
        M4["TIDAK AMAN untuk\ntoken, password, key"]
    end

    subgraph CryptoRand["crypto/rand — GUNAKAN ini"]
        C1["Dari OS entropy\n(/dev/urandom, CryptGenRandom)"]
        C2["Output tidak bisa diprediksi\nbahkan dengan akses ke state sebelumnya"]
        C3["Sedikit lebih lambat\ntapi aman"]
        C4["AMAN untuk token,\npassword, key kriptografi"]
    end

    style MathRand fill:#fce4ec
    style CryptoRand fill:#e8f5e9
import (
    "crypto/rand"
    "math/big"
)

// Baca byte acak langsung
func randomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    if _, err := rand.Read(b); err != nil {
        return nil, fmt.Errorf("randomBytes: %w", err)
    }
    return b, nil
}

// Generate token hex — untuk session ID, API key, reset password
func generateToken(panjang int) (string, error) {
    b, err := randomBytes(panjang)
    if err != nil {
        return "", err
    }
    return hex.EncodeToString(b), nil // panjang string = panjang*2
}

// Generate token URL-safe base64
func generateTokenBase64(panjang int) (string, error) {
    b, err := randomBytes(panjang)
    if err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(b), nil
}

// Random integer dalam rentang [0, max)
func randomInt(max int64) (int64, error) {
    n, err := rand.Int(rand.Reader, big.NewInt(max))
    if err != nil {
        return 0, fmt.Errorf("randomInt: %w", err)
    }
    return n.Int64(), nil
}

// UUID v4 — identifier unik yang aman
func generateUUID() (string, error) {
    b, err := randomBytes(16)
    if err != nil {
        return "", err
    }
    // Set version 4
    b[6] = (b[6] & 0x0f) | 0x40
    // Set variant bits
    b[8] = (b[8] & 0x3f) | 0x80

    return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
        b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil
}

// Contoh penggunaan
func main() {
    // Token 32 byte = 64 karakter hex
    token, _ := generateToken(32)
    fmt.Println("Token:", token)
    // Token: a3f2c1d8e9b7a6f5...

    // Random angka untuk game atau sampling
    n, _ := randomInt(100)
    fmt.Println("Random 0-99:", n)

    // UUID
    uuid, _ := generateUUID()
    fmt.Println("UUID:", uuid)
    // UUID: 550e8400-e29b-41d4-a716-446655440000
}

Password Acak yang Aman #

const (
    charHuruf  = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    charAngka  = "0123456789"
    charSimbol = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
    charSemua  = charHuruf + charAngka + charSimbol
)

func generatePassword(panjang int, gunakkanSimbol bool) (string, error) {
    charset := charHuruf + charAngka
    if gunakkanSimbol {
        charset = charSemua
    }

    password := make([]byte, panjang)
    for i := range password {
        n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        if err != nil {
            return "", fmt.Errorf("generatePassword: %w", err)
        }
        password[i] = charset[n.Int64()]
    }
    return string(password), nil
}

// OTP (One-Time Password) 6 digit
func generateOTP() (string, error) {
    n, err := rand.Int(rand.Reader, big.NewInt(1000000))
    if err != nil {
        return "", err
    }
    return fmt.Sprintf("%06d", n.Int64()), nil
}

crypto/hmac — Verifikasi Integritas #

HMAC (Hash-based Message Authentication Code) menggunakan kunci rahasia untuk menghasilkan tag autentikasi — memverifikasi bahwa pesan tidak diubah DAN berasal dari pihak yang tahu kunci rahasia:

sequenceDiagram
    participant Pengirim as Pengirim (tahu kunci)
    participant Pesan as Pesan
    participant Penerima as Penerima (tahu kunci)

    Pengirim->>Pesan: HMAC(kunci, data) → tag
    Pengirim->>Penerima: kirim (data + tag)
    Penerima->>Penerima: hitung ulang HMAC(kunci, data) → tag2
    Penerima->>Penerima: bandingkan tag vs tag2\n(constant-time!)

    alt tag == tag2
        Penerima-->>Penerima: ✓ data valid & autentik
    else tag != tag2
        Penerima-->>Penerima: ✗ data dimodifikasi atau\nbukan dari pengirim yang sah
    end
import (
    "crypto/hmac"
    "crypto/sha256"
)

// Buat HMAC tag
func buatHMAC(kunci, data []byte) []byte {
    mac := hmac.New(sha256.New, kunci)
    mac.Write(data)
    return mac.Sum(nil)
}

// Verifikasi HMAC — HARUS menggunakan hmac.Equal, bukan bytes.Equal!
func verifikasiHMAC(kunci, data, tag []byte) bool {
    mac := hmac.New(sha256.New, kunci)
    mac.Write(data)
    tagYangDiharapkan := mac.Sum(nil)

    // hmac.Equal menggunakan constant-time comparison
    // untuk mencegah timing attack
    return hmac.Equal(tagYangDiharapkan, tag)
}

// Pola: signed token untuk webhook
func buatWebhookToken(payload []byte, secret string) string {
    tag := buatHMAC([]byte(secret), payload)
    return "sha256=" + hex.EncodeToString(tag)
}

func verifikasiWebhook(payload []byte, secret, signature string) bool {
    expected := buatWebhookToken(payload, secret)
    // constant-time comparison untuk seluruh string
    return hmac.Equal([]byte(expected), []byte(signature))
}

// Penggunaan di HTTP handler untuk webhook GitHub/Stripe
func handlerWebhook(w http.ResponseWriter, r *http.Request) {
    payload, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // max 1MB
    if err != nil {
        http.Error(w, "gagal baca body", 400)
        return
    }

    signature := r.Header.Get("X-Hub-Signature-256")
    webhookSecret := os.Getenv("WEBHOOK_SECRET")

    if !verifikasiWebhook(payload, webhookSecret, signature) {
        http.Error(w, "signature tidak valid", 401)
        return
    }

    // Proses payload...
    w.WriteHeader(http.StatusOK)
}

bcrypt — Hashing Password #

Password tidak boleh disimpan sebagai hash SHA-256 biasa — ini rentan terhadap rainbow table attack dan brute force yang cepat. Gunakan bcrypt yang dirancang khusus untuk hashing password: lambat dengan sengaja dan mengandung salt otomatis.

import "golang.org/x/crypto/bcrypt"

// Hash password saat registrasi
func hashPassword(password string) (string, error) {
    // Cost factor: 10 adalah rekomendasi minimum (2^10 = 1024 iterasi)
    // Lebih tinggi = lebih aman tapi lebih lambat
    // Gunakan 12-14 untuk produksi jika hardware memungkinkan
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    if err != nil {
        return "", fmt.Errorf("hashPassword: %w", err)
    }
    return string(bytes), nil
}

// Verifikasi password saat login
func verifikasiPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

// Cek apakah hash perlu diperbarui (cost factor lebih rendah dari standar saat ini)
func perluRehash(hash string, costMinimum int) bool {
    cost, err := bcrypt.Cost([]byte(hash))
    if err != nil {
        return true // anggap perlu rehash jika error
    }
    return cost < costMinimum
}

// Contoh alur lengkap
func daftarPengguna(db *sql.DB, email, password string) error {
    // Validasi password
    if len(password) < 8 {
        return fmt.Errorf("password minimal 8 karakter")
    }

    // Hash password
    hash, err := hashPassword(password)
    if err != nil {
        return fmt.Errorf("gagal hash password: %w", err)
    }

    // Simpan ke database — simpan HASH, bukan password asli!
    _, err = db.Exec(
        "INSERT INTO pengguna (email, password_hash) VALUES ($1, $2)",
        email, hash)
    return err
}

func loginPengguna(db *sql.DB, email, password string) (*Pengguna, error) {
    var pengguna Pengguna
    var hash string

    err := db.QueryRow(
        "SELECT id, email, nama, password_hash FROM pengguna WHERE email = $1",
        email,
    ).Scan(&pengguna.ID, &pengguna.Email, &pengguna.Nama, &hash)

    if err == sql.ErrNoRows {
        // Jangan ungkapkan apakah email terdaftar atau tidak
        // Tetap jalankan verifikasi untuk mencegah timing attack
        bcrypt.CompareHashAndPassword([]byte("$2a$12$dummy"), []byte(password))
        return nil, fmt.Errorf("email atau password salah")
    }
    if err != nil {
        return nil, fmt.Errorf("query pengguna: %w", err)
    }

    if !verifikasiPassword(password, hash) {
        return nil, fmt.Errorf("email atau password salah")
    }

    // Rehash jika cost factor terlalu rendah
    if perluRehash(hash, 12) {
        hashBaru, _ := hashPassword(password)
        db.Exec("UPDATE pengguna SET password_hash=$1 WHERE id=$2",
            hashBaru, pengguna.ID)
    }

    return &pengguna, nil
}

crypto/subtle — Constant-Time Comparison #

Perbandingan string biasa (== atau bytes.Equal) rentan terhadap timing attack — attacker bisa mengukur waktu yang dibutuhkan untuk mendapatkan petunjuk tentang isi string. crypto/subtle menyediakan operasi constant-time yang membutuhkan waktu yang sama terlepas dari input:

import "crypto/subtle"

// ANTI-PATTERN: perbandingan biasa — rentan timing attack
func verifikasiTokenBuruk(tokenDiterima, tokenValid string) bool {
    return tokenDiterima == tokenValid
    // Waktu bervariasi berdasarkan panjang prefix yang cocok
    // Attacker bisa menebak token karakter per karakter!
}

// BENAR: constant-time comparison
func verifikasiToken(tokenDiterima, tokenValid string) bool {
    // ConstantTimeCompare mengembalikan 1 jika sama, 0 jika tidak
    return subtle.ConstantTimeCompare(
        []byte(tokenDiterima),
        []byte(tokenValid),
    ) == 1
}

// XOR — operasi constant-time pada byte slice
func xorSlice(a, b []byte) []byte {
    if len(a) != len(b) {
        panic("panjang slice harus sama")
    }
    result := make([]byte, len(a))
    subtle.XORBytes(result, a, b) // Go 1.20+
    return result
}

// ConstantTimeByteEq — bandingkan dua byte dalam constant time
fmt.Println(subtle.ConstantTimeByteEq(0x41, 0x41)) // 1 (sama)
fmt.Println(subtle.ConstantTimeByteEq(0x41, 0x42)) // 0 (berbeda)

Pola Keamanan di Produksi #

Signed URL dengan HMAC #

// Buat URL yang dilindungi tanda tangan untuk download sementara
type SignedURL struct {
    URL       string
    ExpiresAt time.Time
    Signature string
}

func buatSignedURL(baseURL, path, secret string, durasi time.Duration) SignedURL {
    expiresAt := time.Now().Add(durasi)
    payload := fmt.Sprintf("%s|%d", path, expiresAt.Unix())
    sig := buatHMAC([]byte(secret), []byte(payload))

    return SignedURL{
        URL:       baseURL + path,
        ExpiresAt: expiresAt,
        Signature: hex.EncodeToString(sig),
    }
}

func verifikasiSignedURL(path, signatureHex, secret string, expiresAt time.Time) bool {
    // Periksa expiry
    if time.Now().After(expiresAt) {
        return false
    }

    payload := fmt.Sprintf("%s|%d", path, expiresAt.Unix())
    sigBytes, err := hex.DecodeString(signatureHex)
    if err != nil {
        return false
    }

    expected := buatHMAC([]byte(secret), []byte(payload))
    return hmac.Equal(expected, sigBytes)
}

Fingerprint File untuk Deteksi Perubahan #

// Hitung dan simpan fingerprint file untuk deteksi perubahan
type Fingerprint struct {
    Path     string
    SHA256   string
    Size     int64
    ModTime  time.Time
}

func hitungFingerprint(path string) (*Fingerprint, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("buka file: %w", err)
    }
    defer f.Close()

    info, err := f.Stat()
    if err != nil {
        return nil, fmt.Errorf("stat file: %w", err)
    }

    h := sha256.New()
    if _, err := io.Copy(h, f); err != nil {
        return nil, fmt.Errorf("hashing: %w", err)
    }

    return &Fingerprint{
        Path:    path,
        SHA256:  hex.EncodeToString(h.Sum(nil)),
        Size:    info.Size(),
        ModTime: info.ModTime(),
    }, nil
}

func verifikasiIntegritas(path, hashDiharapkan string) (bool, error) {
    fp, err := hitungFingerprint(path)
    if err != nil {
        return false, err
    }
    return subtle.ConstantTimeCompare(
        []byte(fp.SHA256),
        []byte(hashDiharapkan),
    ) == 1, nil
}

Token Reset Password #

type TokenResetPassword struct {
    Token     string
    UserID    int
    ExpiresAt time.Time
}

// Buat token reset password yang aman
func buatTokenReset(db *sql.DB, email string) (*TokenResetPassword, error) {
    // Cari pengguna
    var userID int
    err := db.QueryRow(
        "SELECT id FROM pengguna WHERE email = $1", email).Scan(&userID)
    if err != nil {
        // Jangan ungkapkan apakah email terdaftar
        return nil, fmt.Errorf("jika email terdaftar, link reset telah dikirim")
    }

    // Generate token acak yang aman (32 byte = 64 hex chars)
    tokenBytes, err := randomBytes(32)
    if err != nil {
        return nil, fmt.Errorf("generate token: %w", err)
    }
    token := hex.EncodeToString(tokenBytes)

    // Hash token sebelum disimpan ke database
    // Sehingga jika database bocor, token tidak bisa digunakan
    tokenHash := sha256.Sum256([]byte(token))
    tokenHashHex := hex.EncodeToString(tokenHash[:])

    expiresAt := time.Now().Add(1 * time.Hour)

    // Simpan hash token, bukan token asli
    _, err = db.Exec(
        "INSERT INTO reset_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, $3) "+
        "ON CONFLICT (user_id) DO UPDATE SET token_hash=$2, expires_at=$3",
        userID, tokenHashHex, expiresAt)
    if err != nil {
        return nil, fmt.Errorf("simpan token: %w", err)
    }

    return &TokenResetPassword{
        Token:     token, // kirim token asli via email
        UserID:    userID,
        ExpiresAt: expiresAt,
    }, nil
}

// Verifikasi token reset
func verifikasiTokenReset(db *sql.DB, token string) (int, error) {
    // Hash token yang diterima
    tokenHash := sha256.Sum256([]byte(token))
    tokenHashHex := hex.EncodeToString(tokenHash[:])

    var userID int
    var expiresAt time.Time

    err := db.QueryRow(
        "SELECT user_id, expires_at FROM reset_tokens WHERE token_hash = $1",
        tokenHashHex,
    ).Scan(&userID, &expiresAt)

    if err == sql.ErrNoRows {
        return 0, fmt.Errorf("token tidak valid atau sudah digunakan")
    }
    if err != nil {
        return 0, fmt.Errorf("verifikasi token: %w", err)
    }

    if time.Now().After(expiresAt) {
        // Hapus token yang kadaluarsa
        db.Exec("DELETE FROM reset_tokens WHERE token_hash = $1", tokenHashHex)
        return 0, fmt.Errorf("token kadaluarsa")
    }

    // Hapus token setelah digunakan (one-time use)
    db.Exec("DELETE FROM reset_tokens WHERE token_hash = $1", tokenHashHex)

    return userID, nil
}

Kesalahan Kriptografi yang Harus Dihindari #

flowchart TD
    subgraph Salah["✗ Kesalahan Umum"]
        E1["Gunakan math/rand\nuntuk token/key"]
        E2["Simpan password\nsebagai SHA-256 biasa"]
        E3["Bandingkan token\ndengan == atau bytes.Equal"]
        E4["Gunakan MD5\nuntuk keamanan"]
        E5["Simpan token asli\ndi database"]
        E6["Ungkapkan apakah\nemail terdaftar\nsaat login/reset"]
    end

    subgraph Benar["✓ Yang Harus Dilakukan"]
        C1["Gunakan crypto/rand\nuntuk semua random kriptografi"]
        C2["Gunakan bcrypt\nuntuk hashing password"]
        C3["Gunakan subtle.ConstantTimeCompare\natau hmac.Equal"]
        C4["Gunakan SHA-256 atau SHA-512\nuntuk keamanan"]
        C5["Simpan hash token\ndi database"]
        C6["Kembalikan respons\nyang sama untuk\nemail valid/invalid"]
    end

    E1 -.->|"perbaiki"| C1
    E2 -.->|"perbaiki"| C2
    E3 -.->|"perbaiki"| C3
    E4 -.->|"perbaiki"| C4
    E5 -.->|"perbaiki"| C5
    E6 -.->|"perbaiki"| C6

    style Salah fill:#fce4ec
    style Benar fill:#e8f5e9
// ✗ ANTI-PATTERN: math/rand untuk token
import "math/rand"
token := fmt.Sprintf("%d", rand.Int63()) // TIDAK AMAN!

// ✓ BENAR: crypto/rand
tokenBytes, _ := randomBytes(32)
token := hex.EncodeToString(tokenBytes)

// ✗ ANTI-PATTERN: SHA-256 untuk password
hash := sha256.Sum256([]byte(password)) // mudah di-brute force!

// ✓ BENAR: bcrypt
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)

// ✗ ANTI-PATTERN: perbandingan biasa untuk token
if receivedToken == storedToken { // timing attack!

// ✓ BENAR: constant-time
if subtle.ConstantTimeCompare([]byte(receivedToken), []byte(storedToken)) == 1 {

// ✗ ANTI-PATTERN: simpan token asli di DB
db.Exec("INSERT INTO tokens (token) VALUES ($1)", token)
// Jika DB bocor, semua token bisa digunakan!

// ✓ BENAR: simpan hash token
h := sha256.Sum256([]byte(token))
db.Exec("INSERT INTO tokens (token_hash) VALUES ($1)", hex.EncodeToString(h[:]))

Kapan Beralih ke Alternatif #

Tetap gunakan crypto standard library jika:
  ✓ SHA-256 / SHA-512 untuk hashing data dan integritas
  ✓ crypto/rand untuk semua kebutuhan random kriptografi
  ✓ crypto/hmac untuk verifikasi integritas dengan kunci
  ✓ crypto/subtle untuk constant-time comparison

Gunakan golang.org/x/crypto (extended library) untuk:
  ✓ bcrypt — hashing password
  ✓ argon2 — hashing password modern (lebih kuat dari bcrypt)
  ✓ scrypt — hashing password dengan memory-hard function
  ✓ chacha20poly1305 — enkripsi symmetric modern
  ✓ ssh — SSH client dan server
  ✓ tls — konfigurasi TLS yang lebih advanced

Pertimbangkan library eksternal jika:
  ✗ JWT (JSON Web Token) → golang-jwt/jwt
  ✗ OAuth 2.0 → golang.org/x/oauth2
  ✗ OpenPGP → golang.org/x/crypto/openpgp
  ✗ Hardware Security Module (HSM) → library vendor spesifik

JANGAN implementasikan algoritma kriptografi sendiri —
selalu gunakan implementasi yang sudah teruji dari standard library
atau library terpercaya.

Ringkasan #

  • crypto/rand, bukan math/rand untuk semua kebutuhan kriptografi — math/rand adalah PRNG yang hasilnya bisa diprediksi, crypto/rand menggunakan entropi sistem operasi yang tidak bisa diprediksi.
  • SHA-256 untuk hashing data, bukan SHA-1 atau MD5 — SHA-1 dan MD5 sudah terbukti memiliki kelemahan kriptografis dan tidak boleh digunakan untuk keamanan.
  • bcrypt (atau argon2) untuk hashing password — SHA-256 tidak cocok untuk password karena terlalu cepat, memudahkan brute force; bcrypt sengaja lambat dan mengandung salt otomatis.
  • hmac.Equal atau subtle.ConstantTimeCompare untuk membandingkan nilai kriptografi — perbandingan biasa (==) rentan terhadap timing attack yang memungkinkan attacker menebak nilai karakter per karakter.
  • Simpan hash token, bukan token asli di database — jika database bocor, attacker tidak bisa menggunakan token langsung karena perlu mengetahui nilai aslinya.
  • io.Copy ke hasher untuk hashing file besar — hindari membaca seluruh file ke memori, gunakan streaming untuk efisiensi.
  • HMAC untuk verifikasi integritas dengan kunci rahasia — membuktikan bahwa pesan tidak diubah DAN berasal dari pihak yang memegang kunci.
  • Jangan ungkapkan informasi berlebih pada endpoint login/reset — kembalikan respons yang sama untuk email valid dan invalid untuk mencegah user enumeration attack.
  • Jangan implementasikan kriptografi sendiri — gunakan selalu implementasi yang sudah teruji dari standard library atau golang.org/x/crypto.

← Sebelumnya: Encoding Csv   Berikutnya: Testing →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact