IlmHamroh
JavaScript Full-stack/24-qism. Node.js21/55-dars17 daqiqa
Mundarija (26)

Node.js crypto 2: parolni hash'lash, shifrlash va imzo (AES-GCM, Ed25519)

Qisqacha: Uch xil vazifa — uch xil vosita. Parol saqlanmaydi, uning sekin hash'i saqlanadi: scrypt yoki argon2id, har foydalanuvchiga alohida tasodifiy tuz (salt) bilan. Node 24 da ikkalasi ham o'rnatilgan (crypto.scrypt; crypto.argon2 — 24.7 dan). Ma'lumotni yashirish — AES-256-GCM: 32 baytlik kalit, har safar yangi 12 baytlik IV va auth tag — buzilgan shifr ochilmaydi, xato beradi. "Buni men yubordim" degan isbot — ochiq kalitli imzo (Ed25519): yopiq kalit bilan imzolanadi, ochiq kalit bilan har kim tekshiradi.

Bu darsda

  • Parolni nega oddiy hash bilan saqlab bo'lmasligini va scrypt/argon2 qanday himoya qilishini o'lchovlar bilan tushuntirasiz.
  • Parolni tuz bilan hash qilib saqlaydigan va tekshiradigan kod yozasiz.
  • AES-256-GCM bilan shifrlaysiz va IV, auth tag nima uchun kerakligini bilasiz.
  • Ed25519 bilan imzolab tekshirasiz; simmetrik va asimmetrik kalit farqini bilasiz.
  • Web Crypto'da kalit juftligi yaratasiz (web push uchun ECDSA P-256).

Oldin bilishingiz kerak: crypto 1: tasodif, hash va HMAC, Buffer (qisqa eslatma pastda), Web push bildirishnomalar.

1. Nega bu kerak?

O'tgan darsda hash va HMAC'ni ko'rdik. Ikkalasi ham "o'zgarmagan" degan savolga javob beradi. Bugun uch yangi savol:

  1. Saytingiz bazasi o'g'irlandi. Unda 10 000 foydalanuvchining paroli bor. Hujumchi ularni o'qiy oladimi? Ko'pchilik bir parolni hamma joyda ishlatadi — bitta o'g'irlik ularning Telegram, pochta va bank hisoblarini ham ochishi mumkin.
  2. Faylni faqat egasi o'qisin. Masalan, valyuta sozlamasida kelajakda pullik API kaliti bo'lsa — diskda ochiq yotmasin.
  3. Faylni men yuborganimni hamma tekshira olsin. @login/valyuta paketingiz arxivini (login-valyuta-1.0.0.tgz) yuklagan odam uni aynan siz yasaganingizga ishonch hosil qilmoqchi — lekin u bilan sir bo'lishishingiz kerak emas.

Har biriga boshqa vosita kerak. Ularni chalkashtirish — real loyihalardagi eng og'ir xavfsizlik xatolaridan.

O'xshatish: uch xil qulf. Parol hash — barmoq izi skaneri: barmoqni saqlamaydi, faqat solishtiradi. Shifrlash — seyf: kaliti bor odam ochadi. Imzo — muhr: muhrni faqat egasi bosadi, lekin muhr namunasini hamma ko'rib solishtiradi.

valyuta ga bularning hech biri kerak emas: unda parol, sir yoki imzo yo'q. Shuning uchun bu dars — kanon tajribasi: o'lchovlar valyuta loyihasidan tashqarida qilingan.

2. Parolni saqlash

2.1 Nega SHA-256 emas

SHA-256 ataylab tez qilib yaratilgan: fayl va xabarlarni tez tekshirish uchun. Parol uchun bu kamchilik. Hujumchi bazani olgach, mashhur parollar ro'yxatini (123456, parol123, toshkent2026 …) bittalab hash qiladi va solishtiradi. Bitta zamonaviy videokarta sekundiga milliardlab SHA-256 hisoblaydi — oddiy parollar daqiqalarda topiladi.

