bcrypt — parolni hash qilish, solishtirish

~15 daq · Express · Autentifikatsiya va Xavfsizlik

Muammodan boshlaylik

Foydalanuvchi ro'yxatdan o'tdi, parolini ma'lumotlar bazasiga saqlamoqchisiz. Qanday saqlaysiz?

// Hech qachon bunday qilmang:
await pool.query('INSERT INTO users (email, password) VALUES ($1, $2)', [email, password]);
// password = 'qwerty123' — ochiq matn

Ma'lumotlar bazasi buzilsa — hamma parollar oshkor bo'ladi. Foydalanuvchilar ko'pincha bir xil parolni boshqa saytlarda ham ishlatadi — ularning boshqa hisoblariga ham kirish mumkin bo'ladi.

To'g'ri yechim — parolni hash qilib saqlash.


Hash nima, bcrypt nima?

Hash — bir tomonlama funksiya. Paroldan hash hosil qilish oson, lekin hashdan parolni tiklash — amalda imkonsiz.

'qwerty123'  →  bcrypt  →  '$2b$10$...'   ← saqlanadi
'$2b$10$...' →    ???   →  'qwerty123'    ← imkonsiz

Nima uchun oddiy SHA256 emas? SHA256 juda tez — hacker sekundiga milliardlab urinish qila oladi. Bcrypt esa ataylab sekin qilingan. Sekundiga faqat bir necha o'n urinish mumkin. Bu brute force hujumini amalda imkonsiz qiladi.

Salt — bcrypt ning yana bir xususiyati. Har hash yaratilganda tasodifiy qo'shimcha (salt) qo'shiladi. Ikki foydalanuvchi bir xil parol ishlatsa ham — hashlari har xil bo'ladi. Bu "rainbow table" hujumlaridan himoya qiladi.

npm install bcrypt

Parolni hash qilish

const bcrypt = require('bcrypt');

const SALT_ROUNDS = 10; // qancha sekin bo'lishi — 10 standart qiymat

const hash = await bcrypt.hash('qwerty123', SALT_ROUNDS);
// '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy'

SALT_ROUNDS — "cost factor". Har birlik oshirilganda hisoblash 2 baravar sekinlashadi. 10 — standart, zamonaviy serverda ~100 ms. 12 — ~400 ms. Sekinroq — xavfsizroq, lekin server zo'riqadi.


Parolni solishtirish

Login qilayotganda foydalanuvchi parolini ma'lumotlar bazasidagi hash bilan solishtirish kerak. Lekin hashdan parolni tiklash imkonsiz — qanday solishtirish mumkin?

bcrypt.compare — parolni qayta hash qilib, saqlangan hash bilan solishtiradi. Salt hash ichida saqlanadi — bcrypt uni o'zi o'qiydi.

const isValid = await bcrypt.compare('qwerty123', storedHash);
// true  — parol to'g'ri
// false — parol noto'g'ri

Ro'yxatdan o'tish va login

// Ro'yxatdan o'tish
router.post('/register', async (req, res, next) => {
  try {
    const { name, email, password } = req.body;

    const passwordHash = await bcrypt.hash(password, 10);

    const result = await pool.query(
      'INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3) RETURNING id, name, email',
      [name, email, passwordHash]
    );

    res.status(201).json(result.rows[0]);
  } catch (err) {
    if (err.code === '23505') {
      return res.status(409).json({ message: 'Bu email allaqachon ro\'yxatdan o\'tgan' });
    }
    next(err);
  }
});

// Login
router.post('/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;

    const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
    const user   = result.rows[0];

    // Foydalanuvchi topilmasa yoki parol noto'g'ri bo'lsa — bir xil xabar
    // Nima uchun? "Email topilmadi" deb yozsak — hacker qaysi emaillar mavjudligini bilib oladi
    if (!user || !(await bcrypt.compare(password, user.password_hash))) {
      return res.status(401).json({ message: 'Email yoki parol noto\'g\'ri' });
    }

    const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' });
    res.json({ token });
  } catch (err) {
    next(err);
  }
});

Tez-tez uchraydigan xatolar

SALT_ROUNDS ni juda kichik qo'yish:

10 dan kam qo'yilmaydi. Production da 12 tavsiya etiladi. Kichik bo'lsa — hacker tez sinab ko'ra oladi.

bcrypt.compare o'rniga hash solishtirish:

// Xato — salt har xil bo'lgani uchun bir xil parolning hashlari har xil:
const newHash = await bcrypt.hash(password, 10);
if (newHash === storedHash) { ... } // hech qachon true bo'lmaydi!

// To'g'ri:
const isValid = await bcrypt.compare(password, storedHash);

Parolni ochiq yoki SHA256 bilan saqlash:

SHA256 juda tez — brute force uchun mos. Parol uchun faqat bcrypt, argon2 yoki scrypt ishlatiladi.


Xulosa

Parolni hech qachon ochiq saqlamang. Bcrypt — ataylab sekin, salt bilan — brute force va rainbow table hujumlaridan himoya.

bcrypt.hash(password, 10) — hash yaratish. bcrypt.compare(password, hash) — tekshirish. SALT_ROUNDS = 10 — standart, production da 12.


Keyingi dars

22-dars: Passport.js — local strategiya, JWT strategiya