crypto — hash, hmac, randomBytes

~15 daq · Node.js · Core Modullar

Muammodan boshlaylik

Foydalanuvchi parolini ma'lumotlar bazasiga saqlashingiz kerak. Qanday saqlaysiz?

// Xato — hech qachon bunday qilmang:
db.save({ username: 'ali', password: 'qwerty123' });

Ma'lumotlar bazasi buzilsa — hamma parollar ochiq ko'rinadi. Millionlab foydalanuvchi zarar ko'radi.

To'g'ri usul — parolni hash qilib saqlash. Hash — bir tomonlama funksiya. Paroldan hash hosil qilish oson, lekin hashdan parolni tiklash — amalda imkonsiz.

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

crypto moduli aynan shu va boshqa kriptografik operatsiyalar uchun.


crypto moduli nima?

crypto — Node.js ning o'rnatilgan kriptografiya moduli. Hash, HMAC, shifrlash, tasodifiy sonlar — bularning hammasi shu yerda. Alohida o'rnatish shart emas.

const crypto = require('crypto');

Hash nima va qanday ishlaydi?

Hash — ixtiyoriy uzunlikdagi ma'lumotni qat'iy uzunlikdagi belgilar ketma-ketligiga aylantiruvchi funksiya.

'salom'          →  SHA256  →  'f4d8b2a1...' (64 belgi)
'salom!'         →  SHA256  →  '9c3e7f2b...' (64 belgi)  ← bitta ! farq, natija butunlay boshqa
'Bu juda uzun...' → SHA256  →  'a1b2c3d4...' (64 belgi)  ← har doim 64 belgi

Hash ning xususiyatlari:

  • Bir tomonlama — hashdan asl ma'lumotni tiklash imkonsiz
  • Deterministik — bir xil kirish har doim bir xil hash beradi
  • Avalanche effect — kichik o'zgarish butunlay boshqa hash beradi
  • Qat'iy uzunlik — kirish qanchalik katta bo'lmasin, natija bir xil uzunlikda

createHash — hash hosil qilish

const crypto = require('crypto');

const hash = crypto
  .createHash('sha256')       // algoritm tanlash
  .update('salom dunyo')      // ma'lumot berish
  .digest('hex');             // natijani hex formatda olish

console.log(hash);
// 'b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576...'

Algoritmlar:

  • md5 — tez, lekin xavfsiz emas (parol uchun ishlatmang)
  • sha1 — eskirgan, xavfsiz emas
  • sha256 — keng ishlatiladi, xavfsiz
  • sha512 — sha256 dan kuchliroq, lekin sekinroq

Parol uchun sha256 ham yetarli emas — bu haqda pastda.

digest formatlari:

.digest('hex')    // '5d41402abc4b...'  ← 16-lik, eng ko'p ishlatiladi
.digest('base64') // 'XUFAKrxLKna5cZ...' ← base64, qisqaroq
.digest('binary') // xom baytlar

Fayl integrity tekshirish

Hash ning keng tarqalgan ishlatilishi — fayl o'zgarmaganligini tekshirish. Siz fayl yuklab oldingiz. U to'liq va buzilmagan ekanini qanday bilasiz?

const crypto = require('crypto');
const fs     = require('fs');

function fileHash(filePath) {
  return new Promise((resolve, reject) => {
    const hash   = crypto.createHash('sha256');
    const stream = fs.createReadStream(filePath);

    stream.on('data', chunk => hash.update(chunk));
    stream.on('end',  ()    => resolve(hash.digest('hex')));
    stream.on('error', err  => reject(err));
  });
}

const hash = await fileHash('setup.exe');
console.log(hash);
// '3b4c5d...'  ← bu qiymatni sayt da ko'rsatilgan bilan solishtirish mumkin

Stream bilan ishlash muhim — katta faylni xotiraga yuklamasdan hash hisoblanadi.


Parol uchun hash — nima uchun sha256 yetarli emas?

Sha256 tez ishlaydi — bu uning kamchiligi. Hacker millionlab parolni sinab ko'rishi mumkin (brute force). Shuning uchun parol uchun maxsus algoritmlar ishlatiladi: bcrypt, argon2, scrypt.

Node.js da scrypt o'rnatilgan:

const crypto = require('crypto');
const util   = require('util');

const scrypt = util.promisify(crypto.scrypt);

async function hashPassword(password) {
  const salt = crypto.randomBytes(16).toString('hex'); // tasodifiy tuz
  const hash = await scrypt(password, salt, 64);       // 64 bayt hash
  return `${salt}:${hash.toString('hex')}`;            // salt va hash birga saqlanadi
}

async function verifyPassword(password, stored) {
  const [salt, hash] = stored.split(':');
  const newHash = await scrypt(password, salt, 64);
  return crypto.timingSafeEqual(
    Buffer.from(hash, 'hex'),
    newHash
  );
}