Ikkinchi muammo: bir xil parol — bir xil hash. Hujumchi bitta hisob bilan minglab foydalanuvchini ochadi. Oldindan hisoblangan "parol → hash" jadvallari (rainbow table) ham shu yerda ishlaydi.

Ikkala muammoning davosi:

  • Tuz (salt) — har foydalanuvchi uchun tasodifiy 16 bayt, parolga qo'shib hash qilinadi va hash yonida ochiq saqlanadi. Bir xil parol — har xil hash. Tayyor jadvallar ishlamaydi.
  • Sekin hash — KDF (key derivation function, kalit hosil qilish funksiyasi): bitta hisob ataylab o'nlab-yuzlab millisekund va o'nlab megabayt xotira oladi. Foydalanuvchi kirishda 50 ms ni sezmaydi. Milliard urinish esa yillarga cho'ziladi.

2.2 Kanon o'lchovi

Kanon uch algoritmni bir xil parol bilan o'lchadi (Node 24.21.0, i5-12500H, Windows 11):

text
scrypt N=2^17 r=8 p=1: 286 ms
pbkdf2 sha256 600 000: 107 ms
crypto.argon2Sync bormi: function
argon2id m=19 MiB t=2 p=1: 31 ms
Bitta parol hash'i: vaqt (kattarog'i — hujumchiga qimmatroq)
  • argon2id m=19 MiB t=2OWASP minimal31 ms
  • pbkdf2-sha256 600 000OWASP tavsiyasi107 ms
  • scrypt N=2^17 r=8128 MiB xotira286 ms

Manba: IlmHamroh kanon tajribasi t21-crypto2: Node 24.21.0, Windows 11, i5-12500H, 2026-10-23

Rasmni to'g'ri o'qing: argon2id eng tez chiqdi, lekin bu "eng zaif" degani emas. Uning kuchi — xotira: har urinish 19 MiB oladi. Videokarta minglab yadroga ega, lekin har yadroga 19 MiB berolmaydi — parallel hujum qimmatlashadi. pbkdf2 esa xotirani deyarli ishlatmaydi, uni videokartada parallel buzish arzon. Shuning uchun OWASP tavsiyasi tartibi: argon2id → scrypt → bcrypt → pbkdf2 (oxirgisi — faqat FIPS talab qilinganda).

Parametrlar — OWASP Password Storage Cheat Sheet'dagi minimal qiymatlar: argon2id uchun m=19 MiB, t=2, p=1; pbkdf2-sha256 uchun 600 000 iteratsiya. Serveringizda bitta hash 50–500 ms orasida bo'lsin — parametrlarni shunga moslang.

2.3 scrypt bilan saqlash va tekshirish

Node'da scrypt ancha yillardan beri bor. To'liq misol: hash qilish, saqlash formati, tekshirish. Tez ishlashi uchun N kichikroq (2^14) — haqiqiy serverda kattaroq:

js
import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
import { promisify } from "node:util";

const scryptP = promisify(scrypt);
const N = 2 ** 14;

async function parolniHashla(parol) {
  const tuz = randomBytes(16);
  const hash = await scryptP(parol, tuz, 32, { N });
  const b64 = (b) => b.toString("base64url");
  return `scrypt$${N}$${b64(tuz)}$${b64(hash)}`;
}

async function parolniTekshir(parol, saqlangan) {
  const [, n, tuz64, hash64] = saqlangan.split("$");
  const kutilgan = Buffer.from(hash64, "base64url");
  const tuz = Buffer.from(tuz64, "base64url");
  const uzunlik = kutilgan.length;
  const hash = await scryptP(parol, tuz, uzunlik, { N: Number(n) });
  return timingSafeEqual(hash, kutilgan);
}

const a = await parolniHashla("toshkent2026");
const b = await parolniHashla("toshkent2026");
console.log(a.split("$").length, a === b);
console.log(await parolniTekshir("toshkent2026", a));
console.log(await parolniTekshir("Toshkent2026", a));

