Mundarija (42)
- Bu darsda
- 1. Nega bu kerak?
- 1.1 Baza sizib chiqadi
- 1.2 Autentifikatsiya va avtorizatsiya
- 2. Nega oddiy hash yetmaydi
- 2.1 Rainbow table
- 2.2 Tuz yetarli emas: tezlik
- 3. Sekin hash algoritmlari
- 3.1 Uchta oila
- 3.2 argon2id parametrlari
- 3.3 NIST nima deydi
- 4. crypto.argon2 va PHC satri
- 4.1 Birinchi hash
- 4.2 PHC satrini o'qish
- 5. bcrypt tuzog'i va Unicode
- 5.1 72 bayt
- 5.2 Bir harf — ikki xil yozilish
- 6. Parametrlarni yangilash va xavfli satrlar
- 6.1 Rehash on login
- 6.2 Bazadagi satr ham xavfli bo'lishi mumkin
- 7. timingSafeEqual
- 8. Pepper: maxfiy qo'shimcha
- 9. Ko'p login birdaniga
- 9.1 Thread pool navbati
- 9.2 Event loop va xotira
- 10. Bazada: faqat hash
- 10.1 Migratsiya va CHECK
- 10.2 O'z bazangizda sinang
- 11. Hujumchi nigohi
- 12. Ko'p uchraydigan xatolar
- 13. Mashqlar
- 1-mashq (oson): to'g'ri yoki noto'g'ri
- 2-mashq (o'rta): bcrypt kesadimi?
- 3-mashq (qiyin): brauzerda PBKDF2
- 14. Loyiha qadami: parol.ts va migratsiya
- 14.1 Migratsiya va Prisma sxemasi
- 14.2 src/auth/parol.ts
- 14.3 Testlar
- 14.4 Tekshiruv va commit
- 15. Real ishda
- Xulosa
- Manbalar
Parolni xavfsiz saqlash: argon2id, tuz, PHC satri va bcrypt tuzog'i
Qisqacha: Parol bazada hech qachon ochiq yoki tez hash (SHA-256, MD5) bilan saqlanmaydi — faqat sekin, tuzli hash bilan. 2026-yilda eng yaxshi tanlov — argon2id (OWASP: m=19 MiB, t=2, p=1), Node 24 da u o'rnatilgan:
crypto.argon2. Natija PHC satri ko'rinishida saqlanadi: algoritm, parametrlar, tuz va hash bitta qatorda. Keyin parametrlarni oshirsangiz, eski hash'lar ham tekshiriladi va login paytida yangilanadi. bcrypt parolning faqat birinchi 72 baytini ko'radi — kanon buni o'lchab ko'rsatdi.
Bu darsda
- Nega tez hash parol uchun yaramasligini rainbow table misolida ko'rasiz.
- argon2id parametrlarini (m, t, p) va PHC satrini o'qiy olasiz.
- bcrypt'ning 72 bayt tuzog'ini va Unicode normallashtirishni tushuntirasiz.
- Login paytida qayta hash'lash (rehash on login) va xavfli parametrlardan himoyani yozasiz.
vazifalar-apigasrc/auth/parol.tsmodulini, migratsiyani va 6 ta testni qo'shasiz.
Oldin bilishingiz kerak: crypto 2: parolni hash'lash, shifrlash va imzo (tuz, scrypt, argon2 asoslari), crypto 1: tasodif, hash va HMAC, OWASP Top 10:2025 xaritasi, Prisma Migrate chuqur.
crypto 2 darsida parolni scrypt bilan hash qilib, tekshirishni yozgansiz va argon2id Node 24 da borligini ko'rgansiz. Bugun shu bilimni haqiqiy loyihaga olib kiramiz: vazifalar-api da foydalanuvchilar paroli paydo bo'ladi. Loyihaga kirganda yangi savollar chiqadi: qaysi parametrlar, qaysi format, eski hash'lar bilan nima qilinadi, bir vaqtda 16 kishi kirsa nima bo'ladi.
1. Nega bu kerak?
1.1 Baza sizib chiqadi
Oldingi darsda A04 Cryptographic Failures toifasini ko'rdik. Endi uning eng mashhur ko'rinishi: baza sizib chiqishi. Bu juda ko'p uchraydi — noto'g'ri zaxira nusxa, ochiq qolgan port, SQL injection, xodimning noutbuki. Savol "sizib chiqadimi?" emas, "sizib chiqqanda nima bo'ladi?".
Agar parollar ochiq yozilgan bo'lsa — hujumchi hammasini darhol o'qiydi. Undan yomoni: odamlar bir parolni ko'p joyda ishlatadi. Sizning kichik vazifalar saytingizdan chiqqan parol bilan hujumchi o'sha odamning pochtasiga, Telegram'iga, bank ilovasiga kirishga urinadi. Bu hujum credential stuffing deyiladi.
Maqsad: baza butunlay o'g'irlansa ham, parollarni tiklash juda qimmat bo'lsin.
1.2 Autentifikatsiya va avtorizatsiya
28-qismda bu ikki so'z doim yonma-yon keladi, ular 11-qismdan tanish:
- Autentifikatsiya (authentication) — "siz kimsiz?" savoliga isbot: parol, kod, barmoq izi. Pasportni ko'rsatish.
- Avtorizatsiya (authorization) — "sizga nima mumkin?": faqat o'z vazifalaringiz, admin sahifasi yo'q. Pasport ko'rilgandan keyin qaysi eshikka kirishingiz mumkinligi.
Parol — autentifikatsiyaning eng keng tarqalgan usuli. Bugun uning saqlash qismini, keyingi darsda esa ro'yxat va login oqimini quramiz.
2. Nega oddiy hash yetmaydi
2.1 Rainbow table
crypto 1 darsida SHA-256 ni o'rgangansiz: bir xil matn — doim bir xil hash. Fayl tekshirish uchun bu ajoyib. Parol uchun — halokat. Hujumchi mashhur parollarning hash'ini oldindan hisoblab qo'yadi va bazadagi hash'ni shunchaki jadvaldan qidiradi. Bunday tayyor jadvalni rainbow table deyishadi (aniqrog'i — uning soddalashtirilgan ko'rinishi).
async function sha256(matn) {
const baytlar = new TextEncoder().encode(matn);
const hash = await crypto.subtle.digest("SHA-256", baytlar);
return Array.from(new Uint8Array(hash), (b) =>
b.toString(16).padStart(2, "0")).join("");
}
// hujumchi oldindan tayyorlagan jadval: hash → parol
const MASHHUR = ["123456", "qwerty", "parol123", "Toshkent2026"];
const jadval = new Map();
for (const p of MASHHUR) jadval.set(await sha256(p), p);
// bazadan sizib chiqqan hash (tuzsiz)
const tuzsiz = await sha256("parol123");
console.log(tuzsiz.slice(0, 16), "→", jadval.get(tuzsiz));
// xuddi shu parol, lekin tasodifiy tuz bilan
const tuzli = await sha256("k3Jx9Qw2" + "parol123");
console.log(tuzli.slice(0, 16), "→", jadval.get(tuzli));Konsolda:
a203ba4e9c565ec0 → parol123
357d9e3ccedfc125 → undefinedBirinchi qator: tuzsiz hash jadvalda bor — parol bir zumda topildi. Ikkinchi qator: parol o'sha, lekin oldiga tasodifiy tuz (salt) qo'shilgan. Hujumchining jadvali endi yaroqsiz — u har foydalanuvchi uchun alohida jadval tuzishi kerak. Hash'ning faqat boshini ko'rsatdik (16 belgi) — telefon ekraniga sig'sin.
2.2 Tuz yetarli emas: tezlik
Tuz tayyor jadvalni o'ldiradi. Lekin hujumchi baribir har foydalanuvchi uchun alohida "taxmin qilib ko'rish" mumkin: tuzni oladi (u hash yonida ochiq turadi) va millionlab parolni sinaydi. Bu yerda hal qiluvchi narsa — bitta urinish qancha turadi.
Kanon o'lchovi (Node 24.21.0, i5-12500H, 2026-10-27): oddiy noutbukning bitta yadrosi 1 soniyada 149 216 ta SHA-256 hash hisobladi. Videokartada bu milliardlab. 8 belgili kichik harfli parolni bunday tezlikda bir necha daqiqada topish mumkin.
Shuning uchun parol uchun ataylab sekin algoritm kerak. Sizga bitta login uchun 30 ms sezilmaydi. Hujumchiga esa har urinish 30 ms — milliard urinish o'nlab yil.
Tekshirib ko'ring: Tuz sir emas va hash yonida ochiq turadi. Unda u nimadan himoya qiladi?
Javob
Tuzning vazifasi — bir xil parollarga har xil hash berish. Natijada:
- Tayyor jadval (rainbow table) ishlamaydi.
- Hujumchi bitta hisob bilan hamma foydalanuvchini birdan sinay olmaydi — har biriga alohida ish.
- Bazaga qarab "kimning paroli bir xil" ekanini bilib bo'lmaydi.
Buning uchun tuz faqat noyob bo'lishi kerak, sir emas.
3. Sekin hash algoritmlari
3.1 Uchta oila
Parol uchun maxsus algoritmlar — parol hash funksiyalari (password hashing function). Ular ataylab sekin va ko'pincha ko'p xotira talab qiladi:
| Algoritm | Yil | Kuchi | Holati |
|---|---|---|---|
| argon2id | 2015 | vaqt + xotira, sozlanadi | OWASP tavsiyasi №1 |
| scrypt | 2009 | vaqt + xotira | argon2id bo'lmasa |
| bcrypt | 1999 | faqat vaqt, 72 bayt chegarasi | eski tizimlar uchun |
Kanon hammasini bitta mashinada o'lchadi — 9 marta ketma-ket, mediana:
- crypto.argon2 OWASPm=19 MiB, t=2, p=132,7 ms
- argon2 paketi OWASPnative addon30 ms
- bcrypt cost 10native70,9 ms
- bcryptjs cost 10sof JS116,5 ms
- bcrypt cost 12native489,7 ms
- scrypt N=2^17OWASP, 128 MiB626,1 ms
Manba: Kanon o'lchovi (IlmHamroh, 2026-10): Node 24.21.0, OpenSSL 3.5.8, i5-12500H, Windows 11, argon2 0.45.1, bcrypt 6.0.0, bcryptjs 3.0.3, 2026-10-27
Rasmni to'g'ri o'qing: argon2id eng tez chiqdi, lekin bu "eng zaif" degani emas. Uning kuchi — xotira: har urinish 19 MiB oladi. Videokartada minglab yadro bor, lekin har biriga 19 MiB berish qimmat. bcrypt esa xotirani deyarli ishlatmaydi va 1999-yildan beri unga moslashgan maxsus qurilmalar bor.
Node'ning o'z crypto.argon2 i bilan argon2 npm paketi orasidagi farq kichik: 32.7 va 30.0 ms. Paket native C++ addon (o'rnatishda kompilyatsiya yoki tayyor binar kerak), Node'niki esa bog'liqliksiz. Kanon shuning uchun Node'nikini tanladi.
3.2 argon2id parametrlari
argon2id uchta parametr bilan sozlanadi:
| Harf | Nomi | Ma'nosi | OWASP minimal |
|---|---|---|---|
m |
memory | xotira, KiB (1 KiB = 1024 bayt) | 19 456 KiB (19 MiB) |
t |
passes | xotira bo'ylab necha marta o'tish | 2 |
p |
parallelism | parallel yo'laklar soni | 1 |
OWASP Password Storage Cheat Sheet bir nechta teng kuchli kombinatsiya beradi: m=47104 (46 MiB) va t=1, m=19456 va t=2, m=12288 va t=3 va boshqalar. Ko'p xotira bo'lsa — kam o'tish yetadi. vazifalar-api uchun kanon ikkinchisini oldi: m=19456, t=2, p=1.
"Variant" haqida bir gap: argon2 ning uch turi bor — argon2d, argon2i, argon2id. Parol uchun faqat argon2id: u ikkalasining yaxshi tomonini birlashtiradi.
3.3 NIST nima deydi
NIST — AQSh standartlar instituti; uning SP 800-63B hujjati butun dunyoda parol siyosati uchun asos bo'lib xizmat qiladi. Joriy nashri — SP 800-63B-4 (2025-yil iyul, avvalgi SP 800-63B o'rniga). Parol saqlash bo'yicha uning talablari (pages.nist.gov, 2026-10-28 da o'qildi):
- parol tuzlanadi va mos parol hash sxemasi bilan hash qilinadi;
- tuz kamida 32 bit va hash bilan birga saqlanadi;
- narx (cost) parametri tekshiruvchi serverning ishlashini buzmaydigan darajada iloji boricha yuqori;
- qo'shimcha ravishda faqat server biladigan maxfiy kalit bilan kalitli hash yoki shifrlash bosqichi bo'lishi tavsiya etiladi (should) — bu "pepper", «Pepper: maxfiy qo'shimcha» bo'limida.
NIST aniq algoritm nomini aytmaydi — u o'zining boshqa hujjatlariga (SP 800-132) havola qiladi. Aniq parametrlar uchun dasturchilar OWASP ga tayanadi. Parol uzunligi va tarkibi haqidagi NIST talablarini keyingi darsda, ro'yxatdan o'tish formasi bilan ko'ramiz.
4. crypto.argon2 va PHC satri
4.1 Birinchi hash
Node 24 dagi crypto.argon2 callback bilan ishlaydi, uni promisify (util moduli) bilan Promise'ga aylantiramiz. Natija bir xil bo'lsin deb tuzni qat'iy qildik — faqat ko'rsatish uchun:
import { argon2 } from "node:crypto";
import { promisify } from "node:util";
const argon2P = promisify(argon2);
const OWASP = { memory: 19456, passes: 2, parallelism: 1 };
// tuz faqat namoyish uchun qat'iy — haqiqiy kodda randomBytes(16)
const tuz = Buffer.from("IlmHamroh-tuz-16");
const hash = await argon2P("argon2id", {
message: "Bahor-choyxona-2026",
nonce: tuz,
tagLength: 32,
...OWASP,
});
const b64 = (b) => b.toString("base64").replace(/=+$/, "");
console.log(tuz.length, hash.length);
console.log(
`$argon2id$v=19$m=${OWASP.memory},t=${OWASP.passes},` +
`p=${OWASP.parallelism}$${b64(tuz)}$${b64(hash)}`,
);Konsolda:
16 32
$argon2id$v=19$m=19456,t=2,p=1$SWxtSGFtcm9oLXR1ei0xNg$nQYohXu/htZb0ZzSu+3Q4bh0L9H32vfldWmUtbZZf+QParametr nomlari Node hujjatidagidek: message — parol, nonce — tuz (kamida 8 bayt), memory — KiB, passes — o'tishlar, parallelism — yo'laklar, tagLength — natija uzunligi (baytda). Bu blok faqat Node'da ishlaydi — brauzerda argon2 yo'q.
Node hujjatiga ko'ra crypto.argon2 v24.7.0 da qo'shilgan va unga alohida "eksperimental" belgisi qo'yilmagan — butun node:crypto moduli kabi barqaror (Stability: 2). Node 22 da yoki 24.7 dan oldingi versiyada u yo'q — shuning uchun package.json da "engines": { "node": ">=24" } turibdi.
4.2 PHC satrini o'qish
Ikkinchi qator — PHC satri (Password Hashing Competition formati). Uni $ bo'yicha bo'laklaymiz:
| Bo'lak | Qiymat | Ma'nosi |
|---|---|---|
| 1 | argon2id |
algoritm |
| 2 | v=19 |
argon2 versiyasi (0x13) |
| 3 | m=19456,t=2,p=1 |
parametrlar |
| 4 | SWxtSGFtcm9oLXR1ei0xNg |
tuz, base64 (= siz) |
| 5 | nQYohXu/… |
hash, base64 |
Bu format nega muhim: bir qatorda hamma kerakli narsa bor. Ikki yildan keyin m ni 64 MiB ga oshirsangiz ham, eski foydalanuvchining satrida m=19456 yozilgan — server uni eski parametr bilan tekshira oladi. Boshqa tilda yozilgan dastur ham shu satrni tushunadi.
Kanon buni sinadi: Node crypto.argon2 bilan yasalgan satrni argon2 npm paketi tekshirdi va aksincha:
node PHC: $argon2id$v=19$m=19456,t=2,p=1$UftpOZ56yZA4IW+75w8dpQ$<hash>
argon2.verify(node PHC, to'g'ri parol): true | noto'g'ri: false
pkg PHC: $argon2id$v=19$m=19456,p=1,t=2$1BQNVWqSsvbdE66GsmG5oQ$<hash>
nodeArgon2Tekshir(pkg PHC): true | noto'g'ri: false
bir xil parol — ikki hash teng emas (tuz): trueE'tibor bering: paket parametrlarni m,p,t tartibida yozdi, biz esa m,t,p. Ikkalasi ham to'g'ri — o'quvchi dastur nomiga qaraydi, joyiga emas. Bizning parol.ts faqat o'z tartibini o'qiydi — u faqat o'zi yozgan satrlarni tekshiradi, bu yetarli.
5. bcrypt tuzog'i va Unicode
5.1 72 bayt
bcrypt parolning faqat birinchi 72 baytini hisoblaydi, qolganini jim tashlaydi. Kanon tajribasi:
bcrypt: 72×'a'+'BIRINCHI' hash'i bilan 72×'a'+'BOSHQA' tekshiruvi → true (73-baytdan keyingi qism e'tiborga olinmaydi)
bcrypt: 36 kirill harfi (72 bayt) + 'X' vs + 'Y' → true (73 bayt — 37 belgi ham chegaradan oshdi)
argon2: xuddi shu sinov → falseBirinchi qator: ikki har xil parol bitta hash'ga mos keldi. Ikkinchisi — o'zbek foydalanuvchisi uchun muhimroq: kirill harfi UTF-8 da 2 bayt oladi. 37 ta kirill harfi — allaqachon 74 bayt. "Uzun parol — xavfsiz parol" degan foydalanuvchi bcrypt bilan buning teskarisini oladi.
Belgilar va baytlar farqini o'zingiz ko'ring:
const enc = new TextEncoder();
const lotin = "a".repeat(72) + "BIRINCHI";
console.log(lotin.length, enc.encode(lotin).length);
const kirill = "ж".repeat(36) + "X";
console.log(kirill.length, enc.encode(kirill).length);Konsolda:
80 80
37 73length — belgilar (aniqrog'i UTF-16 birliklari), encode(...).length — baytlar. Lotin harfi 1 bayt, kirill — 2 bayt. bcrypt baytlarni sanaydi. argon2 da bunday chegara yo'q — kanon tajribasida u ikki parolni to'g'ri farqladi.
5.2 Bir harf — ikki xil yozilish
Yana bir nozik joy. Ў harfini kompyuter ikki xil saqlay oladi: bitta belgi (NFC) yoki У + yoy belgisi (NFD). Ekranda bir xil, baytlarda — har xil:
const enc = new TextEncoder();
const nfc = "Ў";
const nfd = nfc.normalize("NFD");
console.log(nfc === nfd);
console.log(enc.encode(nfc).length, enc.encode(nfd).length);
console.log(nfd.normalize("NFC") === nfc);Konsolda:
false
2 4
trueHash baytlardan hisoblanadi — demak bir parol ikki hash beradi. Foydalanuvchi ro'yxatdan telefonda o'tdi, keyin kompyuterdan kirmoqchi — klaviatura boshqa shaklda yubordi — "parol noto'g'ri". Kanon testi aynan shuni tekshiradi: NFD shakl false qaytaradi.
Yechim: hash'dan oldin parolni bitta shaklga keltirish — normalize("NFC"). NIST SP 800-63B-4 ham shuni tavsiya qiladi. Kanon bu ishni parol.ts da emas, ro'yxat va login sxemasida qiladi — keyingi darsda.
Tekshirib ko'ring: Nega
trim()(boshi va oxiridagi bo'shliqlarni olib tashlash) parolga qo'llanmaydi,normalizeesa qo'llanadi?
Javob
Bo'shliq — parolning haqiqiy belgisi; foydalanuvchi uni ataylab qo'ygan bo'lishi mumkin. trim parolni o'zgartiradi va uni zaiflashtiradi. normalize esa hech narsani o'zgartirmaydi — ekranda bir xil ko'rinadigan narsani baytlarda ham bir xil qiladi. Kanon testida "Bahor-choyxona-2026 " (oxirida bo'shliq) — boshqa parol.
6. Parametrlarni yangilash va xavfli satrlar
6.1 Rehash on login
Bugun m=19 MiB. Uch yildan keyin serverlar kuchliroq, siz m ni oshirmoqchisiz. Lekin bazadagi eski hash'larni qayta hisoblab bo'lmaydi — buning uchun ochiq parol kerak, u esa sizda yo'q.
Yechim: foydalanuvchi kirganda parol sizning qo'lingizda bo'ladi (u hozirgina yubordi). Shu paytda:
- eski parametrlar bilan tekshirasiz;
- to'g'ri bo'lsa va parametrlar eskirgan bo'lsa — yangi parametrlar bilan qayta hash qilib, bazaga yozasiz.
Buni rehash on login (kirishda qayta hash'lash) deyishadi. OWASP ham eng keng tarqalgan yo'l sifatida shuni tavsiya qiladi.
flowchart TD
K[Login: email + parol] --> S[(Bazadan PHC satri)]
S --> F{Format va chegaralar}
F -->|buzuq| R[401]
F --> T{argon2 tekshiruvi}
T -->|noto'g'ri| R
T -->|to'g'ri| E{Parametrlar eskimi?}
E -->|ha| Y[Yangi hash yozish]
E -->|yo'q| O[Kirish]
Y --> ODiagrammaga qarang: "Format va chegaralar" bosqichi — tekshiruvdan oldin. Nima uchun — keyingi bo'limda.
6.2 Bazadagi satr ham xavfli bo'lishi mumkin
Server bazadagi PHC satriga ishonib, unda yozilgan m bilan argon2 ni ishga tushiradi. Agar kimdir satrni m=4194304 ga o'zgartirsa (4 GiB!), har login serverga 4 GiB xotira ajratishni buyuradi. Bu — xotira tugashiga olib keladigan DoS hujumi. Bazaga yozish imkoni qanday paydo bo'ladi? SQL injection, admin panel xatosi, buzilgan migratsiya.
"Hech kimga ishonma" tamoyili bu yerda ham ishlaydi: o'z bazangizdagi satr ham tekshiriladi. Kanon PHC satrini qat'iy regex va chegaralar bilan o'qiydi:
const PHC = new RegExp(
String.raw`^\$argon2id\$v=19\$m=(\d{1,7}),t=(\d{1,2}),` +
String.raw`p=(\d{1,2})\$([A-Za-z0-9+/]{16,64})\$` +
String.raw`([A-Za-z0-9+/]{32,128})$`,
);
const CHEGARA = { m: 262_144, t: 10, p: 8 };
const JORIY = { m: 19_456, t: 2, p: 1 };
function parametrlar(saqlangan) {
const t = PHC.exec(saqlangan);
if (t === null) return null;
const prm = { m: Number(t[1]), t: Number(t[2]), p: Number(t[3]) };
if (prm.m > CHEGARA.m || prm.t > CHEGARA.t || prm.p > CHEGARA.p) {
return null;
}
return prm;
}
function eskirganmi(prm) {
return prm.m !== JORIY.m || prm.t !== JORIY.t || prm.p !== JORIY.p;
}
const tuz = "SWxtSGFtcm9oLXR1ei0xNg";
const hash = "nQYohXu/htZb0ZzSu+3Q4bh0L9H32vfldWmUtbZZf+Q";
const SATRLAR = [
`$argon2id$v=19$m=19456,t=2,p=1$${tuz}$${hash}`,
`$argon2id$v=19$m=4096,t=1,p=1$${tuz}$${hash}`,
`$argon2id$v=19$m=4194304,t=2,p=1$${tuz}$${hash}`,
"$2b$10$abcdefghijklmnopqrstuuJ1Yyq0zqP2yXv8xQ3lQx4n1yQy9n6qG",
"Bahor-choyxona-2026",
];
for (const s of SATRLAR) {
const prm = parametrlar(s);
if (prm === null) console.log("rad");
else console.log(`m=${prm.m} eski: ${eskirganmi(prm)}`);
}Konsolda:
m=19456 eski: false
m=4096 eski: true
rad
rad
radBeshta satr, besh xil holat:
- Joriy parametrlar — oddiy tekshiruv.
- Eski, kuchsizroq parametrlar — tekshiriladi va to'g'ri bo'lsa, qayta hash'lanadi.
- 4 GiB so'ragan satr — hisoblashga ham yetib bormaydi.
- bcrypt satri (
$2b$) — format boshqa, rad. - Ochiq parol — rad.
Regex'ni new RegExp va String.raw (RegExp asoslari va metodlari) bilan bo'laklarga bo'ldik — telefon ekraniga sig'sin. \d{1,7} — m ko'pi bilan 7 xonali, chegaradan oshganini esa son sifatida tekshiramiz (256 MiB = 262 144 KiB).
Muhim: noto'g'ri satr xato tashlamaydi, false qaytaradi. Aks holda bazadagi bitta buzuq qator login'ni 500 xatoga aylantirardi.
7. timingSafeEqual
Hisoblangan hash'ni saqlangan hash bilan qanday solishtirish kerak? === — xato. Oddiy solishtirish birinchi farqli baytda to'xtaydi: bir bayt mos kelsa — biroz sekinroq, ikki bayt — yana sekinroq. Bu farq nanosekundlarda, lekin minglab o'lchov bilan uni sezish mumkin. Bu timing attack (vaqt hujumi) — crypto 1 darsida ko'rgansiz.
timingSafeEqual hamma baytni doim oxirigacha solishtiradi. Lekin uning bitta talabi bor:
import { timingSafeEqual } from "node:crypto";
const a = Buffer.from("abc");
console.log(timingSafeEqual(a, Buffer.from("abc")));
console.log(timingSafeEqual(a, Buffer.from("abd")));
timingSafeEqual(a, Buffer.from("abcd"));Konsolda:
true
false
RangeError: Input buffers must have the same byte length"Kirish buferlari bir xil bayt uzunligida bo'lishi kerak". Uzunligi farq qilsa — xato tashlaydi. Shuning uchun kanon avval uzunlikni tekshiradi: h.length === kutilgan.length && timingSafeEqual(h, kutilgan). && birinchisi false bo'lsa ikkinchisini chaqirmaydi — xato chiqmaydi.
Parol hash'ida timing hujumi amalda kam xavfli (argon2 ning o'zi 30 ms davom etadi va natija tasodifiy tuzga bog'liq). Lekin qoida oddiy: sirni solishtirish — doim timingSafeEqual. Keyinroq token, HMAC imzo va API kalitda bu qoida haqiqatan muhim bo'ladi.
8. Pepper: maxfiy qo'shimcha
Tuz hash yonida turadi va baza bilan birga o'g'irlanadi. Agar hamma parolga yana bitta maxfiy qiymat qo'shilsa va u bazada emas, serverning sozlamasida (yoki maxsus kalit qurilmasida) saqlansa-chi? Baza o'g'irlansa ham, pepper'siz hujumchi birorta parolni sinay olmaydi. Bu qiymat pepper (inglizcha "qalampir" — tuzdan keyingi ziravor) deyiladi.
Node crypto.argon2 da buning uchun alohida parametr bor — secret. Hujjatda aynan shunday yozilgan: tuzga o'xshash qo'shimcha kirish, lekin u hash bilan birga saqlanmasligi kerak, parol hash'lashda u pepper deyiladi.
Pepper'ning narxi ham bor. OWASP ogohlantiradi: pepper o'zgartirilsa (masalan, u ham sizib chiqdi), eski pepper bilan hash qilingan hamma foydalanuvchi parolini qayta o'rnatishi kerak — chunki eski hash'larni yangi pepper bilan qayta hisoblab bo'lmaydi. Bundan tashqari, pepper yo'qolsa — hech kim tizimga kira olmaydi.
Kanon qarori: pepper vazifalar-api ga kiritilmadi. Sabablari — aylantirish (rotation) murakkab, pepper'ni saqlash uchun alohida sir boshqaruvi kerak (Sirlarni boshqarish), kichik loyiha uchun foydasi xavfidan kichik. NIST SP 800-63B-4 esa uni tavsiya qiladi (majburiy emas). Halol xulosa: katta tizimda, kalitlarni boshqarish infratuzilmasi bor joyda — pepper qo'shing; kursdagi loyihada — tushuncha sifatida bilib qo'ying.
9. Ko'p login birdaniga
9.1 Thread pool navbati
crypto.argon2 asinxron: hisob Node'ning libuv thread pool'ida bajariladi (libuv thread pool), asosiy oqim bo'sh qoladi. Lekin pool'da sukut bo'yicha atigi 4 ta ish oqimi bor. 16 kishi bir vaqtda kirsa nima bo'ladi? Kanon o'lchadi:
| bir vaqtdagi loginlar | UV_THREADPOOL_SIZE=4 |
|---|---|
| 1 | 50 |
| 4 | 65 |
| 8 | 128 |
| 16 | 262 |
Manba: Kanon o'lchovi (IlmHamroh, 2026-10): Node 24.21.0, i5-12500H (16 mantiqiy yadro), Windows 11, 2026-10-27
4 tagacha — deyarli bir xil (50 → 65 ms): to'rtta oqim parallel ishlaydi. Keyin chiziq tikka ko'tariladi: 8 ta login — ikki navbat, 16 ta — to'rt navbat. 16-kishi o'z javobini 262 ms da oladi. Mashinada 16 yadro bo'lsa ham — pool 4 ta.
9.2 Event loop va xotira
Ikki o'lchov yana muhim:
crypto.argon2 (async) 8 parallel paytida event loop maks kechikish: 12.0 ms
bcryptjs.hashSync(cost 10) paytida event loop maks kechikish: 113.9 ms (sinxron — butun server to'xtaydi)
m=19456 (19 MiB): 16 parallel — RSS cho'qqisi +76 MiB
m=65536 (64 MiB): 16 parallel — RSS cho'qqisi +257 MiB- Asinxron argon2 server ishini deyarli to'xtatmaydi (12 ms). Sof JS dagi sinxron
bcryptjs.hashSyncesa 114 ms davomida butun serverni to'xtatadi — boshqa hech kimning so'rovi javob olmaydi (Event loop'ni bloklash). Serverda faqat asinxron variant. - Xotira: bir vaqtda 4 ta hash (pool hajmi) × m. 19 MiB da +76 MiB, 64 MiB da +257 MiB. m ni tanlashda serveringiz xotirasini ham hisoblang.
Bu raqamlar hujumchi uchun ham qiziq: login sahifasiga ko'p so'rov yuborib, serverni band qilish mumkin. Himoyasi — tezlik cheklovi va bir vaqtdagi hash'lar sonini cheklash; DoS va resurs tugashidan himoya darsida kanon aynan shu yerga semafor qo'yadi.
10. Bazada: faqat hash
10.1 Migratsiya va CHECK
Parol hash'ini ilova hisoblaydi, baza faqat tayyor satrni saqlaydi. Postgres'da pgcrypto kengaytmasining crypt() funksiyasi bilan bazaning o'zida hash qilish mumkin. Kanon buni ataylab qilmadi: u holda parol SQL so'rov ichida ochiq holda bazaga boradi va so'rov jurnaliga, pg_stat_statements ga yoki xato xabariga tushib qolishi mumkin.
Kanon migratsiyasi:
ALTER TABLE foydalanuvchilar ADD COLUMN parol_hash text;
-- Faqat PHC satri ko'rinishida. Ochiq parol tasodifan yozilsa —
-- baza rad etadi
ALTER TABLE foydalanuvchilar
ADD CONSTRAINT foydalanuvchilar_parol_hash_check
CHECK (parol_hash IS NULL OR parol_hash ~
('^\$argon2id\$v=19\$m=\d+,t=\d+,p=\d+'
|| '\$[A-Za-z0-9+/]+\$[A-Za-z0-9+/]+$'));Kanonda regex bitta uzun satr; telefon uchun uni ikkiga bo'lib, || (satrlarni ulash) bilan birlashtirdik — biz o'z bazamizda sinadik, cheklov xuddi shunday ishlaydi. Bu — chuqur himoyaning yana bir qatlami. Kodda xato bo'lib, kimdir parol_hash ga ochiq parolni yozmoqchi bo'lsa, baza qabul qilmaydi. NULL ruxsat etilgan — paroli yo'q hisoblar (keyinroq Google, Telegram yoki SMS orqali kirganlar).
10.2 O'z bazangizda sinang
Quyidagi so'rovlar kanon sxemasi (109, 110 migratsiyalari bilan) to'ldirilgan lokal bazada bajarildi (biz ishga tushirdik, PostgreSQL 17.6). Namuna foydalanuvchilarning hash'lari:
SELECT id, email, left(parol_hash, 31) AS hash_boshi
FROM foydalanuvchilar
ORDER BY id
LIMIT 3;Natija:
id | email | hash_boshi
----+---------------------+---------------------------------
1 | aziz@example.com | $argon2id$v=19$m=19456,t=2,p=1$
2 | dilnoza@example.com | $argon2id$v=19$m=19456,t=2,p=1$
3 | sardor@example.com | $argon2id$v=19$m=19456,t=2,p=1$
(3 rows)Ochiq parolni yozishga urinish:
\set VERBOSITY terse
UPDATE foydalanuvchilar SET parol_hash = 'Bahor-choyxona-2026'
WHERE id = 1;Natija:
ERROR: new row for relation "foydalanuvchilar" violates check constraint "foydalanuvchilar_parol_hash_check""Yangi qator foydalanuvchilar_parol_hash_check cheklovini buzadi". \set VERBOSITY terse — xatoning qisqa shakli; usiz DETAIL qatorida butun qator chiqadi. bcrypt satri ham xuddi shu xato bilan rad etiladi — baza faqat argon2id ni taniydi.
Va bitta qiziq tafsilot:
SELECT count(*) FILTER (WHERE parol_hash IS NULL) AS parolsiz,
count(DISTINCT parol_hash) AS har_xil_hash
FROM foydalanuvchilar;Natija:
parolsiz | har_xil_hash
----------+--------------
1 | 1
(1 row)Bitta parolsiz hisob (Shahnoza — ataylab). Lekin har_xil_hash = 1: qolgan hamma foydalanuvchida bir xil satr. Namuna fayli (110-namuna-parol.sql) hammaga bitta demo parol Bahor-choyxona-2026 ni bitta tayyor hash bilan yozadi. Bu faqat lokal kurs bazasi uchun qulaylik. Haqiqiy ro'yxatda har foydalanuvchi parolHash() orqali o'z tasodifiy tuzini oladi va bu so'rov foydalanuvchilar soniga teng son qaytaradi. Agar prodakshn bazangizda bu raqam kichik chiqsa — tuz ishlamayapti.
11. Hujumchi nigohi
Jadvalda har hujum yonida himoya va uni tasdiqlaydigan test yoki o'lchov turibdi:
| Hujum | Himoya | Test yoki o'lchov |
|---|---|---|
| rainbow table bilan qidirish | 16 bayt tasodifiy tuz | ikki hash teng emas |
| videokartada milliardlab taxmin | argon2id, 19 MiB × 2 | JORIY testi, grafik |
| 72 baytdan keyin farqli parol | argon2id | bcrypt true, argon2 false |
bazaga m=4194304 yozish |
regex + chegaralar | rad, 200 ms dan tez |
| ochiq parol ustunga | CHECK — faqat PHC |
UPDATE → ERROR |
| ko'p bir vaqtdagi login | tezlik cheklovi, semafor | 16 login → 262 ms |
| solishtirish vaqtini o'lchash | timingSafeEqual |
uzunlik + bayt testi |
Nega bu hujumlar ishlaydi — har bo'limda ko'rdik: tuz yo'q, hash tez, bcrypt chegarasi, server satrga ishonadi, === erta to'xtaydi.
Sizgan baza uchun real hisob: kanon mashinasida bitta argon2id ~33 ms. Hujumchi videokartada tezroq ishlaydi, lekin har urinishga 19 MiB xotira kerak. 10 million mashhur parolni bitta foydalanuvchiga sinash — yuzlab soat. Million foydalanuvchiga — har biriga alohida. Biz tiklashni imkonsiz qilmadik — juda qimmat qildik. Zaif parol (123456) baribir birinchi urinishlarda topiladi. Shuning uchun keyingi darsda zaif parollarni ro'yxatdan o'tishda rad etamiz.
12. Ko'p uchraydigan xatolar
| Xato | Nega xavfli | To'g'risi |
|---|---|---|
sha256(parol) |
sekundiga yuz minglab urinish | argon2id |
| bitta umumiy tuz hamma uchun | bir xil parollar bir xil hash | har parolga randomBytes(16) |
hash'ni === bilan solishtirish |
timing attack | timingSafeEqual + uzunlik |
argon2Sync / hashSync serverda |
event loop to'xtaydi | asinxron variant |
| parametrlarni alohida ustunda saqlamaslik | keyin oshirib bo'lmaydi | PHC satri |
| buzuq satrda xato tashlash | login 500 beradi | { togri: false } |
parolni trim() qilish |
foydalanuvchi parolini o'zgartiradi | faqat normalize("NFC") |
| bcrypt'da uzun parolga ruxsat | 72 baytdan keyingisi jim tashlanadi | argon2id yoki uzunlik cheklovi |
13. Mashqlar
Mashqlar kurs/mashqlar/28/04-parol/ da.
1-mashq (oson): to'g'ri yoki noto'g'ri
- Tuz sir bo'lishi kerak, shuning uchun uni alohida jadvalda saqlash kerak.
- Bir xil parolli ikki foydalanuvchining argon2id hash'lari bir xil bo'ladi.
- PHC satrida parametrlar bo'lgani uchun m ni oshirgandan keyin eski hash'lar ham tekshiriladi.
crypto.argon2bilan yasalgan hash'niargon2npm paketi tekshira oladi.- Pepper'ni o'zgartirish oson: eski hash'larni yangi pepper bilan qayta hisoblash kifoya.
Yechim
- Noto'g'ri — tuz noyob bo'lishi kerak, sir emas; u hash yonida (PHC satri ichida) turadi.
- Noto'g'ri — har hash'ga yangi tasodifiy tuz; kanon testi
a !== bni tekshiradi. - To'g'ri — va to'g'ri bo'lsa,
qaytaHash: truebilan yangisi yoziladi. - To'g'ri — kanon ikki tomonlama sinadi:
true/false. - Noto'g'ri — buning uchun ochiq parol kerak; foydalanuvchilar parolni qayta o'rnatishi kerak bo'ladi (OWASP).
2-mashq (o'rta): bcrypt kesadimi?
bcryptKesadimi(parol) funksiyasini yozing: parolning UTF-8 dagi uzunligi 72 baytdan oshsa true. To'rtta parolda sinang va har biri uchun belgilar soni, baytlar soni va natijani chiqaring: Bahor-choyxona-2026, 72 ta a, 73 ta a, 37 ta ў. Ishora: baytlar — new TextEncoder().encode(parol).length, takrorlash — "a".repeat(72) (Kesish, bo'lish va to'ldirish).
Yechim
const enc = new TextEncoder();
function bcryptKesadimi(parol) {
return enc.encode(parol).length > 72;
}
const SINOV = [
"Bahor-choyxona-2026",
"a".repeat(72),
"a".repeat(73),
"ў".repeat(37),
];
for (const p of SINOV) {
console.log(p.length, enc.encode(p).length, bcryptKesadimi(p));
}Konsolda:
19 19 false
72 72 false
73 73 true
37 74 trueOxirgi qator: 37 belgi — lotin uchun qisqa parol, lekin kirillda 74 bayt. bcrypt ishlatadigan eski tizimda shunday parolga ruxsat bersangiz, foydalanuvchi oxirgi harfni "himoya" deb o'ylaydi, aslida u hisobga olinmaydi.
3-mashq (qiyin): brauzerda PBKDF2
Brauzerda argon2 yo'q, lekin Web Crypto'da PBKDF2 bor — OWASP uni FIPS talab qilinganda tavsiya qiladi (SHA-256 bilan 600 000 iteratsiya). parolHash(parol) va parolTekshir(saqlangan, parol) ni yozing: format pbkdf2-sha256$600000$<tuz>$<hash> (tuz va hash — base64). Tuz — 16 bayt crypto.getRandomValues. Solishtirish — har baytni oxirigacha (timingSafeEqual o'rniga XOR bilan). Formati noto'g'ri satr — false. Ishora: crypto.subtle.importKey("raw", …, "PBKDF2", false, ["deriveBits"]) va deriveBits({ name: "PBKDF2", hash: "SHA-256", salt, iterations }, kalit, 256); base64 — btoa/atob (crypto 1).
Yechim
const ITERATSIYA = 600_000;
const enc = new TextEncoder();
const b64 = (baytlar) => btoa(String.fromCharCode(...baytlar));
const baytga = (matn) =>
Uint8Array.from(atob(matn), (c) => c.charCodeAt(0));
async function hosilQil(parol, tuz, iteratsiya) {
const kalit = await crypto.subtle.importKey(
"raw", enc.encode(parol), "PBKDF2", false, ["deriveBits"],
);
const bitlar = await crypto.subtle.deriveBits(
{
name: "PBKDF2", hash: "SHA-256",
salt: tuz, iterations: iteratsiya,
},
kalit,
256,
);
return new Uint8Array(bitlar);
}
async function parolHash(parol) {
const tuz = crypto.getRandomValues(new Uint8Array(16));
const h = await hosilQil(parol, tuz, ITERATSIYA);
return `pbkdf2-sha256$${ITERATSIYA}$${b64(tuz)}$${b64(h)}`;
}
async function parolTekshir(saqlangan, parol) {
const [nom, n, tuz, hash] = saqlangan.split("$");
if (nom !== "pbkdf2-sha256" || !tuz || !hash) return false;
const h = await hosilQil(parol, baytga(tuz), Number(n));
const kutilgan = baytga(hash);
if (h.length !== kutilgan.length) return false;
let farq = 0;
for (let i = 0; i < h.length; i++) farq |= h[i] ^ kutilgan[i];
return farq === 0;
}
const a = await parolHash("Bahor-choyxona-2026");
const b = await parolHash("Bahor-choyxona-2026");
console.log(a.split("$").slice(0, 2).join("$"));
console.log(a === b);
console.log(await parolTekshir(a, "Bahor-choyxona-2026"));
console.log(await parolTekshir(a, "bahor-choyxona-2026"));
console.log(await parolTekshir("Bahor-choyxona-2026", "x"));Konsolda:
pbkdf2-sha256$600000
false
true
false
falseTuz va hash har safar boshqa, shuning uchun to'liq satrni emas, faqat boshini chiqardik. farq |= h[i] ^ kutilgan[i] — XOR bir xil baytlarda 0 beradi; birorta farq bo'lsa farq noldan katta bo'lib qoladi. Sikl birinchi farqda to'xtamaydi — vaqt mos kelgan baytlar soniga bog'liq emas. Iteratsiya soni ham satr ichida — PHC g'oyasi: keyin oshirsangiz, eskilari ham tekshiriladi. PBKDF2 xotira talab qilmaydi — shuning uchun serverda argon2id afzal.
14. Loyiha qadami: parol.ts va migratsiya
14.1 Migratsiya va Prisma sxemasi
Yangi SQL fayl sql/109-parol.sql — «Bazada: faqat hash» bo'limidagi ALTER TABLE lar. 27-qism qoidasi bo'yicha xuddi shu SQL Prisma migratsiyasi bo'lib ham yoziladi: prisma/migrations/20261027100000_parol/migration.sql. schema.prisma dagi Foydalanuvchi modeliga bitta maydon:
/// argon2id PHC satri; NULL — parolsiz hisob (OIDC, Telegram, OTP)
parolHash String? @map("parol_hash")Kanon izohidagi migratsiya raqamini olib tashladik. Namuna fayl sql/110-namuna-parol.sql — faqat lokal baza uchun, hammaga bitta demo parol:
-- Hamma namuna hisobining paroli: Bahor-choyxona-2026.
-- Shahnozada parol yo'q — parolsiz hisob misoli.
UPDATE foydalanuvchilar
SET parol_hash = '$argon2id$v=19$m=19456,t=2,p=1$FuHr…UywpI'
WHERE email <> 'shahnoza@example.com';Hash satrini qisqartirdik (…) — to'liq qiymatni kanon faylidan oling yoki parolHash("Bahor-choyxona-2026") bilan o'zingiz yasang. Bu parol faqat sizning kompyuteringizdagi kurs bazasi uchun. *-namuna-* fayllar prodakshnga bormaydi.
14.2 src/auth/parol.ts
Darsdagi hamma g'oya bitta modulda. Telefon uchun uzun qatorlar va izohlarni bo'ldik va qisqartirdik, kod ma'nosi kanondagidek (bo'lingan nusxa tip, lint va testlardan o'tdi — biz tekshirdik):
// parol.ts — parolni xavfsiz saqlash: argon2id, Node 24 ning o'z
// crypto.argon2 (v24.7.0+) — bog'liqliksiz.
// Saqlash formati — PHC satri:
// $argon2id$v=19$m=19456,t=2,p=1$<tuz>$<hash>
// — parametrlar hash bilan birga (keyin oshirsak, eskilari ham
// tekshiriladi).
import {
argon2 as argon2Cb,
randomBytes,
timingSafeEqual,
} from "node:crypto";
import { promisify } from "node:util";
const argon2 = promisify(argon2Cb);
export type Argon2Parametrlari = { m: number; t: number; p: number };
/** OWASP Password Storage Cheat Sheet (argon2id, minimal tavsiya).
* Oshirilsa — eski hash'lar login paytida qayta hash'lanadi */
export const JORIY: Argon2Parametrlari = Object.freeze({
m: 19_456,
t: 2,
p: 1,
});
const TUZ_BAYT = 16;
const HASH_BAYT = 32;
// PHC: base64 "=" siz. Parametrlar oralig'i — DoS himoyasi: bazadagi
// buzilgan satr serverni 4 GiB xotira ajratishga majburlamasin
const PHC = new RegExp(
String.raw`^\$argon2id\$v=19\$m=(\d{1,7}),t=(\d{1,2}),` +
String.raw`p=(\d{1,2})\$([A-Za-z0-9+/]{16,64})\$` +
String.raw`([A-Za-z0-9+/]{32,128})$`,
);
const CHEGARA = { mMaks: 262_144, tMaks: 10, pMaks: 8 };
const b64 = (b: Buffer): string =>
b.toString("base64").replace(/=+$/, "");
async function hisobla(
parol: string,
tuz: Buffer,
prm: Argon2Parametrlari,
uzunlik = HASH_BAYT,
): Promise<Buffer> {
return argon2("argon2id", {
message: parol,
nonce: tuz,
memory: prm.m,
passes: prm.t,
parallelism: prm.p,
tagLength: uzunlik,
});
}
export async function parolHash(
parol: string,
prm: Argon2Parametrlari = JORIY,
): Promise<string> {
const tuz = randomBytes(TUZ_BAYT);
const h = await hisobla(parol, tuz, prm);
const sozlama =
`m=${String(prm.m)},t=${String(prm.t)},p=${String(prm.p)}`;
return `$argon2id$v=19$${sozlama}$${b64(tuz)}$${b64(h)}`;
}
export type TekshiruvNatijasi = {
togri: boolean;
qaytaHash: boolean;
};
/**
* Parolni saqlangan PHC satri bilan solishtiradi. `qaytaHash` — parol
* to'g'ri, lekin hash eski parametrlar bilan: login paytida yangisini
* yozish kerak (rehash on login). Formati tanilmagan satr —
* `togri: false` (xato tashlamaydi: bazadagi buzuq qator login'ni
* 500 qilmasin).
*/
export async function parolTekshir(
saqlangan: string,
parol: string,
joriy: Argon2Parametrlari = JORIY,
): Promise<TekshiruvNatijasi> {
const t = PHC.exec(saqlangan);
if (t === null) return { togri: false, qaytaHash: false };
const [, m, tt, p, tuz, hash] = t;
const prm = { m: Number(m), t: Number(tt), p: Number(p) };
if (
prm.m > CHEGARA.mMaks ||
prm.t > CHEGARA.tMaks ||
prm.p > CHEGARA.pMaks ||
prm.m < 8 * prm.p
) {
return { togri: false, qaytaHash: false };
}
const kutilgan = Buffer.from(hash ?? "", "base64");
const h = await hisobla(
parol,
Buffer.from(tuz ?? "", "base64"),
prm,
kutilgan.length,
);
// timingSafeEqual — solishtirish vaqti mos kelgan baytlar soniga
// bog'liq emas
const togri =
h.length === kutilgan.length && timingSafeEqual(h, kutilgan);
const eski =
prm.m !== joriy.m || prm.t !== joriy.t || prm.p !== joriy.p;
return { togri, qaytaHash: togri && eski };
}
/**
* Foydalanuvchi topilmaganda ham BIR XIL ish (argon2) bajariladi —
* javob vaqti "bunday email yo'q" ni oshkor qilmasin (timing).
* Ishga tushishda bir marta hisoblanadi.
*/
let soxta: Promise<string> | undefined;
export function soxtaHash(): Promise<string> {
soxta ??= parolHash(randomBytes(32).toString("base64"));
return soxta;
}Uch narsaga e'tibor bering:
prm.m < 8 * prm.p— Node hujjatiga ko'ramemorykamida8 × parallelismbo'lishi shart; aks holda argon2 xato tashlardi. Tekshiruv uni oldindan ushlaydi vafalseqaytaradi.hash ?? ""— TypeScript regex guruhinistring | undefineddeb biladi (noUncheckedIndexedAccess), shuning uchun zaxira qiymat.soxtaHash()— hozircha ishlatilmaydi. U keyingi darsda kerak: email bazada bo'lmasa ham server argon2 ishini bajaradi. Aks holda javob tezligidan "bunday email yo'q" ekanini bilib olish mumkin.??=— o'zgaruvchi bo'sh bo'lsa, qiymat beradi; natijada hash bir marta hisoblanadi.
14.3 Testlar
test/parol.test.ts — oltita test. Ulardan ikkitasi (telefon uchun bo'lingan):
test("eski parametrlar bilan hash — to'g'ri, lekin " +
"qaytaHash: true (rehash on login)", async () => {
const eski = await parolHash(PAROL, { m: 4096, t: 1, p: 1 });
assert.deepEqual(await parolTekshir(eski, PAROL),
{ togri: true, qaytaHash: true });
assert.deepEqual(await parolTekshir(eski, "x"),
{ togri: false, qaytaHash: false });
assert.deepEqual(JORIY, { m: 19_456, t: 2, p: 1 });
});
test("buzuq yoki xavfli satr — xato emas, togri: false " +
"(bcrypt, ochiq parol, juda katta m — DoS)", async () => {
const yaxshi = await parolHash(PAROL);
// 4 GiB so'rasa — hisoblamaymiz
const katta = yaxshi.replace("m=19456", "m=4194304");
const SATRLAR = [
"", PAROL,
"$2b$10$abcdefghijklmnopqrstuuJ1Yyq0zqP2yXv8xQ3lQx4n1yQy9n6qG",
katta, yaxshi.replace("argon2id", "argon2i"), `${yaxshi}x!`,
];
for (const s of SATRLAR) {
const b = performance.now();
assert.deepEqual(await parolTekshir(s, PAROL),
{ togri: false, qaytaHash: false }, s);
assert.ok(performance.now() - b < 200);
}
});Ikkinchi testdagi performance.now() - b < 200 — "rad etish tez bo'lsin" degan talab. 4 GiB lik satr hisoblashga yetib borsa, test bu chegarani buzardi. Qolgan to'rt test: format va har xil tuz, registr va bo'shliq farqi, NFC/NFD, soxtaHash.
Faqat shu fayl testlarini ishga tushiramiz (biz ishga tushirdik, kanon nusxasi va yuqoridagi bo'lingan parol.ts bilan, Node 24.21.0):
node --test test/parol.test.ts✔ parolHash — PHC satri, joriy parametrlar, har safar boshqa tuz (59.2439ms)
✔ parolTekshir — to'g'ri / noto'g'ri / registr va bo'shliq farq qiladi (180.0863ms)
✔ Unicode: o'zbekcha va kirill parol (NFC/NFD farqi — normallashtirish chaqiruvchida) (89.0655ms)
✔ eski parametrlar bilan hash — to'g'ri, lekin qaytaHash: true (rehash on login) (11.1955ms)
✔ buzuq yoki xavfli satr — xato emas, togri: false (bcrypt, ochiq parol, juda katta m — DoS) (29.6909ms)
✔ soxtaHash — bir marta hisoblanadi, hech qaysi parolga mos kelmaydi (57.9309ms)
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 569.8217Millisekundlar har ishga tushishda boshqacha. To'rtinchi testning qisqaligiga qarang (11 ms): u m=4096 bilan — 5 marta kam xotira, shuning uchun tezroq.
14.4 Tekshiruv va commit
Butun loyiha tekshiruvi (kanon, 2026-10-27, PostgreSQL 18.6): tip va lint xatosiz, testlar 118 (117 + 1 o'tkazilgan) — oldingi 112 ga oltita yangi test qo'shildi. Prisma migratsiyasi sql/ bilan bir xil, schema.prisma va migratsiyalar orasida farq yo'q ("This is an empty migration").
M="feat(auth): parol hash — argon2id (Node crypto.argon2,"
M+=" PHC satri), rehash bayrog'i; db: 109 parol_hash"
git add src/auth/parol.ts test/parol.test.ts sql prisma
git commit -m "$M"Xabarda apostrof (bayrog'i) bor, shuning uchun bu safar qo'sh tirnoq: bitta tirnoq ichida apostrof satrni yopib qo'yardi.
Bu darsda hali hech kim ro'yxatdan o'tmaydi va kirmaydi — API'da endpoint yo'q. Biz faqat poydevor qo'ydik: parol qanday saqlanishi va tekshirilishi.
15. Real ishda
- Parol sizib chiqishlari muntazam bo'ladi. Tadqiqotchilar birinchi qaraydigan narsa — qanday hash ishlatilgani. MD5 yoki tuzsiz SHA-1 bo'lsa — kompaniya obro'si qattiq zarar ko'radi.
- Freymvorklar (framework) bu ishni o'zi qiladi (rasmiy hujjatlarga ko'ra, 2026-10 holatiga). Django sukut bo'yicha PBKDF2 (SHA-256) ishlatadi va parametr o'zgarsa, foydalanuvchi kirganda hash'ni o'zi yangilaydi. Laravel sukut bo'yicha bcrypt ishlatadi, eskirgan hash'ni
Hash::needsRehashaniqlaydi. Spring Security'ningDelegatingPasswordEncoderi sukut bo'yicha bcrypt'ga topshiradi va algoritmni keyin almashtirishga joy qoldiradi. Node'da bu ishni o'zingiz yozasiz yoki auth kutubxonasiga topshirasiz — Auth kutubxonalari va xizmatlari. - Eski tizimdan ko'chish: bazada MD5 bo'lsa, OWASP tavsiyasi — eski hash'ni yangi algoritm ichiga o'rash va foydalanuvchi kirganda to'g'ridan-to'g'ri argon2id ga almashtirish.
- Intervyuda: "Parolni qanday saqlaysiz?", "Tuz va pepper farqi?", "Nega bcrypt emas, argon2id?", "Parametrlarni qanday yangilaysiz?".
Xulosa
- Parol — faqat sekin, tuzli hash: argon2id (OWASP: m=19 MiB, t=2, p=1); Node 24 da
crypto.argon2(v24.7.0+). - Tuz — noyob, sir emas; rainbow table'ni o'ldiradi. Pepper — maxfiy, NIST tavsiya qiladi, lekin aylantirish qimmat.
- PHC satri algoritm, parametr, tuz va hash'ni birga saqlaydi — eski hash'lar kirishda qayta hash'lanadi.
- bcrypt faqat 72 baytni ko'radi (kirillda 36 harf); Unicode — NFC ga keltiriladi.
- Bazadagi satr ham tekshiriladi: regex, parametr chegaralari,
false(xato emas),timingSafeEqual. - Bazada
CHECKfaqat PHC satrini qabul qiladi; parol hash'i ilovada hisoblanadi, bazada emas.
Keyingi dars: Ro'yxat va login oqimi hamda hisob xavfsizligi — parolHash va parolTekshir endpoint'larga ulanadi. U yerda NIST bo'yicha parol siyosati, "email yoki parol noto'g'ri" degan bir xil javob va javob vaqtidan himoyani yozamiz.
Manbalar
- OWASP Password Storage Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html (2026-10-28 da o'qildi)
- NIST SP 800-63B-4 "Digital Identity Guidelines: Authentication and Authenticator Management" (2025-yil iyul): https://pages.nist.gov/800-63-4/sp800-63b.html va https://csrc.nist.gov/pubs/sp/800/63/b/4/final (2026-10-28 da o'qildi)
- Node.js 24
crypto.argon2(v24.7.0 da qo'shilgan): https://nodejs.org/docs/latest-v24.x/api/crypto.html#cryptoargon2algorithm-parameters-callback (2026-10-28 da o'qildi) - PHC string format: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
- RFC 9106 (Argon2): https://www.rfc-editor.org/rfc/rfc9106
- Freymvorklar (2026-10-28 da o'qildi): Django 6.1 "Password management" https://docs.djangoproject.com/en/stable/topics/auth/passwords/ ; Laravel 12 "Hashing" https://laravel.com/docs/12.x/hashing ; Spring Security 7.1 "Password Storage" https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html
- Kanon o'lchovi (IlmHamroh, 2026-10-27): parol algoritmlari tajribasi (Node 24.21.0, OpenSSL 3.5.8, argon2 0.45.1, bcrypt 6.0.0, bcryptjs 3.0.3, i5-12500H), holat 04 tekshiruvi. Darsdagi brauzer va Node misollari, SQL so'rovlar (PostgreSQL 17.6) va
parol.test.ts— biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!