Şu üç cümleyi gerçek kod tabanlarında sayısız kez gördüm:
- “Parolayı base64 ile şifreledim.”
- “Veriyi SHA-512 ile encrypt ettim.”
- “Token’ı hash’leyip sonra geri çözeceğim.”
Üçü 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:
| Kavram | Yön | Anahtar? | Amaç |
|---|---|---|---|
| Encoding (base64, hex) | Çift yönlü | Yok | Temsil / taşıma |
| Hashing (SHA-256, bcrypt) | Tek yönlü | Yok | Bütünlük / parola |
| Encryption (AES, ChaCha20) | Çift yönlü | Var | Gizlilik |
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:
- Parola saklayacaksın → Hashing (bcrypt / argon2id). Geri çözülmez, sadece doğrulanır.
- Dosya/mesaj bütünlüğünü doğrulayacaksın → Hashing (SHA-256).
- İkili veriyi metin olarak taşıyacaksın → Encoding (base64). Güvenlik değil, temsil.
- Sonra geri okuyacağın veriyi gizleyeceksin (API anahtarı, kişisel veri) → Encryption (AES-256-GCM).
En Sık Üç Hata
- base64’ü güvenlik sanmak. Anahtarsız ve herkese açık; gizlilik sağlamaz.
- Parolayı SHA ile hash’lemek (veya daha kötüsü, şifreleyip geri çözmek). Parola için bcrypt/argon2id şarttır.
- Ş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.