Konsolda:

text
4 false
true
false

Bir xil parol — ikki xil saqlangan qator (a === b — false): tuz ishladi. To'g'ri parol — true, bitta katta harf farqi — false.

Saqlash formatiga qarang: scrypt$16384$<tuz>$<hash>. Algoritm nomi va parametrlar ham saqlanadi. Bir yildan keyin N ni oshirmoqchi bo'lsangiz, eski yozuvlar o'z parametri bilan tekshiriladi. Foydalanuvchi keyingi safar kirganda — yangi parametr bilan qayta hash qilinadi. Parolning o'zi hech qayerda yo'q.

promisify (util moduli) — scrypt callback'li, sinxron scryptSync esa event loop'ni 50–300 ms to'xtatadi. Serverda faqat asinxron variant: hash libuv thread pool'ida hisoblanadi (thread pool).

2.4 Tuzoq: maxmem

Kanon parametrini (N: 2 ** 17) to'g'ridan bersak:

js
import { scryptSync } from "node:crypto";

try {
  scryptSync("parol", "tuz", 32, { N: 2 ** 17 });
} catch (xato) {
  console.log(xato.code);
}

Konsolda:

text
ERR_CRYPTO_INVALID_SCRYPT_PARAMS

To'liq xabar: Invalid scrypt params: error:030000AC:digital envelope routines::memory limit exceeded — "xotira chegarasidan oshdi". scrypt 128 × N × r bayt xotira oladi: N=2^17, r=8 da — 128 MiB. Node'ning sukut chegarasi (maxmem) — 32 MiB. Kanon shuning uchun maxmem: 256 * 2 ** 20 qo'shgan. Xabar sababni emas, OpenSSL ichki kodini ko'rsatadi — shuning uchun bu tuzoq ko'p vaqt oladi.

2.5 argon2 Node 24 da

Ilgari argon2 uchun npm paketi kerak edi (argon2 — 2026-10-23 holatiga 0.45.1, C++ addon). Kanon topdi: Node 24.21 da crypto.argon2 va crypto.argon2Sync o'rnatilgan, chaqirilganda ogohlantirish chiqmadi. Imzo kanon tajribasidagidek:

js
import { argon2Sync } from "node:crypto";

const hash = argon2Sync("argon2id", {
  message: "parol123",
  // tuz; haqiqiy kodda — randomBytes(16)
  nonce: Buffer.from("0123456789abcdef"),
  parallelism: 1,
  tagLength: 32,
  memory: 19456, // KiB = 19 MiB
  passes: 2,
});
console.log(hash.length, hash.toString("hex").slice(0, 16));

Konsolda:

text
32 ee0c314b6293859b

Bu yerda tuz qat'iy — natija har safar bir xil bo'lsin (faqat ko'rsatish uchun). Haqiqiy kodda randomBytes(16).

Bu API qanchalik ishonchli? Node hujjatiga ko'ra (2026-10 holatiga), crypto.argon2 v24.7.0 da qo'shilgan. Unga alohida "eksperimental" belgisi qo'yilmagan — demak butun node:crypto moduli kabi Stability: 2 — Stable (barqaror). Lekin u yangi: Node 22 da ham, 24.7 dan oldingi 24.x da ham yo'q. Shuning uchun package.json da engines.node ni >=24.7 qilib qo'ying yoki eski Node uchun argon2 / @node-rs/argon2 paketini ishlating.

bcrypt — eng keng tarqalgan eski variant (bcrypt 6.0.0 — addon, bcryptjs 3.0.3 — toza JS). Uning bitta tuzog'i: parolning faqat birinchi 72 baytini hisobga oladi, qolgani jim tashlanadi. Yangi loyiha uchun — argon2id.

Tekshirib ko'ring: Nega tuz sir emas va hash yonida ochiq saqlanadi? U ham yashirin bo'lsa, xavfsizroq emasmi?

Javob

Tuzning vazifasi — bir xil parollarga har xil hash berish va tayyor jadvallarni yaroqsiz qilish. Buning uchun u faqat noyob bo'lishi kerak, sir emas. Tekshirishda tuz kerak — shuning uchun hash yonida turadi. Qo'shimcha maxfiy kalit (pepper) ham qo'shish mumkin, u bazadan alohida (env'da) saqlanadi — lekin bu ixtiyoriy qatlam.

