Parollar — password_hash, password_verify, ro'yxatdan o'tish
Muammodan boshlaylik
Saytingiz bazasi o'g'irlandi. Bu har kuni kimningdir boshiga tushadi — SQL injection, eskirgan server, o'g'irlangan backup fayl, norozi xodim.
Agar foydalanuvchilar jadvalida parollar ochiq saqlangan bo'lsa:
| email | parol |
|-----------------|--------------|
| ali@mail.uz | Ali1995! |
| malika@mail.uz | qwerty123 |
Hujumchi nafaqat sizning saytingizga kiradi. Odamlarning ko'pchiligi bir xil parolni hamma joyda ishlatadi. Endi hujumchi ularning pochtasi, Telegram i, bank ilovasiga ham kiradi. Sizning xatoyingiz — ularning halokati.
Qoida: parol hech qachon, hech qanday ko'rinishda ochiq saqlanmaydi. Hatto admin ham uni ko'ra olmasligi kerak.
Xeshlash nima?
Xesh — bir tomonlama funksiya: paroldan "barmoq izi" yasaydi, lekin barmoq izidan parolni tiklab bo'lmaydi.
"qwerty123" → $2y$12$Kx8f2...Lq9W (har safar boshqacha, 60 belgi)
Login paytida biz parolni "ochmaymiz" — kiritilgan parolni ham xeshlab, natijalarni solishtiramiz.
Hayotiy o'xshatma: go'sht maydalagich. Go'shtdan qiyma qilish oson. Qiymadan go'shtni qaytarib yig'ish — imkonsiz. Lekin bir xil go'shtni bir xil usulda maydalasangiz — bir xil qiyma chiqadi.
password_hash — xeshlash
<?php
$xesh = password_hash('qwerty123', PASSWORD_DEFAULT);
echo $xesh;
// $2y$12$Y3oGRsQd5k2NqH8nVhE7FOj6K0aD3.rQ1nHbKJvM3sLxX9p2wTq0e
Xesh ichida hamma narsa bor:
$2y$ 12$ Y3oGRsQd5k2NqH8nVhE7FO j6K0aD3.rQ1nHbKJvM3sLxX9p2wTq0e
│ │ │ │
│ │ tuz (salt) — tasodifiy natija
│ qiyinlik (cost)
algoritm (bcrypt)
Ikki muhim xususiyat:
- Tuz (salt) — har safar yangi tasodifiy qiymat. Bir xil parol ikki marta xeshlansa ham turli natija chiqadi. Hujumchi oldindan tayyorlangan "parol → xesh" jadvallaridan foydalana olmaydi.
- Sekinlik — bcrypt ataylab sekin (~0.1-0.3 soniya). Foydalanuvchi uchun sezilmaydi, lekin hujumchi milliardlab parolni sinab ko'rishi uchun yuzlab yillar kerak bo'ladi.
password_verify — tekshirish
<?php
$xesh = '$2y$12$Y3oGRsQd5k2NqH8nVhE7FOj6K0aD3.rQ1nHbKJvM3sLxX9p2wTq0e';
var_dump(password_verify('qwerty123', $xesh)); // bool(true)
var_dump(password_verify('qwerty124', $xesh)); // bool(false)
password_verify xeshdan algoritm, cost va tuzni o'zi o'qiydi. Sizga hech narsa kerak emas.
[!WARNING] Hech qachon
md5(),sha1(),sha256yokihash()bilan parol xeshlamang. Ular tez ishlash uchun yaratilgan — oddiy videokarta sekundiga milliardlab MD5 hisoblaydi. Internetdagi "PHP login" darsliklarining ko'pchiligi hali hammd5($parol)ni ko'rsatadi — bu 2004-yilgi usul.
Jadval
CREATE TABLE foydalanuvchilar (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ism VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
parol_hash VARCHAR(255) NOT NULL,
yaratilgan DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
email ... UNIQUE— bir email bilan ikki marta ro'yxatdan o'tib bo'lmaydi (baza o'zi tekshiradi)parol_hash VARCHAR(255)— hozir 60 belgi, lekin kelajakdagi algoritmlar uzunroq bo'lishi mumkin
Ro'yxatdan o'tish
<?php
declare(strict_types=1);
session_start();
require __DIR__ . '/../src/db.php';
require __DIR__ . '/../src/yordamchi.php';
$xatolar = [];
$ism = $email = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ism = trim($_POST['ism'] ?? '');
$email = mb_strtolower(trim($_POST['email'] ?? ''));
$parol = $_POST['parol'] ?? '';
$takror = $_POST['parol_takror'] ?? '';
if (mb_strlen($ism) < 2) {
$xatolar[] = "Ism kamida 2 belgi";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$xatolar[] = "Email noto'g'ri";
}
if (strlen($parol) < 8) {
$xatolar[] = "Parol kamida 8 belgi";
}
if ($parol !== $takror) {
$xatolar[] = "Parollar mos emas";
}
if (!$xatolar) {
try {
$stmt = db()->prepare(
'INSERT INTO foydalanuvchilar (ism, email, parol_hash) VALUES (?, ?, ?)'
);
$stmt->execute([$ism, $email, password_hash($parol, PASSWORD_DEFAULT)]);
session_regenerate_id(true);
$_SESSION['user_id'] = (int) db()->lastInsertId();
yonaltir('/profil.php');
} catch (PDOException $e) {
if ($e->errorInfo[1] === 1062) { // MySQL: Duplicate entry
$xatolar[] = "Bu email bilan allaqachon ro'yxatdan o'tilgan";
} else {
throw $e;
}
}
}
}
?>
<form method="post">
<?php foreach ($xatolar as $x): ?><p style="color:red"><?= e($x) ?></p><?php endforeach; ?>
<input name="ism" value="<?= e($ism) ?>" placeholder="Ism">
<input name="email" type="email" value="<?= e($email) ?>" placeholder="Email">
<input name="parol" type="password" placeholder="Parol">
<input name="parol_takror" type="password" placeholder="Parolni takrorlang">
<button>Ro'yxatdan o'tish</button>
</form>
Email bandligini avval SELECT bilan tekshirish o'rniga — UNIQUE cheklovga tayanamiz va 1062 xatosini ushlaymiz. Nima uchun? Ikki odam bir soniyada bir xil email bilan yuborsa, SELECT ikkalasiga ham "bo'sh" deydi. UNIQUE esa bazaning o'zida — uni aylanib o'tib bo'lmaydi.
Kirish (login)
<?php
declare(strict_types=1);
session_start();
require __DIR__ . '/../src/db.php';
require __DIR__ . '/../src/yordamchi.php';
$xato = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = mb_strtolower(trim($_POST['email'] ?? ''));
$parol = $_POST['parol'] ?? '';
$stmt = db()->prepare('SELECT id, parol_hash FROM foydalanuvchilar WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($parol, $user['parol_hash'])) {
// Algoritm yoki cost yangilangan bo'lsa — xeshni yangilash
if (password_needs_rehash($user['parol_hash'], PASSWORD_DEFAULT)) {
db()->prepare('UPDATE foydalanuvchilar SET parol_hash = ? WHERE id = ?')
->execute([password_hash($parol, PASSWORD_DEFAULT), $user['id']]);
}
session_regenerate_id(true);
$_SESSION['user_id'] = (int) $user['id'];
yonaltir('/profil.php');
}
$xato = "Email yoki parol noto'g'ri";
}
Nima uchun "Email yoki parol noto'g'ri"?
"Bunday email topilmadi" va "Parol noto'g'ri" deb alohida xabar bersangiz — hujumchi qaysi emaillar saytingizda ro'yxatdan o'tganini bilib oladi. Keyin aynan shu emaillarga hujum qiladi yoki fishing xat yuboradi. Umumiy xabar — hech qanday ma'lumot bermaydi.
password_needs_rehash
Yillar o'tib kompyuterlar tezlashadi. PHP yangi versiyasida PASSWORD_DEFAULT ning cost i oshadi yoki algoritmi o'zgaradi. password_needs_rehash foydalanuvchi navbatdagi marta kirganda (shu paytda bizda ochiq parol bor) xeshini avtomatik yangilaydi. Hech kimdan parolni qayta so'rash shart emas.
Brute force dan himoya
Hujumchi bitta emailga daqiqasiga minglab parol sinab ko'rishi mumkin. Oddiy himoya — urinishlarni cheklash:
CREATE TABLE kirish_urinishlari (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
ip VARCHAR(45) NOT NULL,
vaqt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX (email, vaqt)
);
<?php
$stmt = db()->prepare('
SELECT COUNT(*) FROM kirish_urinishlari
WHERE email = ? AND vaqt > NOW() - INTERVAL 15 MINUTE
');
$stmt->execute([$email]);
if ($stmt->fetchColumn() >= 5) {
$xato = "Juda ko'p urinish. 15 daqiqadan keyin qayta urinib ko'ring.";
} else {
// ... login tekshiruvi; muvaffaqiyatsiz bo'lsa — urinishni yozib qo'yish:
db()->prepare('INSERT INTO kirish_urinishlari (email, ip) VALUES (?, ?)')
->execute([$email, $_SERVER['REMOTE_ADDR']]);
}
Nima uchun shunday?
Nima uchun xeshlash algoritmi ataylab sekin bo'lishi kerak — odatda dasturlashda tezlikka intilmaymizmi?
Chunki bu yerda tezlik hujumchiga xizmat qiladi. Foydalanuvchi bir marta login qiladi — 0.2 soniya sezilmaydi. Hujumchi esa o'g'irlangan bazadagi har bir xesh uchun millionlab parolni sinaydi. SHA-256 bilan bu bir necha soatlik ish. bcrypt bilan esa xuddi shu ish ming yillar talab qiladi. cost parametri esa yillar o'tib, kompyuterlar tezlashgani sari sekinlikni oshirish imkonini beradi.
Xulosa
Eslab qoling:
- Parol hech qachon ochiq saqlanmaydi
password_hash($parol, PASSWORD_DEFAULT)— saqlashpassword_verify($parol, $xesh)— tekshirishmd5,sha1,sha256— parol uchun yaroqsizparol_hash VARCHAR(255),email UNIQUE- Login xatosi: "Email yoki parol noto'g'ri" — qaysi biri ekanini aytmang
password_needs_rehash— xeshlarni avtomatik yangilash- Urinishlar sonini cheklang
Keyingi dars
39-dars: Composer — paketlar, composer.json, vendor/autoload.php