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:#fce4eccrypto/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:#fce4ecimport "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:#e8f5e9import (
"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
endimport (
"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, bukanmath/randuntuk semua kebutuhan kriptografi —math/randadalah PRNG yang hasilnya bisa diprediksi,crypto/randmenggunakan 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.Equalatausubtle.ConstantTimeCompareuntuk 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.Copyke 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.