3. Shifrlash: AES-256-GCM

3.1 Uch tarkibiy qism

Hash bir tomonlama. Shifrlash (encryption) esa ikki tomonlama: kalit bilan yopiladi, o'sha kalit bilan ochiladi. Bitta kalit ikkala ishni qilgani uchun bu simmetrik shifrlash deyiladi. Bugungi standart tanlov — AES-256-GCM:

Qism Hajm Vazifasi
Kalit 32 bayt (256 bit) Sir. randomBytes(32) yoki parol'dan KDF bilan
IV (nonce) 12 bayt Har shifrlashda yangi tasodif; sir emas, shifr yonida saqlanadi
Auth tag 16 bayt Shifr buzilmaganini tasdiqlaydi (GCM — shifrlash + butunlik)
js
import {
  createCipheriv, createDecipheriv, randomBytes,
} from "node:crypto";

const kalit = randomBytes(32);

function shifrla(matn) {
  const iv = randomBytes(12);
  const c = createCipheriv("aes-256-gcm", kalit, iv);
  const shifr = Buffer.concat([c.update(matn, "utf8"), c.final()]);
  return { iv, shifr, tag: c.getAuthTag() };
}

function och({ iv, shifr, tag }) {
  const d = createDecipheriv("aes-256-gcm", kalit, iv);
  d.setAuthTag(tag);
  return Buffer.concat([d.update(shifr), d.final()]).toString("utf8");
}

const quti = shifrla("API kaliti: sk_test_123");
console.log(quti.shifr.length, quti.tag.length);
console.log(och(quti));

const buzuq = { ...quti, shifr: Buffer.from(quti.shifr) };
buzuq.shifr[0] ^= 1; // bitta bitni o'zgartiramiz
try {
  och(buzuq);
} catch (xato) {
  console.log(xato.message);
}

Konsolda:

text
23 16
API kaliti: sk_test_123
Unsupported state or unable to authenticate data

Kuzatuvlar:

  • Shifr 23 bayt — asl matn bilan bir xil uzunlik. GCM to'ldirma (padding) qo'shmaydi.
  • Bitta bit o'zgardi — final() xato tashladi: "holat qo'llab-quvvatlanmaydi yoki ma'lumotni tasdiqlab bo'lmadi". Kanon tajribasi ham aynan shu xabarni oldi. Xabar tushunarsiz, lekin ma'nosi aniq: shifr buzilgan yoki kalit/tag noto'g'ri. Buzilgan ma'lumot jim "ochilib", axlat qaytarilmaydi.
  • ^= 1 — XOR bilan bitta bitni teskarisiga o'zgartirish.

Natijani saqlashda uchalasi birga yoziladi, masalan iv.tag.shifr (har biri base64url). Kalit — alohida, sir sifatida (env, sirlar menejeri).

3.2 IV qoidasi

Bir kalit bilan bitta IV ni ikki marta ishlatmang. GCM'da bu shunchaki zaiflik emas — halokat: ikki shifrni solishtirib, matnlar haqida ma'lumot va hatto tasdiqlash kalitini tiklash mumkin. Shuning uchun IV har safar randomBytes(12). Qat'iy IV — faqat dars namoyishi uchun, kodda hech qachon.

Nega aes-256-cbc emas? Eski kodlarda u ko'p uchraydi. Lekin CBC butunlikni tekshirmaydi — buzilgan shifr jim ochilib, axlat beradi yoki "padding oracle" hujumiga yo'l ochadi. GCM shifrlash va butunlikni birga beradi (AEAD). Yangi kodda — GCM (yoki chacha20-poly1305).

