MySQL'de SHA1() Fonksiyonu Kullanımı
Selamlar, bu yazıda MySQL'in SHA1() fonksiyonuna biraz yakından bakacağız. Üretimde bir tabloda dosya checksum'ı tutmak istediğimizde, ya da iki kaydın içeriğinin gerçekten aynı olup olmadığını anlamak için hash karşılaştırması yapmak istediğimizde sıkça karşımıza çıkıyor. Ama aynı fonksiyonun bir kullanım alanı daha var ki, oraya hiç girmememiz lazım: parola saklamak. Sebebine de gireceğiz, merak etmeyin.
SHA1() ne yapar?
SHA1() (eski takma adıyla SHA()) verdiğiniz string'in 160-bit'lik SHA-1 ozetini hesaplar ve 40 karakter uzunluğunda kucuk harfli hex bir string dondurur. Yani veriyi sıkıştırıp, sabit boyutlu bir parmak izine cevirir. Aynı girdiyi her zaman aynı cıktıya esleyen, deterministik bir fonksiyon.
Hızlıca örnek görelim:
SELECT SHA1('merhaba');
-- Donen deger: 8f74becae3563f5b3b34a32a311b9b9b7d8a4d4f gibi 40 karakterlik bir hex
SELECT SHA1('');
-- da39a3ee5e6b4b0d3255bfef95601890afd80709 (bos string'in sabit hash'i)
SELECT SHA1(NULL);
-- NULL
MD5 ile kıyaslarsak, MD5 32 karakter dondururken SHA-1 40 karakter doner. Yani biraz daha geniş bir cıktı uzayı, dolayısıyla rastlantısal carpısma (collision) olasılığı biraz daha düşük. Ama dikkat: 'biraz daha düşük' kelimesi çok kritik; aşağıda ona da değineceğiz.
Pratik kullanım: dosya checksum'ı
Bence SHA1()'in bugün hala dürüstçe kullanılabileceği yer burası. Bir tabloya dosya yüklediğinizde, içeriğin bozulup bozulmadığını ya da daha sonra değiştirilip değiştirilmediğini anlamak için bir checksum tutmak çok mantıklı:
CREATE TABLE yuklenen_dosyalar (
id INT AUTO_INCREMENT PRIMARY KEY,
dosya_adi VARCHAR(255) NOT NULL,
icerik LONGBLOB NOT NULL,
sha1_ozet CHAR(40) NOT NULL,
yuklendi TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO yuklenen_dosyalar (dosya_adi, icerik, sha1_ozet)
VALUES ('rapor.pdf', LOAD_FILE('/tmp/rapor.pdf'), SHA1(LOAD_FILE('/tmp/rapor.pdf')));
Daha sonra içeriğin tutarlı olup olmadığını sorgulamak da kolay:
SELECT
id,
dosya_adi,
SHA1(icerik) = sha1_ozet AS checksum_dogru
FROM yuklenen_dosyalar;
Yer kazanmak isterseniz CHAR(40) yerine BINARY(20) saklayabilirsiniz, yarısı kadar yer tutar:
CREATE TABLE icerik_hash (
id INT PRIMARY KEY,
sha1_hash BINARY(20) NOT NULL
);
INSERT INTO icerik_hash VALUES (1, UNHEX(SHA1('bir icerik')));
SELECT id, HEX(sha1_hash) FROM icerik_hash;
Tekrar eden içerikleri yakalamak
Aynı gövdeye sahip kayıtları bulmak için de kullanışlı. Tek tek body = body karşılaştırmak yerine hash uzerinden gruplarsak çok daha hızlı:
SELECT SHA1(body) AS hash, COUNT(*) AS adet, MIN(id) AS tutulacak_id
FROM makaleler
GROUP BY SHA1(body)
HAVING adet > 1;
Parola hashleme icin SHA1 kullanmayın
Şimdi geldik en kritik kısma. SHA-1 kriptografik açıdan kırılmıştır. 2017'deki SHAttered saldırısıyla pratikte bir çarpışma üretildi; iki farklı girdiyi aynı SHA-1 hash'ine eşlemek artık teorik tehdit değil, sahada gösterilmiş bir gerçek.
Ama parola saklama işi sadece çarpışma direnciyle ilgili değil. Asıl sorun şu: SHA-1 (ve SHA-2 ailesi de) çok hızlıdır. Modern bir GPU saniyede milyarlarca SHA-1 hesaplar. Veritabanınız ele geçtiyse, sözlük saldırısıyla parolaları kırmak için saldırgana hediye paketi vermiş olursunuz. Tuz (salt) eklemek bile yeterli değil; saldırgan tuzu da tablodan görür ve her tuz için ayrı brute-force çevirir.
Yapmanız gereken kasıtlı yavaş ve hafıza-yoğun bir KDF kullanmak: bcrypt, scrypt veya argon2. Bunlar tek bir hash hesabını pahalı kılarak saldırının ekonomisini bozar. MySQL içinde bunu doğrudan yapan fonksiyon yok; doğrusu da bu, parola hash'leme uygulama katmanında olmalı (PHP'nin password_hash()'i, Python'un bcrypt paketi gibi).
Güvenlik istiyorsanız: SHA2()
Parola dışı, ama yine de güvenliğe duyarlı bir senaryoda (mesaj doğrulama kodu üretimi, dijital imza için ozet hesabı) SHA1() yerine SHA2() tercih edin:
-- 256 bitlik ozet
SELECT SHA2('hassas veri', 256);
-- 512 bitlik ozet
SELECT SHA2('hassas veri', 512);
SHA2() ikinci parametre olarak 224, 256, 384 veya 512 alır. Modern uygulamalarda 256 makul bir varsayılan.
Sık karşılaşılan hatalar
- Parolaları SHA1 ile saklamak: Yukarıda anlattım. bcrypt, scrypt veya argon2 kullanın, MySQL fonksiyonu değil.
- Tuz eklemeden hash tutmak: Checksum disinda, kullanıcıya bağlı veriyi hash'liyorsanız (ornegin e-posta'nın hash'i ile arama) saldırgan onceden hesaplanmış tablolarla kolayca cozer.
- Hash'i CHAR(40) yerine VARCHAR(40) tutmak: SHA-1 her zaman 40 karakter; sabit uzunlukta
CHAR(40)veya yer içinBINARY(20)daha doğru. - Çarpışma direncine güvenmek: 'İki farklı içerik aynı hash'e dusmez' varsayımı SHA-1 icin artık geçerli değil. Güvenlik kritik bir karşılaştırma yapacaksanız SHA-2'ye geçin.
Kapanış
Özetle: SHA1() checksum, deduplication ve veri bütünlüğü doğrulaması için hala dürüstçe kullanılabilir, ama güvenlikle ilgili herhangi bir senaryoda yerine SHA2(..., 256) koyun, parolalarda ise bcrypt/argon2'ye geçin. Bence bu üçlü ayrımı (checksum, integrity, parola) kafanızda netleştirdikten sonra hangi fonksiyonu nerede kullanacağınız çok netleşiyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