// Ishlatish:
const stored = await hashPassword('qwerty123');
console.log(stored);
// '3f8a2b...:9c4e7d...'

const isValid = await verifyPassword('qwerty123', stored);
console.log(isValid); // true

const isWrong = await verifyPassword('noto\'g\'ri', stored);
console.log(isWrong); // false

Salt nima? — Tasodifiy qo'shimcha. Ikki foydalanuvchi bir xil parol ishlatsa ham, saltlar har xil bo'lgani uchun hashlari ham har xil bo'ladi. Bu "rainbow table" hujumlaridan himoya qiladi.

timingSafeEqual nima uchun? — Oddiy === taqqoslash vaqt farqiga qarab hujumga ochiq (timing attack). timingSafeEqual har doim bir xil vaqt sarflaydi.


HMAC — xabar autentifikatsiyasi

HMAC (Hash-based Message Authentication Code) — hash va maxfiy kalit kombinatsiyasi. Xabar o'zgarmaganligini va uni kim yuborganini tekshirish uchun.

Oddiy hash bilan farqi: hash faqat ma'lumot o'zgarmaganligini tekshiradi. HMAC esa kalit ham kerak — kalitsiz HMAC ni soxtalashtirish imkonsiz.

const hmac = crypto
  .createHmac('sha256', 'maxfiy-kalit')
  .update('Ali 1000 so\'m yubordi')
  .digest('hex');

console.log(hmac);
// 'a3f8b2...'

Amaliy ishlatilishi — API so'rovlarini tekshirish:

// Server tomonida:
function signPayload(data, secret) {
  return crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(data))
    .digest('hex');
}

function verifyPayload(data, signature, secret) {
  const expected = signPayload(data, secret);
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

const SECRET  = process.env.WEBHOOK_SECRET;
const payload = { userId: 1, amount: 1000 };
const sig     = signPayload(payload, SECRET);

// Webhook kelganda tekshirish:
const isValid = verifyPayload(payload, sig, SECRET);

randomBytes — xavfsiz tasodifiy sonlar

Math.random() kriptografik jihatdan xavfsiz emas — uning natijasini taxmin qilish mumkin. Tokenlar, parollar, session ID lar uchun crypto.randomBytes ishlatiladi.

// Tasodifiy baytlar:
const bytes = crypto.randomBytes(32);
console.log(bytes.toString('hex'));
// 'a3f8b2c1...'  ← 64 belgi (32 bayt × 2)

// Tasodifiy token (URL xavfsiz):
const token = crypto.randomBytes(32).toString('base64url');
console.log(token);
// 'A3f8B2c1...'  ← 43 belgi

Amaliy ishlatilishi — email tasdiqlash tokeni:

async function createVerificationToken(userId) {
  const token   = crypto.randomBytes(32).toString('hex');
  const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 soat

  await db.save({ userId, token, expires });
  return token;
}

// Foydalanuvchiga email yuboriladi:
// https://example.com/verify?token=a3f8b2c1...

UUID yaratish

Node.js 14.17+ da crypto.randomUUID() bor:

const id = crypto.randomUUID();
console.log(id);
// '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

Tez-tez uchraydigan xatolar

Parolni sha256 bilan hash qilish:

// Xato — brute force ga ochiq:
const hash = crypto.createHash('sha256').update(password).digest('hex');

Sha256 juda tez — hacker sekundiga milliardlab urinish qila oladi. Parol uchun scrypt, bcrypt yoki argon2 ishlatiladi.

Salt ishlatmaslik:

// Xato — ikki foydalanuvchi bir xil parol ishlatsa, hashlari ham bir xil:
const hash = crypto.createHash('sha256').update(password).digest('hex');
// 'qwerty123' → har doim bir xil hash
// Hacker bir hashni cracklab, boshqalarini ham topadi

=== bilan hash taqqoslash:

// Xato — timing attack ga ochiq:
if (hash1 === hash2) { ... }

// To'g'ri:
if (crypto.timingSafeEqual(Buffer.from(hash1), Buffer.from(hash2))) { ... }

Xulosa

crypto — Node.js ning kriptografiya moduli. Asosiy uchta narsa:

Hash — ma'lumotni bir tomonlama o'zgartirish. Fayl integrity, ma'lumot tekshirish uchun. Parol uchun emas.

HMAC — kalit bilan hash. API autentifikatsiya, webhook tekshirish uchun.

randomBytes — kriptografik xavfsiz tasodifiy sonlar. Token, session ID, salt yaratish uchun.

Parol uchun — scrypt (o'rnatilgan) yoki bcrypt (npm paketi).


Keyingi dars

16-dars: EventEmitter — on, emit, once, off, custom event