3.3 Paroldan kalit

Kalit 32 tasodifiy bayt bo'lishi kerak. Foydalanuvchi esa parol beradi. Paroldan kalit yasash — yana KDF: scrypt(parol, tuz, 32). Parol hash'idagi bilan bir xil g'oya, faqat natija saqlanmaydi, kalit sifatida ishlatiladi. Tuz va IV shifr yonida saqlanadi.

Tekshirib ko'ring: Dasturchi kalit sifatida "mening-sirli-kalitim-32-belgi!!!" satrini to'g'ridan ishlatdi (32 belgi). Nima xato?

Javob

Kalit tasodifiy emas — u insonning o'ylab topgan matni, taxmin qilish ancha oson (256 bit o'rniga amalda ancha kam). Bundan tashqari, u kodda yozilgan va GitHub'ga tushadi. To'g'risi — randomBytes(32) bilan yaratib, env'da saqlash; parol kerak bo'lsa — scrypt bilan kalit hosil qilish.

4. Imzo: Ed25519

4.1 Asimmetrik kalitlar

HMAC'da ikkala tomon bitta kalitni biladi — demak ikkalasi ham imzo yasay oladi. Agar tekshiruvchilar ko'p bo'lsa (paketni yuklagan hamma), ularga kalit berish — ularga soxta imzo yasash huquqini berish. Yechim — asimmetrik kalitlar: juftlik.

  • Yopiq kalit (private key) — faqat sizda; imzolaydi.
  • Ochiq kalit (public key) — hammaga; faqat tekshiradi.

