PHP Manual
/
Безпека

Хешування рядків і паролів

11. 09. 2019

Obsah článku

Процес хешування (на відміну від шифрування) призводить до того, що на виході з вхідних даних отримується такий рядок, з якого вже неможливо відновити вихідний рядок.

Тому він добре підходить для захисту конфіденційних рядків, паролів і контрольних сум.

Ще однією приємною особливістю функцій хешування є те, що вони завжди генерують вихідні дані однакової довжини, а невелика зміна на вході завжди повністю змінює весь вихід.

Функції хешування

У PHP є багато хеш-функцій, ось найважливіші з них:

  • Bcrypt: password_hash() - найбезпечніше хешування паролів, повільне в обчисленнях, використовує внутрішню сіль і хешує ітеративно.
  • md5() - дуже швидка функція для хешування файлів. Вихідні дані завжди складаються з 32 символів.
  • sha1() - Швидка хеш-функція для хешування файлів, використовується всередині Git'а для хешування коммітів. На виході завжди 40 символів.

Хешування

$password = 'секретний пароль';
echo password_hash($password); // Bcrypt
echo md5($password);
echo sha1($password);

Попередження:** Ані md5(), ані sha1() не підходять для хешування паролів, оскільки за їх допомогою легко розкрити оригінальний пароль або, принаймні, попередньо обчислити паролі. Набагато краще використовувати bcrypt, який був розроблений для хешування паролів.

На сайті md5cracker.com є база даних контрольних сум (хешів), спробуйте пошукати хеш: 79c2b46ce2594ecbcb5b73e928345492, як бачите, такий чистий md5() не настільки безпечний для звичайних слів і паролів.

Єдине правильне рішення: "Bcrypt + сіль

У доповіді Як не помилитися в цільовій площині Девід Градл розповів про те, як правильно хешувати та зберігати паролі.

Єдине правильне рішення: "Шифр + сіль".

А саме:

$password = 'хеш';
// Генерує безпечний хеш
echo password_hash($password, PASSWORD_BCRYPT);
// Альтернативний варіант з більшою складністю (за замовчуванням 10):
echo password_hash($password, PASSWORD_BCRYPT, ['вартість' => 12]);

Перевага Bcryp полягає, головним чином, у швидкості та автоматичному посолі.

Той факт, що для генерації, скажімо, 100 мс потрібно довго, робить тестування багатьох паролів дуже дорогим для зловмисника.

Крім того, вихідний хеш автоматично обробляється випадковою сіллю, що означає, що при багаторазовому хешуванні одного і того ж пароля на виході завжди виходить різний хеш. Таким чином, зловмисник не зможе використати заздалегідь розраховану хеш-таблицю.

Тому ми не зможемо перевірити правильність пароля шляхом багаторазового хешування, а будемо змушені викликати спеціалізовану функцію:

if (password_verify($password, $hash)) {
// Пароль правильний
} else {
// Неправильний пароль
}

Підбір пароля

Для того, щоб ускладнити злом хешу, доцільно вставити додатковий рядок у вихідні дані. В ідеалі - випадковий. Цей процес називається засолювання паролем.

Безпека базується на тому, що зловмисник не зможе скористатися заздалегідь розрахованою таблицею паролів та хешів, оскільки не знатиме "солі" і буде змушений зламувати паролі індивідуально.

Наприклад:

$password = 'секретний_паспорт';
$salt = 'fghjgtzjhg';
$hash = md5($password . $salt);
echo $password; // виводить оригінальний пароль
echo $hash; // виводить хеш паролю з урахуванням солі

Складені хеш-функції

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

Наприклад:

$password = 'пароль';
for ($i = 0; $i <= 1000; $i++) {
$password = md5($password);
}
echo $password; // 1000x хешування через md5()

Парадоксально, але складність прориву зменшується або залишається майже незмінною.

Причина в тому, що функція md5() надзвичайно швидка і на звичайному комп'ютері може обчислити понад мільйон хешів за секунду, тому перебір паролів по черзі не сильно сповільнює роботу.

Друга причина - це більше теорія, а саме можливість наштовхнутися на так зване зіткнення. Якщо ми хешуємо пароль багаторазово, то з часом може статися так, що ми потрапимо на хеш, який зловмисник вже знає, і це дозволить йому хешувати пароль, використовуючи базу даних.

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

Надзвичайно безпечне порівняння двох хешів/рядків

Чи знаєте ви, що оператор === не є найбезпечнішим вибором для порівняння хешів при перевірці паролів?

При порівнянні рядків він перебирає два рядки символ за символом до тих пір, поки не дійде до кінця (успіх, вони однакові) або не знайде різниці (рядки різні).

І це може бути використано в атаці. Якщо виміряти час досить точно, то можна оцінити, скільки ще символів залишилося додати, щоб отримати точний збіг і дійти до кінця, або можна оцінити, наскільки далеко пройшли рядки при порівнянні рядків.

Рішення полягає у використанні функції hash_equals() скрізь, де порівнюються рядки, і було б важливо, якби зловмисник міг дізнатися позицію, де порівняння не відбулося.

І як функція це робить? Він гарантує, що порівняння будь-яких 2 струн завжди займає однакову кількість часу, так що ви не можете сказати, вимірявши час, де сталася різниця. Я вважаю, що деякі види атак дійсно дуже малоймовірні і їх важко реалізувати. Це одна з них.

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
5.
Status:
All systems normal.
2024