emirhanHCL 1
emirhanHCL
kralhakan2009 1
kralhakan2009
Vahsi Uzman 1
Vahsi Uzman
farkmt2official 1
farkmt2official
mannaxxx 1
mannaxxx
Bvural41 1
Bvural41
Agora Metin2 1
Agora Metin2
Hikaye Ekle

[NODE JS] bcrypt ile Şifre Hashleme

nodejs.png


[NODE JS] bcrypt ile Şifre Hashleme​


Şifreleri düz metin olarak veritabanında saklamak, yapabileceğiniz en tehlikeli hatalardan biridir. Bir veri ihlali yaşandığında tüm kullanıcılarınızın şifreleri açığa çıkar. Üstelik insanlar genellikle aynı şifreyi birden fazla platformda kullanır, bu yüzden bir sızıntı zincirleme hasarlara yol açar. Ben kariyerimde bizzat şahit oldum: bir startup'ın veritabanı sızdı ve şifreler düz metin olarak saklanıyordu. Sonuç felaket oldu.

bcrypt, şifre hashleme için özel olarak tasarlanmış bir algoritmadır. MD5 veya SHA-256 gibi genel amaçlı hash fonksiyonlarından farklı olarak, bcrypt kasıtlı olarak yavaştır. Bu yavaşlık, brute force saldırılarını pratik olarak imkânsız hale getirir. Bir SHA-256 hash saniyede milyarlarca kez hesaplanabilirken, bcrypt hash'i saniyede sadece birkaç kez hesaplanabilir.

bcrypt'in en güzel özelliği salt (tuz) kavramını otomatik olarak yönetmesidir. Salt, her şifre için rastgele üretilen bir değerdir ve hash'in başına eklenir. Bu sayede aynı şifreye sahip iki kullanıcının hash'leri farklı olur. Rainbow table saldırıları böylece etkisiz hale gelir. bcrypt kullanırken salt'ı ayrıca saklamanız gerekmez, hash'in içinde zaten mevcuttur.

Salt rounds (tuz turu) kavramı, bcrypt'in ne kadar yavaş çalışacağını belirler. Her tur, hesaplama süresini iki katına çıkarır. 10 tur, 2^10 = 1024 iterasyon demektir. Genellikle 10-12 arası bir değer önerilir. 10 tur ile bir hash yaklaşık 100ms sürer, bu da bir giriş işlemi için kabul edilebilir bir süredir. Ancak 15 tura çıkarsanız, hash süresi birkaç saniyeye yükselir.

Node.js'te bcrypt kullanmanın iki yolu vardır: bcrypt ve bcryptjs paketleri. bcrypt paketi native C++ bağlamaları kullanır ve daha hızlıdır ama kurulumu bazen sorunlu olabilir (özellikle Windows'ta). bcryptjs ise tamamen JavaScript ile yazılmıştır, kurulumu kolaydır ama biraz daha yavaştır. Production ortamında bcrypt paketini, geliştirme ortamında bcryptjs paketini tercih edebilirsiniz. API'leri tamamen aynıdır.

Şifre doğrulama sürecinde, kullanıcının girdiği şifreyi tekrar hashleyip veritabanındaki hash ile karşılaştırırsınız. bcrypt.compare() fonksiyonu bu işi güvenli şekilde yapar. Kendi karşılaştırma mantığınızı yazmayın çünkü timing attack'lere karşı savunmasız olabilirsiniz. bcrypt.compare() constant-time karşılaştırma yapar, bu da timing saldırılarını engeller.

Asenkron ve senkron kullanım seçenekleri vardır. Asenkron versiyonu (bcrypt.hash ve bcrypt.compare) her zaman tercih edin çünkü senkron versiyonu event loop'u bloke eder. Özellikle yüksek trafikli bir uygulamada senkron bcrypt kullanmak, sunucunuzu çökertebilir.

Şifre politikaları da önemlidir. Minimum uzunluk, büyük-küçük harf karışımı, sayı ve özel karakter gereksinimleri gibi kurallar belirleyin. Ama aşırıya kaçmayın, çok karmaşık kurallar kullanıcıları basit şifreler seçmeye iter. NIST'in güncel önerileri, minimum 8 karakter ve yaygın şifrelerin engellenmesini önerir.

JavaScript:
// ==============================
// bcrypt ile Şifre Hashleme
// ==============================
const bcrypt = require('bcrypt');

// Salt round sayısı (2^10 = 1024 iterasyon)
const SALT_ROUNDS = 10;

// ==============================
// Şifre Hashleme (Asenkron - Önerilen)
// ==============================
async function sifreHashle(duzSifre) {
    try {
        // Salt oluştur ve hashle (tek adımda)
        const hash = await bcrypt.hash(duzSifre, SALT_ROUNDS);
        console.log('Düz şifre:', duzSifre);
        console.log('Hashlenmiş şifre:', hash);
        console.log('Hash uzunluğu:', hash.length, 'karakter');
        return hash;
    } catch (hata) {
        console.error('Hashleme hatası:', hata.message);
        throw hata;
    }
}

