Şifreleri hash etmek ve doğrulamak için PHP'nin password_hash'i nasıl kullanılır


94

Son zamanlarda internette rastladığım bir oturum açma komut dosyası üzerinde kendi güvenliğimi uygulamaya çalışıyordum. Her kullanıcı için bir tuz oluşturmak için kendi senaryomu nasıl yapacağımı öğrenmeye çalıştıktan sonra, tökezledim password_hash.

Anladığım kadarıyla ( bu sayfadaki okumaya göre ), kullandığınızda zaten satırda tuz üretiliyor password_hash. Bu doğru mu?

Aklıma gelen bir diğer soru da, 2 tuzun olması akıllıca olmaz mıydı? Biri doğrudan dosyada ve diğeri de DB'de mi? Bu şekilde, birisi DB'deki tuzunuzu tehlikeye atarsa, yine de doğrudan dosyada bulunan sizde mi olur? Burada, tuzları saklamanın asla akıllıca bir fikir olmadığını okudum, ama insanların bununla ne demek istediğini hep kafamı karıştırdı.


8
Hayır. Bırakın fonksiyon tuzla ilgilensin. Çifte tuzlama size sorun çıkarır ve buna gerek yoktur.
Funk Forty Niner

Yanıtlar:


182

password_hashParolaları saklamanın önerilen yolu kullanmaktır . Bunları DB'ye ve dosyalara ayırmayın.

Diyelim ki aşağıdaki girdiye sahibiz:

$password = $_POST['password'];

Bunu yaparak önce şifreyi hashlersiniz:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

Ardından çıktıya bakın:

var_dump($hashed_password);

Gördüğünüz gibi hash edilmiş. (Bu adımları yaptığınızı varsayıyorum).

Şimdi bu karma parolayı veritabanınızda saklayarak, parola sütununuzun hash edilmiş değeri (en az 60 karakter veya daha uzun) tutacak kadar büyük olmasını sağlarsınız . Bir kullanıcı oturum açmak istediğinde, bunu yaparak veritabanındaki bu karma değerle şifre girişini kontrol edersiniz:

// Query the database for username and password
// ...

if(password_verify($password, $hashed_password)) {
    // If the password inputs matched the hashed password in the database
    // Do something, you know... log them in.
} 

// Else, Redirect them back to the login page.

Resmi Referans


2
Tamam, bunu denedim ve işe yaradı. Bu işlevden şüphe ettim çünkü neredeyse çok kolay görünüyordu. Varchar'ımın uzunluğunu ne kadar süreyle yapmamı önerirsiniz? 225?
Josh Potter

4
Bu zaten php.net/manual/en/function.password-hash.php --- php.net/manual/en/function.password-verify.php kılavuzlarında , OP muhtemelen okumadı veya anlamadı. Bu soru hiç sorulmamasından daha sık soruldu.
Funk Forty Niner

Bu farklı bir sayfadır.
Josh Potter

@ JoshPotter neyden farklı? artı, 2. sorunuza yanıt vermediklerini fark ettiler. Muhtemelen sizden kendiniz bulmanızı bekliyorlar veya bilmiyorlar.
Funk Forty Niner

8
@FunkFortyNiner, b / c Josh soruyu sordu, 2 yıl sonra buldum ve bana yardımcı oldu. SO'nun amacı budur. Bu kılavuz çamur kadar açık.
Toddmo

23

Evet, doğru anladınız, password_hash () işlevi kendi başına bir tuz oluşturacak ve bunu sonuçtaki karma değerine dahil edecek. Tuzun veri tabanında saklanması kesinlikle doğrudur, bilinse bile işini yapar.

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT);

// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);

Bahsettiğiniz ikinci tuz (bir dosyada saklanan) aslında bir biber veya sunucu tarafı anahtarıdır. Haşlama yapmadan önce eklerseniz (tuz gibi), bir biber eklersiniz. Yine de daha iyi bir yol var, önce karmayı hesaplayabilir ve daha sonra karmayı bir sunucu tarafı anahtarıyla şifreleyebilirsiniz (iki yönlü). Bu size gerektiğinde anahtarı değiştirme imkanı verir.

Tuzun aksine, bu anahtar gizli tutulmalıdır. İnsanlar genellikle onu karıştırır ve tuzu gizlemeye çalışır, ancak tuzun işini yapmasına ve sırrı bir anahtarla eklemesine izin vermek daha iyidir.


8

Evet bu doğru. Neden işlevle ilgili php sss'den şüphe duyuyorsunuz? :)

Koşmanın sonucu password_hash()dört bölümden oluşur:

  1. kullanılan algoritma
  2. parametreleri
  3. tuz
  4. gerçek şifre karması

