Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Vue

Implementasi HMAC di Go untuk Otentikasi Pesan

2 min read .
Implementasi HMAC di Go untuk Otentikasi Pesan

Saat aplikasi saya berhubungan dengan data yang sensitif, memastikan integritas dan keaslian pesan jadi hal penting. Bayangin kalau ada pihak ketiga yang coba mengutak-atik pesan di perjalanan, atau bahkan melakukan replay attack dengan mengirim ulang pesan lama. Nah, salah satu cara sederhana tapi kuat untuk mengatasi ini adalah dengan HMAC (Hash-based Message Authentication Code).

Di postingan ini, kita akan lihat bagaimana cara bikin dan memverifikasi HMAC di Go menggunakan algoritma hash SHA-512, lengkap dengan penggunaan nonce dan timestamp supaya lebih aman.

Apa itu HMAC?

HMAC pada dasarnya menggabungkan fungsi hash dengan kunci rahasia. Tujuannya: memastikan pesan memang datang dari pihak yang sah dan tidak diubah.

Prosesnya kira-kira begini:

  1. Generate HMAC – pesan, kunci rahasia, plus info tambahan (seperti nonce dan timestamp) diproses jadi sebuah kode unik.
  2. Verifikasi HMAC – penerima melakukan hal yang sama, lalu membandingkan hasilnya dengan HMAC yang dikirim. Kalau sama, berarti pesan valid.

Contoh Implementasi di Go

package main

import (
	"crypto/hmac"
	"crypto/sha512"
	"encoding/hex"
	"fmt"
	"time"
)

func main() {
	message := "this is a sample message"
	key := "secret"
	nonce := "random"
	timestamp := time.Now().Unix()
	allowedTimestampDiff := int64(60) // 60 detik

	usedNonces := make(map[string]bool)

	// Generate HMAC
	generatedHMAC := generateMAC(message, key, nonce, timestamp)
	fmt.Println("Generated HMAC:", generatedHMAC)

	// Verifikasi HMAC asli
	isValid := verifyHMAC(message, key, generatedHMAC, nonce, timestamp, allowedTimestampDiff, usedNonces)
	fmt.Println("Validasi HMAC:", isValid)

	// Verifikasi dengan HMAC palsu
	fake := verifyHMAC(message, key, "incorrect", nonce, timestamp, allowedTimestampDiff, usedNonces)
	fmt.Println("Validasi HMAC palsu:", fake)

	// Coba kirim ulang dengan nonce yang sama
	replay := verifyHMAC(message, key, generatedHMAC, nonce, timestamp, allowedTimestampDiff, usedNonces)
	fmt.Println("Validasi dengan nonce sama:", replay)
}

func generateMAC(message, key, nonce string, timestamp int64) string {
	data := fmt.Sprintf("%s:%d:%s", message, timestamp, nonce)
	h := hmac.New(sha512.New, []byte(key))
	h.Write([]byte(data))
	return hex.EncodeToString(h.Sum(nil))
}

func verifyHMAC(message, key, receivedHMAC, nonce string, timestamp int64, allowedTimestampDiff int64, usedNonces map[string]bool) bool {
	expectedHMAC := generateMAC(message, key, nonce, timestamp)

	// Bandingkan HMAC
	expectedBytes, _ := hex.DecodeString(expectedHMAC)
	receivedBytes, _ := hex.DecodeString(receivedHMAC)
	if !hmac.Equal(expectedBytes, receivedBytes) {
		return false
	}

	// Cek timestamp
	now := time.Now().Unix()
	if timestamp < now-allowedTimestampDiff || timestamp > now+allowedTimestampDiff {
		return false
	}

	// Cek nonce
	if usedNonces[nonce] {
		return false
	}
	usedNonces[nonce] = true

	return true
}

Penjelasan Singkat

  1. generateMAC – bikin HMAC dari kombinasi pesan + timestamp + nonce dengan kunci rahasia.

  2. verifyHMAC – verifikasi dengan:

    • Membandingkan HMAC yang diterima dengan hasil generate ulang.
    • Memastikan timestamp masih dalam rentang wajar (contoh: 60 detik).
    • Mengecek apakah nonce sudah pernah dipakai (untuk mencegah replay).
  3. main – contoh penggunaan: verifikasi HMAC asli, HMAC palsu, dan pesan dengan nonce yang sama.

Kenapa Perlu Nonce & Timestamp?

  • Nonce: mencegah serangan replay. Kalau nonce sudah pernah dipakai, langsung ditolak.
  • Timestamp: mencegah pesan lama dipakai ulang. Jadi kalau ada jeda waktu terlalu jauh, pesan dianggap tidak valid.

Kesimpulan