Ed25519 — zamonaviy, tez, xatoga chidamli imzo algoritmi (SSH kalitlari, Git commit imzosi, ko'p protokollar).

js
import { generateKeyPairSync, sign, verify } from "node:crypto";

const { publicKey, privateKey } = generateKeyPairSync("ed25519");
const fayl = Buffer.from("login-valyuta-1.0.0.tgz ichidagi baytlar");

const imzo = sign(null, fayl, privateKey);
console.log(imzo.length);
console.log(verify(null, fayl, publicKey, imzo));
const boshqa = Buffer.from("login-valyuta-1.0.0.tgz o'zgartirilgan");
console.log(verify(null, boshqa, publicKey, imzo));

Konsolda:

text
64
true
false

Kanon natijasi bilan bir xil: imzo 64 bayt, to'g'ri fayl — true, boshqasi — false. sign ning birinchi argumenti — hash algoritmi; Ed25519 uchun null (u o'zi ichida hash qiladi).

Ochiq kalitni tarqatish uchun matnga aylantiriladi:

js
publicKey.export({ type: "spki", format: "pem" });
// -----BEGIN PUBLIC KEY-----
// MCowBQYDK2VwAyEA…
// -----END PUBLIC KEY-----

4.2 Web Crypto: ECDSA P-256

Brauzerda (va Node'da) xuddi shu g'oya — crypto.subtle. Web push darsida VAPID kalitlari ECDSA P-256 juftligi ekanini ko'rgan edik. Bu blok saytda ham ishlaydi:

js
const juft = await crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-256" },
  true,
  ["sign", "verify"],
);
const xom = await crypto.subtle.exportKey("raw", juft.publicKey);
console.log(new Uint8Array(xom).length);

const algoritm = { name: "ECDSA", hash: "SHA-256" };
const xabar = new TextEncoder().encode("100 USD");
const { privateKey, publicKey } = juft;
const imzo = await crypto.subtle.sign(algoritm, privateKey, xabar);
const { subtle } = crypto;
const ok = await subtle.verify(algoritm, publicKey, imzo, xabar);
console.log(imzo.byteLength, ok);

Konsolda:

text
65
64 true

65 bayt — siqilmagan P-256 ochiq kalit (0x04 + 32 bayt x + 32 bayt y). Aynan shu 65 bayt base64url bilan VAPID kalit sifatida beriladi. Web Crypto'da hamma narsa asinxron va kalit obyektlari ichida — yopiq kalitni extractable: false bilan yaratsangiz, uni hatto o'z kodingiz ham eksport qilolmaydi.

5. Uch vazifa — bitta jadval

Jarayonni bir rasmda ko'ring. Har vazifa uchun nima kiradi va nima chiqadi:

flowchart LR
  P["parol"] -->|"argon2id + tuz"| H["hash: ochilmaydi"]
  M["ma'lumot"] -->|"AES-GCM + kalit"| S["shifr: kalit ochadi"]
  F["fayl"] -->|"Ed25519 yopiq kalit"| I["imzo: ochiq kalit bilan"]
Vazifa Vosita Kalit Ortga qaytadimi
Parol saqlash argon2id, scrypt yo'q (tuz) Yo'q
Xabar butunligi (ikki tomon) HMAC bitta sir Yo'q (faqat solishtirish)
Ma'lumotni yashirish AES-256-GCM bitta sir Ha, kalit bilan
Muallifni isbotlash (ko'p tekshiruvchi) Ed25519, ECDSA juftlik Yo'q (faqat tekshirish)

6. Hujumchi nigohi

Xato Hujum To'g'risi
sha256(parol) Videokartada milliardlab urinish argon2id / scrypt
Tuzsiz hash Rainbow table, bir xil parollar ko'rinadi Har foydalanuvchiga randomBytes(16)
Hash'ni === bilan solishtirish Vaqt hujumi timingSafeEqual
Qayta ishlatilgan IV GCM'da kalit va matn sizib chiqadi Har safar randomBytes(12)
aes-*-cbc, auth tag'siz Shifrni o'zgartirish, padding oracle GCM (AEAD)
Kalit kodda GitHub'dan o'g'irlanadi env / sirlar menejeri
bcrypt da 72 baytdan uzun parol Oxiri jim tashlanadi argon2id yoki uzunlikni cheklash

Eng muhim qoida: o'z kriptografiyangizni o'ylab topmang. Algoritmlarni birlashtirish, "o'z" shifringizni yozish — deyarli har doim zaif. Standart vositalarni standart parametrlar bilan ishlating.

7. CLI qadami o'rniga: nega valyuta ga kirmadi

Bu darsda valyuta ga kod qo'shilmaydi. Sababi o'lchov bilan aniq: CLI'da parol yo'q, maxfiy ma'lumot yo'q, imzolanadigan narsa yo'q. Kesh — ochiq valyuta kurslari. Kanon t21-crypto2 tajribasi alohida skriptda qilindi va natijalari yuqorida (scrypt 286 ms, argon2id 31 ms, AES-GCM buzilgan shifr xatosi, Ed25519 64 bayt, timingSafeEqual uzunlik xatosi).

Qachon kerak bo'lardi? Agar valyuta pullik API kalitini saqlasa — u diskda AES-GCM bilan yoki, yaxshisi, OS kalit omborida (Windows Credential Manager, macOS Keychain) turishi kerak. Paketni imzolash esa npm'ning o'zida bor: trusted publishing'dagi provenance (npm paket nashri) — Sigstore imzosi.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
ERR_CRYPTO_INVALID_SCRYPT_PARAMS … memory limit exceeded N katta, maxmem sukut 32 MiB maxmem: 256 * 2 ** 20
Unsupported state or unable to authenticate data Shifr/tag/kalit/IV mos emas yoki buzilgan Saqlash formatini tekshiring; buzilgan ma'lumotni rad eting
Invalid key length Kalit 32 bayt emas randomBytes(32) yoki KDF
Invalid initialization vector IV hajmi noto'g'ri GCM uchun 12 bayt
Server kirishda "qotib" qoladi scryptSync/argon2Sync event loop'da Asinxron variant
Hamma parol "noto'g'ri" bo'lib qoldi Parametr yoki tuz saqlanmagan algoritm$parametr$tuz$hash formati

9. Mashqlar

1-mashq (oson): qaysi vosita?

  • Foydalanuvchi parolini saqlash:
  • Hash'ni har foydalanuvchida noyob qiladigan tasodifiy qo'shimcha:
  • AES-GCM'da har shifrlashda yangi bo'lishi shart bo'lgan qiymat:
  • Imzoni tekshirish uchun kerak bo'lgan kalit:
  • Ed25519 imzosi necha bayt?
Yechim
  • argon2id — OWASP birinchi tavsiyasi (scrypt ham to'g'ri).
  • tuz (salt) — randomBytes(16), ochiq saqlanadi.
  • IV (nonce) — 12 bayt, qayta ishlatilmaydi.
  • ochiq — yopiq kalit faqat imzolaydi.
  • 64 — kanon o'lchovi ham shunday.

2-mashq (o'rta): shifrni matn sifatida saqlash

yopish(matn, kalit) → "iv.tag.shifr" (har qism base64url) va ochish(satr, kalit) funksiyalarini yozing. Ikki marta yopib, satrlar har xil, lekin ikkalasi ham to'g'ri ochilishini ko'rsating. Boshqa kalit bilan ochishga urinib, xatoni ushlang.

Yechim
js
import {
  createCipheriv, createDecipheriv, randomBytes,
} from "node:crypto";

const b64 = (b) => b.toString("base64url");

function yopish(matn, kalit) {
  const iv = randomBytes(12);
  const c = createCipheriv("aes-256-gcm", kalit, iv);
  const shifr = Buffer.concat([c.update(matn, "utf8"), c.final()]);
  return [iv, c.getAuthTag(), shifr].map(b64).join(".");
}

function ochish(satr, kalit) {
  const [iv, tag, shifr] = satr
    .split(".")
    .map((q) => Buffer.from(q, "base64url"));
  const d = createDecipheriv("aes-256-gcm", kalit, iv);
  d.setAuthTag(tag);
  return Buffer.concat([d.update(shifr), d.final()]).toString("utf8");
}

const kalit = randomBytes(32);
const a = yopish("sk_test_123", kalit);
const b = yopish("sk_test_123", kalit);
console.log(a === b, a.split(".").length);
console.log(ochish(a, kalit), ochish(b, kalit));
try {
  ochish(a, randomBytes(32));
} catch (xato) {
  console.log("boshqa kalit:", xato.message);
}

Konsolda:

text
false 3
sk_test_123 sk_test_123
boshqa kalit: Unsupported state or unable to authenticate data

Har yopishda IV yangi — satrlar har xil (false). Bu ham xavfsizlik: hujumchi bir xil ma'lumot ikki marta saqlanganini bilmaydi. Noto'g'ri kalit — tag mos kelmaydi, ochilmaydi.

3-mashq (qiyin): parametr yangilanishi

«scrypt bilan saqlash va tekshirish» bo'limidagi formatdan foydalanib kirish(parol, saqlangan, joriyN) yozing: parolni tekshirsin va to'g'ri bo'lsa-yu, saqlangan N joriyN dan kichik bo'lsa — yangi hash ham qaytarsin: { ok: true, yangi: "scrypt$…" }. Noto'g'ri parol — { ok: false }. Eski N=2^12 bilan saqlab, N=2^14 bilan kiring.

Yechim
js
import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
import { promisify } from "node:util";

const scryptP = promisify(scrypt);
const b64 = (b) => b.toString("base64url");

async function hashla(parol, N) {
  const tuz = randomBytes(16);
  const hash = await scryptP(parol, tuz, 32, { N });
  return `scrypt$${N}$${b64(tuz)}$${b64(hash)}`;
}

async function kirish(parol, saqlangan, joriyN) {
  const [, n, tuz64, hash64] = saqlangan.split("$");
  const kutilgan = Buffer.from(hash64, "base64url");
  const tuz = Buffer.from(tuz64, "base64url");
  const hash = await scryptP(parol, tuz, 32, { N: Number(n) });
  if (!timingSafeEqual(hash, kutilgan)) return { ok: false };
  if (Number(n) >= joriyN) return { ok: true };
  return { ok: true, yangi: await hashla(parol, joriyN) };
}

const eski = await hashla("toshkent2026", 2 ** 12);
const natija = await kirish("toshkent2026", eski, 2 ** 14);
console.log(natija.ok, natija.yangi.split("$")[1]);
console.log(await kirish("toshkent2025", eski, 2 ** 14));
const keyin = await kirish("toshkent2026", natija.yangi, 2 ** 14);
console.log(keyin);

Konsolda:

text
true 16384
{ ok: false }
{ ok: true }

Parolni qayta hash qilish faqat foydalanuvchi kirganda mumkin — o'sha paytdagina ochiq parol qo'lingizda. Shuning uchun parametr yangilanishi bosqichma-bosqich: kirgan foydalanuvchilar yangi formatga o'tadi. Haqiqiy auth tizimini Parolni xavfsiz saqlash darsida qurasiz.

10. Real ishda

  • Auth — ro'yxatdan o'tish va kirish: argon2id/bcrypt. Express, NestJS, Next.js'dagi tayyor kutubxonalar (Auth.js, Lucia uslubidagi yechimlar) ichida shu.
  • Ma'lumotlar bazasida maydon shifrlash — pasport raqami, telefon kabi shaxsiy ma'lumot AES-GCM bilan; kalitlar KMS (AWS KMS, Google Cloud KMS) da. O'zbekistonning shaxsiy ma'lumotlar qonuni (O'RQ-547) talablari bilan bog'liq (shaxsiy ma'lumotlar qonunchiligi).
  • Imzolar — JWT (RS256/EdDSA), npm provenance, Git commit imzosi, Telegram/Stripe webhook'lari (HMAC).
  • Intervyu savollari: "Parolni qanday saqlaysiz?", "Shifrlash va hash farqi?", "Nega IV qayta ishlatilmaydi?", "Simmetrik va asimmetrik farqi?".

Xulosa

  • Parol — sekin KDF bilan: argon2id (Node 24 da crypto.argon2, kanon: 31 ms, 19 MiB) yoki scrypt (286 ms); har foydalanuvchiga tuz, format algoritm$parametr$tuz$hash.
  • SHA-256 parol uchun emas — u tez; bcrypt 72 baytdan uzun parolni kesadi.
  • AES-256-GCM: 32 bayt kalit, har safar yangi 12 bayt IV, 16 bayt auth tag — buzilgan shifr xato beradi (Unsupported state or unable to authenticate data).
  • Ed25519: yopiq kalit imzolaydi, ochiq kalit tekshiradi (64 bayt); brauzerda — crypto.subtle (ECDSA P-256, 65 baytlik ochiq kalit).
  • valyuta ga kirmadi — CLI'da parol va sir yo'q; o'z kriptografiyangizni o'ylab topmang.

Keyingi dars: zlib: siqish — gzip, brotli va zstd o'lchovlari va nega valyuta keshini siqmaymiz.

Manbalar

  • Node.js 24 hujjati — Crypto (scrypt, argon2 — "added: v24.7.0", alohida stability belgisisiz, modul — Stability 2; createCipheriv, generateKeyPair, sign, verify)
  • OWASP — Password Storage Cheat Sheet (argon2id m=19 MiB t=2 p=1; pbkdf2 600 000)
  • RFC 9106 — Argon2; RFC 7914 — scrypt; RFC 8032 — Ed25519; NIST SP 800-38D — GCM
  • MDN — SubtleCrypto
  • npm: argon2 0.45.1, bcrypt 6.0.0, bcryptjs 3.0.3 — 2026-10-23 holatiga
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js crypto 2: parolni hash'lash, shifrlash va imzo (AES-GCM, Ed25519) — IlmHamroh