Gördüğünüz gibi, hash bunun bir parçası.

Elbette, ek bir güvenlik katmanı için ek bir tuzunuz olabilir, ancak dürüst olmak gerekirse bunun normal bir php uygulamasında fazla olduğunu düşünüyorum. Varsayılan bcrypt algoritması iyidir ve isteğe bağlı blowfish algoritması muhtemelen daha da iyidir.


2
BCrypt bir karma işlevdir, Blowfish ise şifreleme için bir algoritmadır . BCrypt, Blowfish algoritmasından kaynaklanmaktadır.
martinstoeckli

7

Şifrenizin güvenliğini sağlamak için asla md5 () kullanmayın, tuzla bile olsa, her zaman tehlikelidir !!

Aşağıdaki gibi en son karma algoritmalarla şifrenizi güvenli hale getirin.

<?php

// Your original Password
$password = '121@121';

//PASSWORD_BCRYPT or PASSWORD_DEFAULT use any in the 2nd parameter
/*
PASSWORD_BCRYPT always results 60 characters long string.
PASSWORD_DEFAULT capacity is beyond 60 characters
*/
$password_encrypted = password_hash($password, PASSWORD_BCRYPT);

Veritabanının şifrelenmiş parolası ve kullanıcı tarafından girilen parolayla eşleştirmek için aşağıdaki işlevi kullanın.

<?php 

if (password_verify($password_inputted_by_user, $password_encrypted)) {
    // Success!
    echo 'Password Matches';
}else {
    // Invalid credentials
    echo 'Password Mismatch';
}

Kendi tuzunuzu kullanmak istiyorsanız, aynı şekilde özel oluşturulmuş işlevinizi kullanın, sadece aşağıyı izleyin, ancak PHP'nin son sürümlerinde kullanımdan kaldırıldığı için bunu önermiyorum.

Aşağıdaki kodu kullanmadan önce password_hash () hakkındaki bilgileri okuyun .

<?php

$options = [
    'salt' => your_custom_function_for_salt(), 
    //write your own code to generate a suitable & secured salt
    'cost' => 12 // the default cost is 10
];

$hash = password_hash($your_password, PASSWORD_DEFAULT, $options);

4
Tuz seçeneği, iyi nedenlerden ötürü kullanımdan kaldırılmıştır, çünkü işlev kriptografik olarak güvenli bir tuz oluşturmak için elinden gelenin en iyisini yapar ve daha iyisini yapmak neredeyse imkansızdır.
martinstoeckli

@martinstoeckli, evet haklısın, cevabımı yeni güncelledim, teşekkürler!
Mahesh Yadav

if (isset ($ _ POST ['btn-kayıt'])) {$ uname = mysql_real_escape_string ($ _ POST ['uname']); $ email = mysql_real_escape_string ($ _ POST ['email']); $ upass = md5 (mysql_real_escape_string ($ _ POST ['pass'])); Bu login.php'de kullanılan koddur .. escape ve md5 kullanmadan yapmak istiyorum. şifre karması kullanmak istiyorum ..
rashmi sm

PASSWORD_DEFAULT - bcrypt algoritmasını kullanın (PHP 5.5.0 gerektirir). Bu sabitin PHP'ye yeni ve daha güçlü algoritmalar eklendikçe zamanla değişmek üzere tasarlandığını unutmayın. Bu nedenle, bu tanımlayıcının kullanımından kaynaklanan sonucun uzunluğu zamanla değişebilir.
Adrian P.

5

PHP'nin parola işlevlerinde yerleşik olarak bulunan geriye ve ileriye dönük uyumluluk konusunda belirgin bir tartışma eksikliği vardır. Özellikle:

  1. Geriye Dönük Uyumluluk: Parola işlevleri temelde iyi yazılmış bir sarmalayıcıdır crypt()ve crypt()eski ve / veya güvenli olmayan karma algoritmalar kullansalar bile, -format karmalarıyla geriye doğru uyumludur .
  2. İleriye Dönük Uyumluluk:password_needs_rehash() Kimlik doğrulama iş akışınıza eklemek ve biraz mantık, iş akışında gelecekte potansiyel olarak sıfır değişiklikle mevcut ve gelecekteki algoritmalarla hashlerinizi güncel tutabilir. Not: Belirtilen algoritmaya uymayan herhangi bir dizge, kripto uyumlu olmayan hash'ler dahil olmak üzere, yeniden işleme ihtiyacı olduğu için işaretlenecektir.

Örneğin:

class FakeDB {
    public function __call($name, $args) {
        printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args));
        return $this;
    }
}

class MyAuth {
    protected $dbh;
    protected $fakeUsers = [
        // old crypt-md5 format
        1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'],
        // old salted md5 format
        2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'],
        // current bcrypt format
        3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO']
    ];