Dengan HMAC + SHA-512, ditambah nonce dan timestamp, kita bisa bikin sistem otentikasi pesan yang relatif sederhana tapi kuat di Go. Teknik ini sering dipakai di API, protokol komunikasi, atau sistem di mana kita perlu memastikan pesan benar-benar otentik dan tidak diutak-atik.

Lihat Juga

Enkripsi & Dekripsi Data di Go Pakai AES (Contoh Praktis)
Enkripsi & Dekripsi Data di Go Pakai AES (Contoh Praktis)
Kalau kamu bikin aplikasi yang butuh ngamanin data (misalnya password, token, atau informasi sensitif), kamu pasti perlu enkripsi. Salah satu algoritma paling populer dan kuat adalah AES (Advanced Encryption Standard). Di Go, saya bisa pakai AES dengan cukup mudah lewat paket bawaan crypto/aes dan crypto/cipher. Dalam contoh ini, saya bakal pakai mode CFB (Cipher Feedback) karena fleksibel dan bisa langsung dipakai buat data dengan panjang sembarang. Konsep Singkat AES Simetris: kunci yang dipakai buat enkripsi dan dekripsi sama. IV (Initialization Vector): angka acak yang bikin hasil enkripsi beda-beda walaupun plaintext-nya sama. CFB mode: mode enkripsi yang cocok buat data bergaya stream, jadi nggak perlu padding ribet. Contoh Program Go Copy package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "fmt" "io" ) func generateRandomKey() ([]byte, error) { key := make([]byte, 32) // AES-256 _, err := rand.Read(key) if err != nil { return nil, err } return key, nil } func encrypt(plaintext []byte, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } ciphertext := make([]byte, aes.BlockSize+len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { return nil, err } stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) return ciphertext, nil } func decrypt(ciphertext []byte, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(ciphertext) < aes.BlockSize { return nil, fmt.Errorf("ciphertext too short") } iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(ciphertext, ciphertext) return ciphertext, nil } func main() { plaintext := []byte("Ini data rahasia yang harus diamankan 🔒") key, _ := generateRandomKey() ciphertext, _ := encrypt(plaintext, key) fmt.Println("Ciphertext (base64):", base64.StdEncoding.EncodeToString(ciphertext)) decrypted, _ := decrypt(ciphertext, key) fmt.Println("Decrypted text:", string(decrypted)) } Cara Kerjanya Bikin kunci acak (generateRandomKey) → panjang 32 byte (AES-256). Enkripsi (encrypt) → bikin IV acak, lalu enkripsi plaintext pakai AES-CFB. Dekripsi (decrypt) → ambil IV dari awal ciphertext, lalu kembalikan data ke bentuk asli. Main → demonya: enkripsi string, print hasil base64, lalu dekripsi lagi buat bukti bahwa datanya aman bolak-balik. Output Contoh Copy Ciphertext (base64): QZrR9m0dpN9Yk3cGYn1xY1Hu0+8EJz6fC4oYc5V1qDqPV6Y= Decrypted text: Ini data rahasia yang harus diamankan Kesimpulan Dengan AES + CFB mode di Go:
Cari Kata di Banyak Teks Sekaligus Pakai Goroutine di Go
Cari Kata di Banyak Teks Sekaligus Pakai Goroutine di Go
Go punya fitur konkuren yang kece banget lewat goroutine. Salah satu contoh penggunaannya: cari kata tertentu di banyak teks sekaligus. Daripada cek teks satu-satu secara berurutan, saya bisa suruh goroutine kerja bareng-bareng, hasilnya lebih cepat dan efisien. Gambaran Umum Program ini bakal: Cari kata di satu teks (fungsi searchInText). Cari kata di banyak teks secara paralel pakai goroutine (fungsi searchWordInTexts). Print hasil pencarian di main. Kode Program Copy package main import ( "fmt" "strings" "sync" ) // searchInText: cek apakah kata ada di dalam teks func searchInText(text, word string) bool { if text == "" || word == "" { return false } // Biar pencarian nggak case-sensitive text = strings.ToLower(text) word = strings.ToLower(word) return strings.Contains(text, word) } // searchWordInTexts: cari kata di banyak teks sekaligus func searchWordInTexts(texts []string, word string) bool { var wg sync.WaitGroup var mu sync.Mutex found := false for _, text := range texts { wg.Add(1) go func(t string) { defer wg.Done() if searchInText(t, word) { mu.Lock() found = true mu.Unlock() } }(text) } wg.Wait() return found } func main() { texts := []string{ "This is a long example text", "Another text for word searching", "This program uses goroutines for searching", } word := "program" if searchWordInTexts(texts, word) { fmt.Printf("Kata '%s' ketemu di salah satu teks\n", word) } else { fmt.Printf("Kata '%s' nggak ditemukan\n", word) } } Penjelasan searchInText Ubah teks dan kata jadi huruf kecil semua biar pencarian nggak peka kapital. Habis itu tinggal pakai strings.Contains.
chevron-up