Geçtiğimiz aylarda VesselAPI’nin kimlik doğrulama sistemini optimize ederken beklenmedik bir rakamla karşılaştık: her yetkilendirilmiş istek, API’ye giriş izni verilmeden önce neredeyse 946 milisaniye harcayarak sistemimizin performansını ciddi şekilde yavaşlatıyordu.
Bu durumun nedeni, API anahtarlarını doğrulamak için bcrypt kullanmamızdı. Bcrypt, şifreler için tasarlanmış bir karma algoritmasıdır ve kasıtlı olarak yavaştır. Ancak API anahtarları, şifrelerden çok farklı bir tehdit modeline sahiptir. Bu farkı anlamadığımızda, her istek için ödediğimiz performans bedelini gözden kaçırmıştık.
Bcrypt’in Her İsteğe Yüklediği 100 Milisaniyelik Vergi
VesselAPI’nin ilk versiyonunda, kullanıcılar API anahtarı oluşturduğunda bu anahtarlar bcrypt ile karma haline getirilip saklanıyordu. Maliyet faktörü 10 olarak ayarlanmıştı, bu da her karşılaştırma işleminin yaklaşık 65–100 milisaniye arasında sürmesine neden oluyordu. Tek bir kullanıcı için bu süre tolere edilebilir olsa da, her API isteğinde bu işlemin tekrarlanması sistemimizin performansını ciddi şekilde düşürüyordu.
Daha da önemlisi, bcrypt’in rastgele tuz kullanması nedeniyle, aynı anahtar farklı hash’lere dönüşüyordu. Bu da, veritabanında doğrudan arama yapmayı imkansız hale getiriyordu. Her istek için tüm hash’lerin karşılaştırılması gerekiyordu. Üretim ortamında 11 aktif anahtar bulunduğunda, en kötü senaryoda 11 bcrypt karşılaştırması yapılıyor ve bu da yaklaşık 1,1 saniyelik CPU kullanımına denk geliyordu.
Bu durum, sistemimizin scalability sınırlarını belirliyordu. Bcrypt’in yavaşlığı, her çekirdek için saniyede sadece bir yetkilendirilmiş isteğe izin veriyordu. Bu, sadece "daha sonra optimize edilebilir" bir sorun değil, sistemin fiziksel bir sınırıydı.
API Anahtarları için Bcrypt Yanlış Araç mı?
API anahtarlarını doğrulamak için bcrypt kullanmanın ne kadar mantıklı olduğunu sorgulamamız gerekiyordu. Bcrypt’in yavaşlığı, şifreler için tasarlanmıştı. Bir saldırganın veritabanını ele geçirdiği ve şifreleri kırmaya çalıştığı durumlarda, bcrypt’in yavaşlığı, saldırganın işini zorlaştırıyordu.
Ancak API anahtarları, 256 bitlik kriptografik olarak rastgele gürültülerdi. Bu anahtarları kırmak için gereken deneme sayısı, 2^255’e ulaşıyordu. Bu da, teorik olarak Güneş’in ömründen daha uzun bir süre anlamına geliyordu. Yani, bcrypt’in yavaşlığı, API anahtarları için hiçbir koruma sağlamıyordu.
API anahtarlarının gerçek tehdidi, sızdırılmalarıydı. Birisi API anahtarını bir kamu depoya commit edebilir, log dosyalarında bulabilir ya da saldırıya uğramış bir istemciden çalabilir. Bcrypt’in yavaşlığı, bu tür tehditlere karşı hiçbir koruma sağlamıyordu. Bunun yerine, hızlı ve belirleyici bir karma algoritması kullanmak ve sunucu tarafında saklanan gizli bir anahtarla koruma sağlamak daha mantıklıydı.
HMAC-SHA256’a Geçiş: Performans ve Güvenlik Dengesi
Kararımız, HMAC-SHA256 kullanmak oldu. Bu algoritma, mikrosaniyeler içinde çalışır ve belirleyici bir karma üretir. Ayrıca, sunucu tarafında saklanan gizli bir anahtar (pepper) kullanarak, veritabanında saklanan hash’lerin bile işe yaramaz hale gelmesini sağladık.
Bu değişiklikle birlikte, doğrulama süresi 946 milisaniyeden 811 milisaniyeye düştü. Ancak bu, henüz istediğimiz seviye değildi. Çünkü kalan sürenin çoğu, veritabanı sorgularından kaynaklanıyordu. Bu nedenle, doğrulama sürecini daha da optimize etmeye karar verdik.
// Önceki hali: bcrypt karşılaştırması (O(n) * 100ms her anahtar için)
for _, stored := range allKeyHashes {
if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte(apiKey)); err == nil {
return stored, nil
}
}
// Yeni hali: HMAC + indeksli arama (O(1), ~1µs + bir veritabanı turu)
mac := hmac.New(sha256.New, []byte(pepper))
mac.Write([]byte(apiKey))
keyHMAC := hex.EncodeToString(mac.Sum(nil))
return db.VerifyApiKeyHMAC(keyHMAC) // indeksli arama, <1msHot Path’ta Sıfır Veritabanı Sorgusu
Doğrulama sürecini optimize etmek için basit bir cache sistemi tasarladık. sync.Map adlı bir veri yapısı kullanarak, anahtar hash’lerini ve kullanıcı bilgilerini bellekte sakladık. Bu sayede, cache dolu olduğu sürece, herhangi bir istek veritabanına hiçbir sorgu yapmadan işlenebiliyordu.
Önceki Durum: Her İsteğin Veritabanı Sorguları
- Bearer token çıkarılması
- Tüm anahtar hash’lerinin veritabanından yüklenmesi
- bcrypt.CompareHashAndPassword (her anahtar için ~100ms)
- Kullanıcı kotasının veritabanından kontrolü
- Kullanıcı kullanım sayacının veritabanında artırılması
- Toplamda 3–4 veritabanı turu, 1,1 saniyeye varan CPU kullanımı
Yeni Durum: Cache Dolu Olduğunda
- Bearer token çıkarılması
- HMAC-SHA256 işlemi (pepper ile, ~1µs)
sync.Maparaması:keyCache(onlarca ila yüzlerce nanosaniye)- Yerel sayaç artırımı:
atomic.AddInt64(~25ns) - Bellekte kullanım kontrolü: kullanım < limit? (nanosaniyeler)
- Sıfır veritabanı sorgusu
Bu optimizasyon sayesinde, sistemimizin performansı katlanarak arttı ve gereksiz CPU kullanımı ortadan kalktı. Artık API’miz, istekleri milisaniyeler içinde işleyebilir hale geldi.
Sonuç: Doğru Aracı Seçmek Performansı Nasıl Değiştirir?
API kimlik doğrulamasını optimize etmek, sadece daha hızlı bir sistem inşa etmekle ilgili değil. Aynı zamanda, tehdit modelinize uygun araçları seçmekle ilgili. Bcrypt, şifreler için mükemmel bir seçimdir, ancak API anahtarları için gereksiz yavaşlık anlamına gelir.
Daha hızlı ve güvenli bir sistem inşa etmek için, HMAC-SHA256 gibi belirleyici karma algoritmaları ve sunucu tarafı gizli anahtarları kullanmayı düşünün. Ayrıca, cache kullanımıyla veritabanı sorgularını en aza indirerek, performansı daha da artırabilirsiniz.
Gelecekte, sistemlerimizin büyümesiyle birlikte, bu optimizasyonların ne kadar önemli olduğunu daha iyi anlayacağız. Performansla güvenliği dengeleyen doğru araçları seçmek, uzun vadede ölçeklenebilir ve güvenilir sistemler inşa etmenin anahtarıdır.
Yapay zeka özeti
API anahtarlarını doğrulamak için bcrypt kullanmanın performans kaybını öğrenin. HMAC-SHA256 ve cache kullanımıyla 1 saniyelik doğrulama süresini nasıl milisaniyelere indirebilirsiniz?