    public function __construct($dbh) {
        $this->dbh = $dbh;
    }

    protected function getuser($id) {
        // just pretend these are coming from the DB
        return $this->fakeUsers[$id];
    }

    public function authUser($id, $password) {
        $userInfo = $this->getUser($id);

        // Do you have old, turbo-legacy, non-crypt hashes?
        if( strpos( $userInfo['password'], '$' ) !== 0 ) {
            printf("%s::legacy_hash\n", __METHOD__);
            $res = $userInfo['password'] === md5($password . $userInfo['salt']);
        } else {
            printf("%s::password_verify\n", __METHOD__);
            $res = password_verify($password, $userInfo['password']);
        }

        // once we've passed validation we can check if the hash needs updating.
        if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) {
            printf("%s::rehash\n", __METHOD__);
            $stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?');
            $stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]);
        }

        return $res;
    }
}

$auth = new MyAuth(new FakeDB());

for( $i=1; $i<=3; $i++) {
    var_dump($auth->authuser($i, 'foo'));
    echo PHP_EOL;
}

Çıktı:

MyAuth::authUser::password_verify
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]])
bool(true)

MyAuth::authUser::legacy_hash
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]])
bool(true)

MyAuth::authUser::password_verify
bool(true)

Son bir not olarak, bir kullanıcının şifresini yalnızca oturum açma sırasında yeniden hashing uygulayabileceğiniz göz önüne alındığında, kullanıcılarınızı korumak için, güvenli olmayan eski karmaları "kullanımdan kaldırmayı" düşünmelisiniz. Bununla, belirli bir yetkisiz kullanım süresinden sonra tüm güvensiz [ör. Çıplak MD5 / SHA / aksi halde zayıf] karmaları kaldırırsınız ve kullanıcılarınızın uygulamanızın şifre sıfırlama mekanizmalarına güvenmesini sağlarsınız.


0

Sınıf Parola tam kodu:

Class Password {

    public function __construct() {}


    /**
     * Hash the password using the specified algorithm
     *
     * @param string $password The password to hash
     * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
     * @param array  $options  The options for the algorithm to use
     *
     * @return string|false The hashed password, or false on error.
     */
    function password_hash($password, $algo, array $options = array()) {
        if (!function_exists('crypt')) {
            trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
            return null;
        }
        if (!is_string($password)) {
            trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
            return null;
        }
        if (!is_int($algo)) {
            trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
            return null;
        }
        switch ($algo) {
            case PASSWORD_BCRYPT :
                // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
                $cost = 10;
                if (isset($options['cost'])) {
                    $cost = $options['cost'];
                    if ($cost < 4 || $cost > 31) {
                        trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
                        return null;
                    }
                }
                // The length of salt to generate
                $raw_salt_len = 16;
                // The length required in the final serialization
                $required_salt_len = 22;
                $hash_format = sprintf("$2y$%02d$", $cost);
                break;
            default :
                trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
                return null;
        }
        if (isset($options['salt'])) {
            switch (gettype($options['salt'])) {
                case 'NULL' :
                case 'boolean' :
                case 'integer' :
                case 'double' :
                case 'string' :
                    $salt = (string)$options['salt'];
                    break;
                case 'object' :
                    if (method_exists($options['salt'], '__tostring')) {
                        $salt = (string)$options['salt'];
                        break;
                    }
                case 'array' :
                case 'resource' :
                default :
                    trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
                    return null;
            }
            if (strlen($salt) < $required_salt_len) {
                trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
                return null;
            } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
                $salt = str_replace('+', '.', base64_encode($salt));
            }
        } else {
            $salt = str_replace('+', '.', base64_encode($this->generate_entropy($required_salt_len)));
        }
        $salt = substr($salt, 0, $required_salt_len);

        $hash = $hash_format . $salt;

        $ret = crypt($password, $hash);

        if (!is_string($ret) || strlen($ret) <= 13) {
            return false;
        }

        return $ret;
    }


    /**
     * Generates Entropy using the safest available method, falling back to less preferred methods depending on support
     *
     * @param int $bytes
     *
     * @return string Returns raw bytes
     */
    function generate_entropy($bytes){
        $buffer = '';
        $buffer_valid = false;
        if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
            $buffer = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
            if ($buffer) {
                $buffer_valid = true;
            }
        }
        if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
            $buffer = openssl_random_pseudo_bytes($bytes);
            if ($buffer) {
                $buffer_valid = true;
            }
        }
        if (!$buffer_valid && is_readable('/dev/urandom')) {
            $f = fopen('/dev/urandom', 'r');
            $read = strlen($buffer);
            while ($read < $bytes) {
                $buffer .= fread($f, $bytes - $read);
                $read = strlen($buffer);
            }
            fclose($f);
            if ($read >= $bytes) {
                $buffer_valid = true;
            }
        }
        if (!$buffer_valid || strlen($buffer) < $bytes) {
            $bl = strlen($buffer);
            for ($i = 0; $i < $bytes; $i++) {
                if ($i < $bl) {
                    $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
                } else {
                    $buffer .= chr(mt_rand(0, 255));
                }
            }
        }
        return $buffer;
    }

    /**
     * Get information about the password hash. Returns an array of the information
     * that was used to generate the password hash.
     *
     * array(
     *    'algo' => 1,
     *    'algoName' => 'bcrypt',
     *    'options' => array(
     *        'cost' => 10,
     *    ),
     * )
     *
     * @param string $hash The password hash to extract info from
     *
     * @return array The array of information about the hash.
     */
    function password_get_info($hash) {
        $return = array('algo' => 0, 'algoName' => 'unknown', 'options' => array(), );
        if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
            $return['algo'] = PASSWORD_BCRYPT;
            $return['algoName'] = 'bcrypt';
            list($cost) = sscanf($hash, "$2y$%d$");
            $return['options']['cost'] = $cost;
        }
        return $return;
    }

    /**
     * Determine if the password hash needs to be rehashed according to the options provided
     *
     * If the answer is true, after validating the password using password_verify, rehash it.
     *
     * @param string $hash    The hash to test
     * @param int    $algo    The algorithm used for new password hashes
     * @param array  $options The options array passed to password_hash
     *
     * @return boolean True if the password needs to be rehashed.
     */
    function password_needs_rehash($hash, $algo, array $options = array()) {
        $info = password_get_info($hash);
        if ($info['algo'] != $algo) {
            return true;
        }
        switch ($algo) {
            case PASSWORD_BCRYPT :
                $cost = isset($options['cost']) ? $options['cost'] : 10;
                if ($cost != $info['options']['cost']) {
                    return true;
                }
                break;
        }
        return false;
    }

    /**
     * Verify a password against a hash using a timing attack resistant approach
     *
     * @param string $password The password to verify
     * @param string $hash     The hash to verify against
     *
     * @return boolean If the password matches the hash
     */
    public function password_verify($password, $hash) {
        if (!function_exists('crypt')) {
            trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
            return false;
        }
        $ret = crypt($password, $hash);
        if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
            return false;
        }

        $status = 0;
        for ($i = 0; $i < strlen($ret); $i++) {
            $status |= (ord($ret[$i]) ^ ord($hash[$i]));
        }

        return $status === 0;
    }

}

