Göktay Logo

Şifreleme, Hash ve Encoding: Go'da crypto/aes, bcrypt ve base64'ü Karıştırmayı Bırakın

4 dk okuma

Şu üç cümleyi gerçek kod tabanlarında sayısız kez gördüm:

Üçü de aynı temel yanılgının farklı yüzleri: encoding, hashing ve encryption birbirine karıştırılıyor. Bu üçü tamamen farklı işler yapar ve birini diğerinin yerine kullanmak, çoğu zaman fark edilmeyen ciddi güvenlik açıklarına yol açar. Bu yazıda üçünü Go bağlamında netleştirip — encoding/base64, bcrypt ve crypto/aes ile — hangisinin ne zaman kullanılacağını gerçek kodla ortaya koyacağız.

Zihinsel Model: Üç Farklı İş

Her şeyden önce ne olduklarını tek cümlede ayıralım:

KavramYönAnahtar?Amaç
Encoding (base64, hex)Çift yönlüYokTemsil / taşıma
Hashing (SHA-256, bcrypt)Tek yönlüYokBütünlük / parola
Encryption (AES, ChaCha20)Çift yönlüVarGizlilik

Kilit ayrım: Anahtar var mı ve geri döndürülebilir mi? Encoding anahtarsız ve herkese açık geri döndürülebilir; hashing hiç geri döndürülemez; encryption yalnızca doğru anahtarla geri döndürülebilir.

Encoding: Gizlilik Değil, Temsil

base64 bir güvenlik aracı değildir. Bir gizlilik katmanı sağlamaz; sadece ikili (binary) veriyi metin olarak taşınabilir hale getirir. Anahtar yoktur, dolayısıyla herkes anında geri çözebilir.

import "encoding/base64"

encoded := base64.StdEncoding.EncodeToString([]byte("gizli mesaj"))
// "Z2l6bGkgbWVzYWo=" — bu şifreleme değil, sadece farklı bir yazım
decoded, _ := base64.StdEncoding.DecodeString(encoded)
// tekrar "gizli mesaj" — hiçbir anahtar gerekmedi

Kural: base64’ü JSON içinde ikili veri taşımak, bir token’ı URL’de göndermek gibi durumlar için kullanın — asla “gizleme” için değil. “Base64 ile şifreledim” cümlesi bir güvenlik yanılgısıdır.

Hashing: Tek Yönlü Kapı

Hashing veriyi sabit boyutlu bir özete çevirir ve geri döndürülemez. İki ana kullanım alanı vardır ve bu ikisi farklı araçlar ister.

1. Bütünlük / Checksum: SHA-256

Bir dosyanın veya mesajın değişmediğini doğrulamak için hızlı hash’ler (SHA-256) idealdir:

import "crypto/sha256"

sum := sha256.Sum256([]byte("dosya içeriği"))
// Aynı girdi her zaman aynı özeti verir; en ufak değişiklik özeti tamamen değiştirir

2. Parola Saklama: Asla SHA Değil — bcrypt / argon2

İşte en kritik hata burada: parolaları SHA ile hash’lemeyin. SHA hızlıdır; parola için tam istemediğimiz şey budur, çünkü saldırgan saniyede milyarlarca tahmin deneyebilir. Parolalar için kasıtlı olarak yavaş ve tuzlu (salted) algoritmalar kullanın:

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

// Kayıt sırasında: parolayı hash'le (cost 10-12 arası önerilir)
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
    return err
}
// hash'i veritabanına yaz — parolanın kendisini ASLA saklama

// Giriş sırasında: doğrula (parolayı geri çözmezsin!)
err = bcrypt.CompareHashAndPassword(hash, []byte(girilenParola))
if err != nil {
    // parola yanlış
}

Dikkat edin: parolayı hiçbir yerde geri çözmüyoruz. CompareHashAndPassword aynı işlemi tekrar yapıp karşılaştırır. Parolayı “geri çözebildiğiniz” bir sistem tasarladıysanız, muhtemelen hash yerine şifreleme kullanmışsınız demektir — ve bu bir güvenlik açığıdır. Parola hashleme tuzaklarını sık yapılan hatalar yazımda da işlemiştim.

Encryption: Anahtarla Geri Dönülebilir Gizlilik

Şimdi asıl “şifreleme”ye geldik. Encryption, veriyi bir anahtar ile gizler ve yalnızca o anahtarla geri açılır. Go’da modern, doğru seçim AES-256-GCM’dir: hem şifreler hem de verinin değiştirilmediğini doğrular (authenticated encryption).

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "io"
)

func encrypt(plaintext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key) // key 32 byte = AES-256
    if err != nil {
        return nil, err
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }
    // Her şifrelemede benzersiz, rastgele nonce
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }
    // Seal: nonce'u başa ekler + authentication tag üretir
    return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

Çözme tarafında gcm.Open, hem veriyi açar hem de tag’i doğrular; veri kurcalanmışsa hata döner. crypto/aes’in bu ve diğer standart kütüphane paketlerinin gücünü standart kütüphane yazımda ele almıştım.

Hangi Durumda Hangisi?

Kafa karışıklığını bitiren pratik tablo:

En Sık Üç Hata

  1. base64’ü güvenlik sanmak. Anahtarsız ve herkese açık; gizlilik sağlamaz.
  2. Parolayı SHA ile hash’lemek (veya daha kötüsü, şifreleyip geri çözmek). Parola için bcrypt/argon2id şarttır.
  3. Şifreleme anahtarını koda gömmek. Anahtarı ortam değişkeninde veya bir secret manager’da tutun; koda veya repoya asla yazmayın. Bu tür sınır ihlallerini Clean Architecture yazımda katman disipliniyle ilişkilendiriyorum.

Özet

Encoding, hashing ve encryption üç farklı iştir: base64 temsil eder (anahtarsız, geri döndürülebilir), hash tek yönlü özettir (parola için bcrypt/argon2, bütünlük için SHA), encryption ise anahtarla geri döndürülebilir gizlilik sağlar (AES-256-GCM). Doğru aracı doğru işe koyduğunuzda, “base64 ile şifreledim” gibi sessiz açıklar kendiliğinden ortadan kalkar. Kavramları tarayıcıda denemek için aşağıdaki ilgili araçları kullanabilirsiniz.