Как использовать хэш-пароль PHP для хэширования и проверки паролей

php salt password-hash php-password-hash


Недавно я пытался реализовать свою собственную безопасность в сценарии входа в систему, на который я наткнулся в Интернете. После попыток узнать, как создать свой собственный скрипт для генерации соли для каждого пользователя, я наткнулся на password_hash .

Из того, что я понимаю (основываясь на чтении на этой странице ), соль уже генерируется в строке, когда вы используете password_hash . Это правда?

У меня был еще один вопрос,не будет ли это умно,если у меня будет 2 соли? Одна прямо в файле,а другая в БД? Таким образом,если кто-то компрометирует вашу соль в БД,у вас все еще есть та,что прямо в файле? Я читал здесь,что хранение соли никогда не было умной идеей,но это всегда сбивало меня с толку,что люди имели в виду под этим.




Answer 1 Akar


Использование password_hash является рекомендуемым способом хранения паролей. Не разделяйте их на БД и файлы.

Допустим,у нас есть следующие данные:

$password = $_POST['password'];

Я не проверяю входные данные только для того,чтобы понять концепцию.

Сначала ты хэшируешь пароль,делая это:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

Тогда посмотрите на выход:

var_dump($hashed_password);

Как вы видите,он хэширован.(Я предполагаю,что вы сделали эти шаги).

Теперь вы храните этот hashed_password в своей базе данных,гарантируя,что столбец пароля достаточно большой,чтобы содержать хэш-значение (не менее 60 символов и более).Когда пользователь просит его войти в систему,вы проверяете ввод пароля с этим хэш-значением в базе данных,делая это:

// 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.

Официальная справка




Answer 2 martinstoeckli


Да,вы правильно поняли,функция password_hash()сама сгенерирует соль и включит ее в результирующее хэш-значение.Хранение соли в базе данных абсолютно корректно,она делает свою работу,даже если она известна.

// 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);

Вторая соль,которую вы упомянули (та,что хранится в файле),на самом деле является перцем или ключом на стороне сервера.Если вы добавляете его перед хешированием (как соль),то добавляете перец.Есть и лучший способ,однако,сначала вычислить хэш,а затем зашифровать (двусторонним)хэш с помощью ключа на стороне сервера.Это дает вам возможность изменять ключ при необходимости.

В отличие от соли,этот ключ должен храниться в секрете.Люди часто путают его и пытаются спрятать соль,но лучше дать соли сделать свою работу и добавить секрет с помощью ключа.




Answer 3 Joel Hinz


Да,это правда.Почему ты сомневаешься в том,что php-фактор работает? :)

Результат запуска password_hash() состоит из четырех частей:

  1. используемый алгоритм
  2. parameters
  3. salt
  4. фактический хэш пароля

Так что,как вы видите,гашиш-это его часть.

Конечно,вы могли бы получить дополнительную соль для дополнительного уровня безопасности,но я честно думаю,что это излишество в обычном php-приложении.Алгоритм bcrypt по умолчанию хорош,а опциональный минет,возможно,даже лучше.




Answer 4 Mahesh Yadav


Никогда не используйте md5()для защиты пароля,даже с солью,это всегда опасно!!!

Сделайте свой пароль защищенным с помощью новейших алгоритмов хэширования,как показано ниже.

<?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);

?>

Для сопоставления с зашифрованным паролем базы данных и паролем,введенным пользователем,используйте следующую функцию.

<?php 

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

?>

Если вы хотите использовать свою собственную соль,используйте для этого специальную сгенерированную функцию,просто следуйте инструкциям ниже,но я не рекомендую это делать,так как она найдена устаревшей в последних версиях PHP.

Прочтите этот http://php.net/manual/en/function.password-hash.php перед использованием приведенного ниже кода.

<?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);

?>

Надеюсь,это все поможет!




Answer 5 Sammitch


Отмечается явное отсутствие обсуждения обратной и обратной совместимости,которая встроена в функции паролей PHP.Примечательно:

  1. Обратная совместимость: функции пароля по сути являются хорошо написанной оболочкой для crypt() и по своей природе обратно совместимы с хэш-форматами crypt() , даже если они используют устаревшие и / или небезопасные алгоритмы хеширования.
  2. Вперед Совместимость: вставка password_needs_rehash() и немного логики в ваш рабочий процесс аутентификации может поддерживать ваши хеш-коды в актуальном состоянии с текущими и будущими алгоритмами с потенциально нулевым будущим изменением рабочего процесса. Примечание. Любая строка, которая не соответствует указанному алгоритму, будет помечена для необходимости перефразирования, включая несовместимые с шифрованием хэши.

Eg:

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;
}

Output:

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)

В качестве последнего замечания,учитывая,что вы можете только перезашивать пароль пользователя при входе в систему,вы должны учитывать "закатные" незащищенные хэши наследства для защиты ваших пользователей.Под этим я подразумеваю,что после определенного льготного периода вы удалите все небезопасные [например:пустые MD5/SHA/небезопасные]хэши и заставите ваших пользователей полагаться на механизмы сброса паролей вашего приложения.