password_hash).password_verify).Ми навчилися створювати користувачів та запускати сесії. Але є одна світова проблема безпеки, яка щороку призводить до витоку мільярдів акаунтів (згадайте злами LinkedIn чи Yahoo). Це збереження паролів “як є” у базі даних (у колонці password лежить 123456). Якщо розробник так робить, і базу даних сайту викраде хакер (або навіть сам адміністратор-розробник вирішить підглянути) — всі ці люди втратять доступ не лише до вашого сайту, а й до своїх пошт та інтернет-банкінгів, бо більшість використовує один пароль скрізь. У цій лекції ми навчимось грамотно захищати дані наших майбутніх користувачів.
Код з Лекції 9, де ми перевіряли if ($_POST['password'] === '123'), або вставка сирого пароля в таблицю users — це категоричне табу для реальних проєктів (ми робили так лише в навчальних цілях для розуміння сесій).
База даних повинна бути спроєктована так, щоб НАВІТЬ ВИ (як її власник і адміністратор), глянувши в таблицю users через phpMyAdmin, не могли дізнатися справжній пароль ваших клієнтів.
password_hashХешування — це одностороння математична м’ясорубка.
Ви кидаєте туди слово qwerty, крутите ручку, і на виході отримуєте “фарш” (рядок із 60 випадкових символів: $2y$10$xyz123abc...).
Головне правило хешу: З цього “фаршу” вже ніколи і ні за яких умов неможливо математично зібрати назад початкове слово qwerty. Алгоритм працює тільки в один бік. Якщо хакер вкраде базу з хешами — вони для нього марні (якщо алгоритм надійний).
В PHP для цього є геніально проста і максимально міцна функція: password_hash(). Вона автоматично генерує додаткову “сіль” (домішку), тому навіть у двох користувачів з однаковим паролем qwerty будуть абсолютно різні кінцеві хеші!
<?php
// Пароль, який користувач ввів при РЕЄСТРАЦІЇ
$plainPassword = "MySuperSecretPassword_2024";
// Перетворюємо його у незворотний Хеш за допомогою алгоритму Bcrypt (кодується під капотом):
$hashToSave = password_hash($plainPassword, PASSWORD_DEFAULT);
// Виведе щось на зразок:
// $2y$10$M9bZ/08xHbZ7YcQ1N5W.pOC6x.HZmI5xH9X.qP.Z.8H3p.TzB/IJi
echo "Будемо зберігати в базі ЦЕ: " . $hashToSave;
?>
Зверніть увагу: Стовпець password в таблиці users (в phpMyAdmin) ОБОВ’ЯЗКОВО має мати тип VARCHAR(255), щоб цей довгий хеш туди точно помістився. Якщо ви поставите VARCHAR(20), він обріжеться посередині і користувач ніколи не зможе увійти в акаунт!
Як виглядає грамотний файл реєстрації register.php?
Він повинен прийняти email та password, перевірити, чи немає вже такого email в системі (щоб не було дублювання), ЗХЕШУВАТИ пароль і лише потім зробити SQL INSERT.
<?php
require_once 'db.php'; // Наш PDO
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['user_email'];
$rawPass = $_POST['user_password'];
// 1. Спочатку перевіряємо, чи не зайнятий Email?
$checkStmt = $pdo->prepare('SELECT id FROM users WHERE email = :mail');
$checkStmt->execute([':mail' => $email]);
// Якщо fetch() щось знайшов (НЕ false), значить такий рядок вже є!
if ($checkStmt->fetch()) {
die("Помилка: Ця електронна пошта вже зареєстрована!");
}
// 2. Хешуємо пароль (головний момент лекції)
$hashedPassword = password_hash($rawPass, PASSWORD_DEFAULT);
// 3. Зберігаємо користувача в БД (вставляємо ХЕШ, а не $rawPass)
$insertStmt = $pdo->prepare('INSERT INTO users (email, password) VALUES (:e, :p)');
$insertStmt->execute([
':e' => $email,
':p' => $hashedPassword // Відправляємо безпечний $2y$10...
]);
echo "Реєстрація успішна! Тепер можете увійти.";
}
?>
password_verify)Отже, користувач зареєструвався. В базі лежить хеш. Наступного дня він приходить на сайт і вводить у форму знову qwerty.
Але як нам перевірити, що пароль правильний, якщо “розшифрувати” хеш з бази технічно неможливо?
Тут застосовується магія: ми хешуємо те, що користувач ввів сьогодні, і порівнюємо отриманий результат з хешем, який лежить у базі з учорашнього дня!
В PHP є спеціальна функція-напарник: password_verify(сирий_текст, хеш_з_бази). Вона внутрішньо дістає “сіль” з хешу, робить математику і каже: true (відповідає) або false (бреше).
<?php
// Файл логіну: login.php
require_once 'db.php';
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$emailAttempt = $_POST['email'];
$passAttempt = $_POST['password']; // Сирий 'qwerty'
// 1. Шукаємо користувача за його Поштою
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :e');
$stmt->execute([':e' => $emailAttempt]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// 2. Якщо користувача взагалі не існує в базі
if (!$user) {
die("Користувача з такою поштою не знайдено!");
}
// 3. Користувач є. Його хеш з бази лежить у $user['password'].
// Порівнюємо сирий ввід з базисним хешем:
if (password_verify($passAttempt, $user['password'])) {
// УСПІХ! Паролі "зійшлися". Запускаємо сесію!
$_SESSION['user_id'] = $user['id'];
$_SESSION['logged_in'] = true;
header('Location: profile.php');
exit;
} else {
echo "Невірний пароль!";
}
}
?>
Bcrypt.password_hash(). Вона автоматично застосовує “Соління” пароля і генерує довжелезний специфічний хеш-рядок.password_verify(). Вона поверне true, якщо все збігається.users, при її проєктуванні, повинна мати стовпець password з довжиною мінімум VARCHAR(255).$2y...) є математично неможливою та вкрай ресурсоємною? Чим він принципово відрізняється від процесу “шифрування” (наприклад перекладу в Base64)?"password" під час створення таблиці users у phpMyAdmin? Що фізично станеться з вашим користувачем, якого ви додасте в БД, якщо ви задасте цій колонці специфікацію VARCHAR(16)?password_verify і з яких двох “аргументів” вона складається під час використання? (Де вона бере сирий порівняльний пароль, а звідки вона дістає еталонний хеш?).$registerForm). Яку додаткову “Перевірку Запитом (SELECT…)” ви маєте обов’язково зробити до того, як застосувати INSERT INTO, якщо ми не бажаємо мати два профілі з повністю однаковим Email у базі?md5($pswd)) сьогодні категорично не рекомендується в комерційних проєктах?