// ==============================
// Şifre Doğrulama
// ==============================
async function sifreDogrula(duzSifre, hashlenmisSifre) {
    try {
        const eslesiyor = await bcrypt.compare(duzSifre, hashlenmisSifre);

        if (eslesiyor) {
            console.log('Şifre doğru! Giriş başarılı.');
        } else {
            console.log('Şifre yanlış! Giriş reddedildi.');
        }

        return eslesiyor;
    } catch (hata) {
        console.error('Doğrulama hatası:', hata.message);
        throw hata;
    }
}

// ==============================
// Kullanıcı Kayıt ve Giriş Sistemi
// ==============================
class KullaniciYonetimi {
    constructor() {
        // Bellek içi kullanıcı deposu (gerçekte veritabanı kullanılmalı)
        this.kullanicilar = new Map();
    }

    async kayitOl(kullaniciAdi, email, sifre) {
        // Kullanıcı zaten var mı kontrol et
        if (this.kullanicilar.has(kullaniciAdi)) {
            throw new Error('Bu kullanıcı adı zaten kullanılıyor.');
        }

        // Şifre politikası kontrolü
        this.sifrePolitikasiKontrol(sifre);

        // Şifreyi hashle
        const hashlenmis = await bcrypt.hash(sifre, SALT_ROUNDS);

        // Kullanıcıyı kaydet
        const kullanici = {
            kullaniciAdi,
            email,
            sifreHash: hashlenmis,
            olusturulma: new Date().toISOString(),
            sonGiris: null,
            basarisizGirisSayisi: 0,
            hesapKilitli: false
        };

        this.kullanicilar.set(kullaniciAdi, kullanici);
        console.log(`Kullanıcı "${kullaniciAdi}" başarıyla kaydedildi.`);
        return { kullaniciAdi, email };
    }

    async girisYap(kullaniciAdi, sifre) {
        const kullanici = this.kullanicilar.get(kullaniciAdi);

        if (!kullanici) {
            // Timing attack'i önlemek için yine de hash karşılaştırması yap
            await bcrypt.hash(sifre, SALT_ROUNDS);
            throw new Error('Kullanıcı adı veya şifre hatalı.');
        }

        // Hesap kilitli mi kontrol et
        if (kullanici.hesapKilitli) {
            throw new Error('Hesabınız kilitlendi. Lütfen destek ekibiyle iletişime geçin.');
        }

        // Şifre doğrulama
        const gecerli = await bcrypt.compare(sifre, kullanici.sifreHash);

        if (!gecerli) {
            kullanici.basarisizGirisSayisi++;

            // 5 başarısız denemeden sonra hesabı kilitle
            if (kullanici.basarisizGirisSayisi >= 5) {
                kullanici.hesapKilitli = true;
                console.warn(`Hesap kilitlendi: ${kullaniciAdi}`);
            }

            throw new Error('Kullanıcı adı veya şifre hatalı.');
        }

        // Başarılı giriş
        kullanici.basarisizGirisSayisi = 0;
        kullanici.sonGiris = new Date().toISOString();
        console.log(`"${kullaniciAdi}" başarıyla giriş yaptı.`);
        return { kullaniciAdi, sonGiris: kullanici.sonGiris };
    }

    async sifreDegistir(kullaniciAdi, eskiSifre, yeniSifre) {
        const kullanici = this.kullanicilar.get(kullaniciAdi);

        if (!kullanici) {
            throw new Error('Kullanıcı bulunamadı.');
        }

        // Eski şifreyi doğrula
        const eskiGecerli = await bcrypt.compare(eskiSifre, kullanici.sifreHash);
        if (!eskiGecerli) {
            throw new Error('Mevcut şifre hatalı.');
        }

        // Yeni şifre politikası kontrolü
        this.sifrePolitikasiKontrol(yeniSifre);

        // Yeni şifreyi hashle ve güncelle
        kullanici.sifreHash = await bcrypt.hash(yeniSifre, SALT_ROUNDS);
        console.log(`"${kullaniciAdi}" şifresi güncellendi.`);
    }

    sifrePolitikasiKontrol(sifre) {
        if (sifre.length < 8) {
            throw new Error('Şifre en az 8 karakter olmalıdır.');
        }
        if (!/[A-Z]/.test(sifre)) {
            throw new Error('Şifre en az bir büyük harf içermelidir.');
        }
        if (!/[a-z]/.test(sifre)) {
            throw new Error('Şifre en az bir küçük harf içermelidir.');
        }
        if (!/[0-9]/.test(sifre)) {
            throw new Error('Şifre en az bir rakam içermelidir.');
        }
    }
}

// Kullanım örneği
async function ornek() {
    const yonetim = new KullaniciYonetimi();

    await yonetim.kayitOl('ahmet_dev', 'ahmet@ornek.com', 'Guclu_Sifre123');
    await yonetim.girisYap('ahmet_dev', 'Guclu_Sifre123');
    await yonetim.sifreDegistir('ahmet_dev', 'Guclu_Sifre123', 'YeniSifre_456');
}

ornek().catch(console.error);

bcrypt, şifre güvenliğinin temel taşıdır. Her zaman asenkron versiyonunu kullanın, salt rounds değerini donanımınıza göre ayarlayın ve şifre politikalarını uygulayın. Şifreleri asla düz metin, MD5 veya SHA ile saklamayın. bcrypt kullanmak sadece birkaç satır kod eklemek demektir ama güvenlik açısından muazzam bir fark yaratır.
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst