İçeriğe geç
Programlama

PHP password_hash ve password_verify Kullanımı

Güncellendi: Son kontrol: Eylül 2026 5 dk okuma

Kısaca cevap

PHP password_hash, kullanıcı şifrelerini güvenli saklamak için yerleşik fonksiyondur: kayıtta password_hash($sifre, PASSWORD_DEFAULT) ile özeti üretip saklarsınız, girişte password_verify($sifre, $ozet) ile doğrularsınız. Tuzu fonksiyon kendisi üretir; algoritma, maliyet ve tuz sonucun içine yazılır. Özet sütununu 255 karakter yapın, ayarları değiştirdiğinizde password_needs_rehash ile eski özetleri yenileyin. md5() ve sha1() şifre saklamak için kullanılmaz.

Bu sayfada

Bu yazıdaki bilgiler ve örnekler php.net'teki resmi PHP belgelerine dayanır. Algoritmaların (bcrypt, Argon2id) nasıl çalıştığını şifreler nasıl saklanmalı yazısında, PHP'de güvenli rastgele şifre üretmeyi PHP ile rastgele şifre üretme yazısında anlatıyoruz.

PHP password_hash: şifreyi hashleme

Fonksiyonun imzası şöyledir:

password_hash(string $password, string|int|null $algo, array $options = []): string

Kayıt sırasında kullanıcının girdiği şifreyi hashleyip veritabanına yazarsınız. Sorguda hazırlanmış ifade (prepared statement) kullanmayı unutmayın:

<?php
$sifre = $_POST['sifre'];

$ozet = password_hash($sifre, PASSWORD_DEFAULT);

$sorgu = $pdo->prepare('INSERT INTO kullanicilar (eposta, sifre_ozeti) VALUES (?, ?)');
$sorgu->execute([$_POST['eposta'], $ozet]);

php.net belgesindeki örnek çıktı şöyledir:

$2y$12$4Umg0rCJwMswRw/l.SwHvuQV01coP0eWmGzd61QH2RvAOMANUBGC.

$2y$ bcrypt'i, 12 maliyeti gösterir; sonrasında 22 karakterlik tuz ve özet gelir. Aynı kodu tekrar çalıştırdığınızda farklı bir sonuç alırsınız, çünkü her çağrıda yeni tuz üretilir. PHP 8.0'dan beri salt seçeneği yok sayılır; php.net de tuzu elle vermemenizi, fonksiyonun güvenli tuzu kendisinin üretmesini önerir. Yine PHP 8.0'dan beri fonksiyon hata durumunda false döndürmek yerine ValueError fırlatır.

password_verify in PHP: girişte doğrulama

password_verify(string $password, string $hash): bool

Girişte kullanıcıyı e-posta adresiyle bulun, kayıtlı özeti alın ve girilen şifreyle birlikte password_verify'a verin:

<?php
$sorgu = $pdo->prepare('SELECT id, sifre_ozeti FROM kullanicilar WHERE eposta = ?');
$sorgu->execute([$_POST['eposta']]);
$kullanici = $sorgu->fetch();

if ($kullanici && password_verify($_POST['sifre'], $kullanici['sifre_ozeti'])) {
    // giriş başarılı
} else {
    // tek bir genel hata: "E-posta veya şifre hatalı"
}

password_verify, özetin içindeki algoritma, maliyet ve tuzu okuduğu için bunları ayrıca saklamanıza gerek yoktur. php.net'e göre fonksiyon zamanlama saldırılarına karşı güvenlidir ve crypt() ile üretilmiş özetleri de doğrulayabilir. Özetleri == veya === ile kendiniz karşılaştırmayın.

PHP password_hash PASSWORD_DEFAULT, BCRYPT ve ARGON2ID

SabitAlgoritmaNotlar (php.net)
PASSWORD_DEFAULTBugün bcryptYeni PHP sürümlerinde daha güçlü bir algoritmaya geçebilir; sonuç uzunluğu değişebileceği için 255 karakterlik sütun önerilir.
PASSWORD_BCRYPTbcrypt ($2y$)Her zaman 60 karakter. Seçenek: cost. Varsayılan maliyet PHP 8.4 ile 10'dan 12'ye çıktı.
PASSWORD_ARGON2IArgon2iPHP 7.2+, PHP Argon2 desteğiyle derlenmişse.
PASSWORD_ARGON2IDArgon2idPHP 7.3+, PHP Argon2 desteğiyle derlenmişse. Seçenekler: memory_cost (KiB), time_cost, threads.

OWASP şifre saklamada Argon2id'yi ilk sıraya, bcrypt'i eski sistemlere koyar. Sunucunuzdaki PHP Argon2 desteğiyle derlenmişse Argon2id'yi OWASP'ın ayarlarından biriyle kullanabilirsiniz (19 MiB bellek, 2 geçiş, 1 iş parçacığı):

<?php
$ayarlar = [
    'memory_cost' => 19456,   // KiB
    'time_cost'   => 2,
    'threads'     => 1,
];
$ozet = password_hash($sifre, PASSWORD_ARGON2ID, $ayarlar);

threads seçeneği yalnızca PHP libargon2 kullandığında geçerlidir, libsodium uygulamasında kullanılamaz. PASSWORD_ARGON2ID sabiti tanımlı değilse PHP'niz Argon2 desteği olmadan derlenmiştir; defined('PASSWORD_ARGON2ID') ile kontrol edebilirsiniz.

Maliyet (cost) nasıl seçilir?

php.net, fonksiyonun kullanılacağı makinede test edilmesini ve etkileşimli girişlerde bir özetin 350 milisaniyeden kısa sürecek şekilde ayarlanmasını önerir. Belgedeki örneğe benzer bir ölçüm:

<?php
$hedef = 0.350;      // saniye
$cost  = 10;
do {
    $cost++;
    $baslangic = microtime(true);
    password_hash('deneme', PASSWORD_BCRYPT, ['cost' => $cost]);
    $sure = microtime(true) - $baslangic;
} while ($sure < $hedef);

echo 'Uygun cost: ' . ($cost - 1);

OWASP de bcrypt için en az 10'u ve sunucunun kaldırabileceği en yüksek değeri önerir. bcrypt maliyeti logaritmiktir: bir artırmak süreyi yaklaşık iki katına çıkarır.

password_needs_rehash: eski özetleri güncelleme

Maliyeti yükselttiğinizde veya algoritmayı değiştirdiğinizde eski kullanıcıların özetleri eski ayarlarla kalır. password_needs_rehash bir özetin istediğiniz algoritma ve ayarlarla oluşturulup oluşturulmadığını söyler. Kullanıcı doğru şifreyle giriş yaptığı anda şifre elinizdedir; özeti o an yenileyin:

<?php
$algoritma = PASSWORD_DEFAULT;
$ayarlar   = ['cost' => 13];

if (password_verify($sifre, $kullanici['sifre_ozeti'])) {
    if (password_needs_rehash($kullanici['sifre_ozeti'], $algoritma, $ayarlar)) {
        $yeni = password_hash($sifre, $algoritma, $ayarlar);
        $pdo->prepare('UPDATE kullanicilar SET sifre_ozeti = ? WHERE id = ?')
            ->execute([$yeni, $kullanici['id']]);
    }
    // oturumu başlat
}

Bir özetin hangi algoritma ve ayarlarla oluşturulduğunu görmek için password_get_info($ozet) kullanabilirsiniz; algo, algoName (ör. bcrypt) ve options anahtarlarını döndürür. Özet olmayan bir metin verirseniz algoName değeri unknown olur.

md5 ile saklanmış eski şifreler

Eski bir sistemde şifreler md5() ile saklanmışsa OWASP'ın anlattığı katmanlama yöntemini kullanabilirsiniz: mevcut MD5 değerlerini hemen password_hash(md5_degeri, PASSWORD_DEFAULT) ile sarın, kaydın eski türden olduğunu bir sütunla işaretleyin. Kullanıcı giriş yaptığında password_verify(md5($sifre), $ozet) ile doğrulayıp şifreyi doğrudan password_hash ile yeniden kaydedin ve işareti kaldırın.

72 bayt sınırı ve NUL bayt

php.net, PASSWORD_BCRYPT kullanıldığında şifrenin en fazla 72 bayta kadar kesileceği ve ilk NUL bayttan sonrasının yok sayılacağı uyarısını yapar. Yani 80 karakterlik bir şifrenin son 8 karakteri doğrulamada hiç kontrol edilmez. Türkçe karakterler UTF-8'de 2 bayt tuttuğu için sınır daha erken dolabilir.

Uzun şifreleri tam desteklemek istiyorsanız Argon2id kullanın. bcrypt'te kalmanız gerekiyorsa OWASP'ın önerdiği biçim, şifreyi gizli bir anahtarla (pepper) HMAC-SHA384'ten geçirip Base64 hâlini bcrypt'e vermektir:

<?php
$pepper = getenv('SIFRE_PEPPER');   // veritabanında saklamayın
$girdi  = base64_encode(hash_hmac('sha384', $sifre, $pepper, true));
$ozet   = password_hash($girdi, PASSWORD_BCRYPT);
// doğrularken: password_verify(base64_encode(hash_hmac('sha384', $sifre, $pepper, true)), $ozet)

hash_hmac'ın son parametresi true olduğunda ham ikili çıktı döner; 48 baytlık bu çıktının Base64 hâli 64 karakterdir, 72 bayt sınırının altında kalır ve NUL bayt içermez. Düz sha1 ikili çıktısını doğrudan bcrypt'e vermeyin; php.net'in NUL bayt uyarısı tam olarak bu durumu anlatır.

PHP password_hash generator: test için özet üretmek

"password_hash generator" diye aranan çevrimiçi araçlar, yazdığınız şifreyi kendi sunucularına gönderir. Gerçek bir şifreyi bu tür sitelere yazmayın. Test verisi veya yönetici hesabı için bir özete ihtiyacınız varsa PHP'nin kurulu olduğu kendi bilgisayarınızda komut satırından üretebilirsiniz:

php -r 'echo password_hash("deneme-sifresi", PASSWORD_DEFAULT), PHP_EOL;'

Komut geçmişine şifre yazılacağını unutmayın; gerçek bir yönetici şifresi için bunu yapmak yerine uygulamanızın kayıt veya şifre sıfırlama akışını kullanın. Rastgele ve güçlü bir test şifresi için şifre üreticimizi, bir şifrenin tahmini gücünü görmek için şifre gücü testini kullanabilirsiniz.

Dikkat edilmesi gerekenler

  • md5(), sha1() veya hash('sha256', ...) ile şifre saklamayın; hızlı özetler sızıntıda kolayca denenir. Farkı hash nedir yazısında anlatıyoruz.
  • Özet sütununu VARCHAR(255) yapın; CHAR(60) Argon2 veya gelecekteki bir PASSWORD_DEFAULT için yetmez.
  • Şifreyi trim() ile kırpmayın veya küçük harfe çevirmeyin; kullanıcının yazdığı her karakter şifrenin parçasıdır.
  • Şifreyi hata günlüklerine veya oturum verisine yazmayın.
  • Başarısız girişleri sınırlayın; NIST SP 800-63B-4, art arda başarısız denemelerin 100'ü geçmemesini şart koşar.

Aynı işin Python karşılığı Python ile bcrypt ve Argon2 yazısında. Kaynaklar: password_hash, password_verify, password_needs_rehash, parola sabitleri (php.net, İngilizce).

Sık sorulan sorular

PHP password_hash nasıl kullanılır?

Kayıt sırasında $ozet = password_hash($sifre, PASSWORD_DEFAULT) çağırın ve dönen metni veritabanında saklayın. Fonksiyon rastgele tuzu kendisi üretir; algoritma, maliyet ve tuz bilgisi dönen metnin içindedir. Tuzu ayrıca saklamanız veya elle vermeniz gerekmez.

password_verify nasıl çalışır?

password_verify($girilen, $kayitli_ozet) kayıtlı özetin içindeki algoritma, maliyet ve tuzu okur, girilen şifreyi aynı ayarlarla yeniden hesaplar ve eşleşirse true döndürür. php.net'e göre fonksiyon zamanlama saldırılarına karşı güvenlidir. Özetleri kendiniz === ile karşılaştırmayın.

PASSWORD_DEFAULT hangi algoritmayı kullanır?

Bugün PASSWORD_DEFAULT, bcrypt anlamına gelen PASSWORD_BCRYPT'in takma adıdır. php.net bu sabitin ileride daha güçlü bir algoritmaya geçebileceğini belirtir; bu yüzden özet sütununun 255 karakter olması önerilir. PHP 8.4 ile bcrypt'in varsayılan maliyeti 10'dan 12'ye yükseldi.

password_hash her seferinde neden farklı sonuç veriyor?

Çünkü her çağrıda yeni ve rastgele bir tuz üretilir. Aynı şifrenin iki özeti farklı görünür ama ikisi de password_verify ile doğrulanır. Bu bir hata değil, tuzun amacıdır; aynı şifreyi kullanan iki kullanıcının özetleri birbirine benzemez.

PHP password_hash generator güvenli mi?

Gerçek bir şifreyi internetteki bir password_hash oluşturucuya yazmayın; şifreniz o siteye gönderilir. Test için bir özete ihtiyacınız varsa kendi bilgisayarınızda php komut satırından password_hash çağırın. Uygulamanızda ise özet her zaman sunucu tarafında, kayıt sırasında üretilmelidir.