0

Her zaman parola doğrulama ve parola oluşturmak için kullandığım bir işlev oluşturdum, örneğin bunları bir MySQL veritabanında saklamak için. Statik tuz kullanmaktan çok daha güvenli olan rastgele oluşturulmuş bir tuz kullanır.

function secure_password($user_pwd, $multi) {

/*
    secure_password ( string $user_pwd, boolean/string $multi ) 

    *** Description: 
        This function verifies a password against a (database-) stored password's hash or
        returns $hash for a given password if $multi is set to either true or false

    *** Examples:
        // To check a password against its hash
        if(secure_password($user_password, $row['user_password'])) {
            login_function();
        } 
        // To create a password-hash
        $my_password = 'uber_sEcUrE_pass';
        $hash = secure_password($my_password, true);
        echo $hash;
*/

// Set options for encryption and build unique random hash
$crypt_options = ['cost' => 11, 'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)];
$hash = password_hash($user_pwd, PASSWORD_BCRYPT, $crypt_options);

// If $multi is not boolean check password and return validation state true/false
if($multi!==true && $multi!==false) {
    if (password_verify($user_pwd, $table_pwd = $multi)) {
        return true; // valid password
    } else {
        return false; // invalid password
    }
// If $multi is boolean return $hash
} else return $hash;

}

6
saltParametreyi atlamak en iyisidir, en iyi uygulamalar izlenerek password_hash () işlevi tarafından otomatik olarak oluşturulur . Bunun yerine, gelecekteki kanıt kodunu yazmak PASSWORD_BCRYPTiçin kullanılabilir PASSWORD_DEFAULT.
martinstoeckli

Bu tavsiye için teşekkürler. Belgelerde onu denetlemiş olmalıyım. Uzun geceler oldu.
Gerrit Fries

1
Secure.php.net/manual/en/function.password-hash.php'ye göre "Tuz seçeneği PHP 7.0.0'dan itibaren kullanımdan kaldırılmıştır. Artık varsayılan olarak oluşturulan tuzun kullanılması tercih edilmektedir."
jmng
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.