Хэш, соль и ваша безопасность: почему это важно в 2025 году?
Представьте: вы написали крутой сайт, всё работает, пользователи регистрируются. Но однажды база данных утекает в сеть. Что увидят злоумышленники? Если вы храните пароли в открытом виде — это катастрофа. Если вы используете старый md5 — это почти то же самое. А если вы применили secure hash and salt — злоумышленники увидят лишь бессмысленный набор символов. В 2025 году это не просто рекомендация, а стандарт безопасности.
В этой статье мы разберем, как правильно хэшировать пароли в PHP, используя современные алгоритмы, соль и лучшие практики. Вы узнаете, почему старые методы опасны, и как перейти на безопасное хранение паролей за 10 минут.
Почему md5 и sha1 — это зло?
Многие начинающие разработчики совершают одну и ту же ошибку: используют md5($password) или sha1($password). В 2025 году это не просто плохая практика, а прямой путь к уязвимости. Почему?
- Скорость вычислений. MD5 и SHA1 созданы для скорости, а не для безопасности. Современные GPU позволяют перебирать миллиарды хэшей в секунду.
- Отсутствие соли. Если два пользователя выбрали одинаковый пароль, их хэши будут идентичны. Это позволяет использовать радужные таблицы.
- Коллизии. Для MD5 найдены практические коллизии — два разных входа дают одинаковый хэш.
К счастью, в PHP есть встроенные функции, которые решают все эти проблемы. И это не требует установки сторонних библиотек.
password_hash и password_verify: ваше секретное оружие
Начиная с PHP 5.5.0, в языке появились две волшебные функции: password_hash() и password_verify(). Они автоматически генерируют соль, используют криптостойкие алгоритмы и защищают от атак по времени.
Пример простейшего использования:
// Хэширование пароля
$password = "MySuperSecureP@ssw0rd!";
$hash = password_hash($password, PASSWORD_DEFAULT);
echo $hash; // $2y$10$...
Что здесь произошло? Функция password_hash:
- Сгенерировала случайную соль
- Выбрала оптимальный алгоритм (по умолчанию bcrypt)
- Вычислила хэш
- Вернула строку, которая содержит и хэш, и соль, и информацию об алгоритме
Проверка пароля выглядит так же просто:
// Проверка пароля
$inputPassword = "MySuperSecureP@ssw0rd!";
if (password_verify($inputPassword, $hash)) {
echo "Пароль верный!";
} else {
echo "Пароль неверный.";
}
Обратите внимание: вам не нужно хранить соль отдельно. Она уже встроена в хэш. Это и есть secure hash and salt в действии.
Какой алгоритм выбрать в 2025 году?
По умолчанию password_hash использует bcrypt. Это отличный выбор для большинства проектов. Но у нас есть и другие варианты:
| Алгоритм | Константа | Рекомендация |
|---|---|---|
| Bcrypt | PASSWORD_BCRYPT |
Отличный выбор, поддерживается везде |
| Argon2i | PASSWORD_ARGON2I |
Устойчив к атакам по сторонним каналам |
| Argon2id | PASSWORD_ARGON2ID |
Лучший выбор в 2025 году |
Argon2id — победитель конкурса PHC (Password Hashing Competition). Он устойчив как к GPU-атакам, так и к атакам по времени. Если ваш PHP собран с поддержкой Argon2 (а в PHP 8.x это уже стандарт), используйте его.
Настройка стоимости хэширования
Одна из ключевых идей secure hash and salt — сделать вычисление хэша достаточно медленным, чтобы перебор был невозможен. В bcrypt это регулируется параметром cost:
$options = [
'cost' => 12, // Чем выше, тем медленнее
];
$hash = password_hash($password, PASSWORD_BCRYPT, $options);
Для Argon2 параметры называются memory_cost, time_cost и threads:
$options = [
'memory_cost' => 1 << 17, // 128 MB
'time_cost' => 4,
'threads' => 2,
];
$hash = password_hash($password, PASSWORD_ARGON2ID, $options);
Как выбрать правильные значения? Проведите тест на вашем сервере: хэширование должно занимать 200-400 миллисекунд. Этого достаточно, чтобы замедлить атакующего, но не раздражать пользователя.
Регистрация и авторизация: полный пример
Давайте соберем всё вместе. Вот как должен выглядеть процесс регистрации с secure hash and salt:
// registration.php
function registerUser($username, $password, $pdo) {
// Проверяем, что пользователь не существует
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
return "Пользователь уже существует";
}
// Хэшируем пароль
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 1 << 17,
'time_cost' => 4,
'threads' => 2,
]);
// Сохраняем в базу
$stmt = $pdo->prepare("INSERT INTO users (username, password_hash, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$username, $hash]);
return "Регистрация успешна!";
}
А вот процесс авторизации:
// login.php
function loginUser($username, $password, $pdo) {
// Получаем хэш из базы
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user || !password_verify($password, $user['password_hash'])) {
return "Неверное имя пользователя или пароль";
}
// Пароль верный, начинаем сессию
$_SESSION['user_id'] = $user['id'];
return "Добро пожаловать!";
}
Обратите внимание: никогда не сообщайте пользователю, что