6-bo‘lim

Kalit hosil qilish

Paroldan kalit olish (PBKDF2, Argon2) va bitta kalitdan bir nechta kalit olish (HKDF).

🕑 14 daqiqa o‘qish 📄 1 069 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: parol kalit emas
  2. PBKDF2
  3. Argon2id - zamonaviy tanlov
  4. HKDF - bitta kalitdan ko'p kalit
  5. HKDF ning ikki bosqichi
  6. Kalitlarni ajratishning amaliy misoli
  7. Qaysi funksiyani qachon
  8. Xulosa

Shifrlash uchun kalit kerak. Uni qayerdan olish kerak?

Ikki butunlay boshqa vazifa bor va ular turli vositalar talab qiladi:

VazifaKirishVosita
Paroldan kalit olishPast entropiyaSekin KDF: Argon2id, PBKDF2
Kalitdan kalitlar olishYuqori entropiyaTez KDF: HKDF

Ularni chalkashtirish jiddiy zaiflikka olib keladi.

Muammo: parol kalit emas #

PHP
function korsat(string $nom, string $hex): void
{
    printf("%-22s %s%s", $nom . ':', $hex, PHP_EOL);
}
PHP
$parol = 'Parol123';

// XATO 1: parolni to'g'ridan-to'g'ri kalit sifatida ishlatish
$xato1 = str_pad($parol, 32, "\x00");
korsat('parol + nol', bin2hex($xato1));

// XATO 2: oddiy xesh - juda tez
$xato2 = hash('sha256', $parol, true);
korsat('sha256(parol)', bin2hex($xato2));

echo PHP_EOL, 'ikkalasi ham 32 bayt, lekin ikkalasi ham XATO', PHP_EOL;
Natija
parol + nol:           5061726f6c313233000000000000000000000000000000000000000000000000
sha256(parol):         46fac07aae8ccfcfc7e52b540af49b9c0886feb960aced5ea63e4a4aab10f510

ikkalasi ham 32 bayt, lekin ikkalasi ham XATO
Nima uchun ikkalasi ham xato

Birinchi usul - parolni to'ldirish. Muammolari:

MuammoIzoh
Entropiya past"Parol123" da ~30 bit entropiya bor, 256 emas
Nol baytlarKalit maydonining katta qismi doimiy
Lug'at hujumiHujumchi to'g'ridan-to'g'ri parollarni sinaydi

Ikkinchi usul - sha256. U birinchisidan yaxshiroq ko'rinadi, lekin 3-bo'limdagi muammo o'z kuchida qoladi: juda tez. Hujumchi soniyasiga millionlab parolni sinaydi.

Ikkalasida ham tuz yo'q - demak oldindan hisoblangan jadval ishlaydi.

Kerak bo'lgan narsa: tuz + ataylab sekinlik.

PBKDF2 #

PHP
$parol = 'Parol123';
$tuz = hex2bin(str_repeat('5a', 16));   // misol uchun qat'iy

// hash_pbkdf2(algo, parol, tuz, takrorlar, uzunlik, xom_baytmi)
$kalit = hash_pbkdf2('sha256', $parol, $tuz, 600000, 32, true);

korsat('kalit (32 bayt)', bin2hex($kalit));

// Bir xil kirish - bir xil kalit
$yana = hash_pbkdf2('sha256', $parol, $tuz, 600000, 32, true);
var_dump(hash_equals($kalit, $yana));

// Tuz o'zgarsa - butunlay boshqa kalit
$boshqaTuz = hex2bin(str_repeat('5b', 16));
$boshqa = hash_pbkdf2('sha256', $parol, $boshqaTuz, 600000, 32, true);
korsat('boshqa tuz bilan', bin2hex($boshqa));
Natija
kalit (32 bayt):       3da99e012d49e8f24a619972ce428428170e921998201597613daf462d5bdfa8
bool(true)
boshqa tuz bilan:      9b51abbfcd86937fc691b2d7009714164db1235e63426e9f38757795b3302666
Takrorlar soni

PBKDF2 ning butun kuchi takrorlar sonida. U qancha katta bo'lsa, hujum shuncha qimmat.

YilTavsiya etilgan (SHA-256)
20001 000
201010 000
2017100 000
Hozir600 000 dan kam emas

Bu raqamlar uskuna tezlashgani sari o'sib boradi. Kodingizda ularni sozlama sifatida saqlang, qattiq yozmang.

Muhim: PBKDF2 faqat protsessor vaqtini talab qiladi, xotira talab qilmaydi. Shuning uchun uni GPU da parallel ishlatish oson - Argon2id bu jihatdan kuchliroq.

Argon2id - zamonaviy tanlov #

PHP
$parol = 'Parol123';

// PHP da Argon2id parol XESHI sifatida mavjud
$xesh = password_hash($parol, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,   // 64 MB
    'time_cost' => 4,
    'threads' => 1,
]);

// Parametrlar xeshning ichida yozilgan
echo 'parametrlar: ', substr($xesh, 0, 30), PHP_EOL;
echo 'uzunlik:     ', strlen($xesh), ' belgi', PHP_EOL;

// Undan SHIFRLASH kaliti kerak bo'lsa - HKDF bilan ajratamiz
$shifrKaliti = hash_hkdf('sha256', hash('sha256', $xesh, true),
    32, 'shifrlash-v1');

echo 'kalit uzunligi: ', strlen($shifrKaliti), ' bayt', PHP_EOL;

// Tuz tasodifiy bo'lgani uchun har chaqiruvda BOSHQA kalit chiqadi
$ikkinchi = password_hash($parol, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536, 'time_cost' => 4, 'threads' => 1,
]);
$ikkinchiKalit = hash_hkdf('sha256', hash('sha256', $ikkinchi, true),
    32, 'shifrlash-v1');

echo 'ikki kalit bir xilmi: ',
    hash_equals($shifrKaliti, $ikkinchiKalit) ? 'ha' : 'YOQ', PHP_EOL;
Natija
parametrlar: $argon2id$v=19$m=65536,t=4,p=1
uzunlik:     97 belgi
kalit uzunligi: 32 bayt
ikki kalit bir xilmi: YOQ

Oxirgi qatorga e'tibor bering: ikki kalit har xil chiqdi. Sabab - password_hash har chaqiruvda yangi tuz yaratadi.

Demak bu usul bilan shifrlangan ma'lumotni qayta ochib bo'lmaydi: kalitni tiklash uchun o'sha xesh satri (tuzi bilan birga) saqlangan bo'lishi kerak.

PHP da Argon2id dan to'g'ridan-to'g'ri kalit olish yo'q

password_hash parol xeshi qaytaradi (tuz va parametrlar bilan birga), xom kalit emas.

Uch variant bor:

VariantBaho
sodium_crypto_pwhashEng to'g'ri - lekin sodium kengaytmasi kerak
Yuqoridagidek xeshdan HKDF bilan ajratishIshlaydi, lekin nozik
PBKDF2 (hash_pbkdf2)Har doim mavjud, oddiy va tushunarli

Agar sodium mavjud bo'lsa:

KOD
$kalit = sodium_crypto_pwhash(
    32, $parol, $tuz,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE
);

Kengaytma yo'q bo'lsa - PBKDF2 ni yuqori takrorlar bilan ishlating. Bu ushbu darslikda tanlangan yo'l.

HKDF - bitta kalitdan ko'p kalit #

Endi butunlay boshqa vazifa. Sizda allaqachon kuchli kalit bor va undan bir nechta maqsad uchun kalit kerak.

PHP
// Asosiy kalit - yuqori entropiyali (random_bytes dan olingan)
$asosiy = hex2bin(str_repeat('9c', 32));

// Har maqsad uchun ALOHIDA kalit
$shifrlash = hash_hkdf('sha256', $asosiy, 32, 'shifrlash');
$imzolash = hash_hkdf('sha256', $asosiy, 32, 'imzolash');
$kuki = hash_hkdf('sha256', $asosiy, 32, 'kuki');

korsat('shifrlash', bin2hex($shifrlash));
korsat('imzolash', bin2hex($imzolash));
korsat('kuki', bin2hex($kuki));

echo PHP_EOL, 'uchalasi bir asosiy kalitdan, lekin BOGLIQ EMAS', PHP_EOL;
Natija
shifrlash:             333af06e84756d182d427df717e772b795496e196216225ab5bc0bd54dc9f25f
imzolash:              3f9e19f35848bb2851d1494024eb05321826328d177ea9f47ea30ac5155c8d03
kuki:                  d997113d3a48ed1079e3ec18cc292941d0f896ea7eef3712bbba0c92410caad9

uchalasi bir asosiy kalitdan, lekin BOGLIQ EMAS
info parametri - domen ajratish

Uchinchi argument ('shifrlash', 'imzolash') - bu kontekst yorlig'i. U kalitlarni bir-biridan ajratadi.

Nima uchun bu muhim:

Bitta kalitni hamma joyda ishlatishAjratilgan kalitlar
Bir tizimda sizib chiqsa - hammasi ochiladiFaqat o'sha maqsad zarar ko'radi
Protokollararo hujum mumkinKalitlar bir-biriga bog'liq emas
Kalit almashtirish - hamma joydaAlohida almashtirish mumkin

Yorliqni versiyalash ham foydali:

KOD
hash_hkdf('sha256', $asosiy, 32, 'shifrlash-v2')

Shunda kalit sxemasini o'zgartirganda eski ma'lumot ochiq qoladi (18-bo'lim).

Ikki xil kalit hosil qilish - ikki xil vosita Paroldan kalit - SEKIN bo'lishi kerak "Parol123" ~30 bit Argon2id 600 000 takror 64 MB xotira 32 B Sekinlik - HIMOYA Foydalanuvchi: 1 marta, 200 ms Hujumchi: milliard marta - imkonsiz tuz MAJBURIY Kalitdan kalitlar - TEZ bo'lishi mumkin random_bytes 256 bit HKDF tez shifrlash imzolash kuki Sekinlik KERAK EMAS Kirish allaqachon kuchli - taxmin qilib bo'lmaydi Maqsad: kalitlarni bir-biridan ajratish info = kontekst yorlig'i Eng ko'p uchraydigan xato Paroldan kalit olishda HKDF yoki sha256 ishlatish - ular tez, demak himoya bermaydi. Oddiy qoida Kirish odam yodlagan bo'lsa - sekin KDF. Kirish mashina yaratgan bo'lsa - HKDF.
Kirish entropiyasi vositani belgilaydi

HKDF ning ikki bosqichi #

PHP
// HKDF ikki qadamdan iborat: extract va expand
$xomManba = hex2bin(str_repeat('3f', 32));
$tuz = hex2bin(str_repeat('11', 16));

// PHP da hash_hkdf ikkalasini birga bajaradi
$kalit1 = hash_hkdf('sha256', $xomManba, 32, 'kontekst', $tuz);
korsat('tuz bilan', bin2hex($kalit1));

// Tuzsiz ham mumkin (tuz ixtiyoriy)
$kalit2 = hash_hkdf('sha256', $xomManba, 32, 'kontekst');
korsat('tuzsiz', bin2hex($kalit2));

// Turli uzunlikdagi kalitlar
foreach ([16, 32, 64] as $n) {
    printf('%2d bayt: %s%s', $n,
        bin2hex(hash_hkdf('sha256', $xomManba, $n, 'test')), PHP_EOL);
}
Natija
tuz bilan:             46b5713570cceecfa2c0b7f51b41173c08e756291f05bf3eb1f9a81ba24e1230
tuzsiz:                0ec74b127a549d788a4c2e5ffd72a8f5c44efa03ac7bd0f64ca7fe49bdcd9380
16 bayt: 5d3472e0966a3725aace96a26c4bb25d
32 bayt: 5d3472e0966a3725aace96a26c4bb25df3490a5acf3d840ee80282ae8e973e13
64 bayt: 5d3472e0966a3725aace96a26c4bb25df3490a5acf3d840ee80282ae8e973e13a32390ced09867eacafa8226398f5f6c12514469dfa03a769e6e14c9ce4b8fba
Chiqish uzunligi va prefiks

Yuqoridagi natijaga diqqat qiling: 16, 32 va 64 baytlik kalitlarning boshlanishi bir xil.

Bu HKDF ning ishlash tarzidan kelib chiqadi - u chiqishni bloklar bilan kengaytiradi.

Amaliy oqibat: bitta HKDF chaqiruvidan ikki kalit kesib olmang.

Noto'g'riTo'g'ri
$x = hash_hkdf(..., 64, 'k') va uni ikkiga bo'lishIkki marta chaqirish, turli info bilan

Noto'g'ri usul ishlaydi, lekin kalitlar bir-biriga bog'liq bo'lib qoladi - va kod o'qiganda buni ko'rish qiyin.

Kalitlarni ajratishning amaliy misoli #

PHP
final class KalitOmbori
{
    private const VERSIYA = 'v1';

    public function __construct(private readonly string $asosiy)
    {
        if (strlen($asosiy) < 32) {
            throw new InvalidArgumentException(
                'asosiy kalit kamida 32 bayt bolishi kerak');
        }
    }

    public function kalit(string $maqsad, int $uzunlik = 32): string
    {
        return hash_hkdf('sha256', $this->asosiy, $uzunlik,
            self::VERSIYA . ':' . $maqsad);
    }

    public function barmoqIzi(string $maqsad): string
    {
        // Kalitning O'ZINI emas, uning qisqa barmoq izini ko'rsatamiz
        return substr(bin2hex(hash('sha256', $this->kalit($maqsad), true)), 0, 16);
    }
}
PHP
$ombor = new KalitOmbori(hex2bin(str_repeat('42', 32)));

foreach (['kuki-imzosi', 'fayl-shifri', 'api-imzo', 'kesh-kaliti'] as $m) {
    printf("%-14s %s%s", $m, $ombor->barmoqIzi($m), PHP_EOL);
}

echo PHP_EOL;

// Qisqa asosiy kalit rad etiladi
try {
    new KalitOmbori('qisqa');
} catch (InvalidArgumentException $x) {
    echo 'RAD: ', $x->getMessage(), PHP_EOL;
}
Natija
kuki-imzosi    f00e42e967d63b60
fayl-shifri    d388387f84356e95
api-imzo       a75838d92bbceac1
kesh-kaliti    b6209e2528965f21

RAD: asosiy kalit kamida 32 bayt bolishi kerak
Jurnalga kalitni hech qachon yozmang

Yuqoridagi barmoqIzi() metodiga e'tibor bering: u kalitni emas, kalitning xeshini qaytaradi.

Bu nosozlik qidirishda juda foydali:

MaqsadNima yoziladi
"Qaysi kalit ishlatildi"Barmoq izi (16 hex belgi)
"Kalitlar mos keldimi"Ikki barmoq izini solishtirish
"Kalit almashtirildimi"Barmoq izi o'zgardimi

Kalitning o'zi jurnalga tushsa, u:

  • Jurnal fayllarida qoladi
  • Markazlashgan jurnal tizimiga ketadi
  • Zaxira nusxalarga tushadi
  • Xato hisobotlariga (Sentry va sh.k.) yuboriladi

Amalda kalit sizib chiqishining eng keng tarqalgan sababi - var_dump va error_log.

Qaysi funksiyani qachon #

VaziyatFunksiya
Foydalanuvchi paroli → shifr kalitihash_pbkdf2 (600k+) yoki sodium_crypto_pwhash
Parolni saqlashpassword_hash (kalit emas!)
Asosiy kalit → maqsadli kalitlarhash_hkdf
Yangi tasodifiy kalitrandom_bytes(32)
Umumiy sirdan (DH) kalithash_hkdf
API kaliti yaratishrandom_bytes(32) + base64url
PHP
// To'g'ri usullar to'plami
echo 'yangi kalit:      ', strlen(random_bytes(32)), ' bayt', PHP_EOL;

$paroldan = hash_pbkdf2('sha256', 'Parol123',
    hex2bin(str_repeat('aa', 16)), 600000, 32, true);
echo 'paroldan:         ', strlen($paroldan), ' bayt', PHP_EOL;

$ajratilgan = hash_hkdf('sha256', $paroldan, 32, 'fayl-shifri');
echo 'ajratilgan:       ', strlen($ajratilgan), ' bayt', PHP_EOL;

echo 'parol saqlash:    ', substr(password_hash('Parol123',
    PASSWORD_BCRYPT, ['cost' => 4]), 0, 7), '...', PHP_EOL;
Natija
yangi kalit:      32 bayt
paroldan:         32 bayt
ajratilgan:       32 bayt
parol saqlash:    $2y$04$...
Amaliy topshiriq
  1. Parolni to'ldirib kalit qilishga urinib ko'ring va kamchiliklarini yozing.
  2. hash_pbkdf2 bilan kalit oling va bir xil kirishda bir xil natija chiqishini tekshiring.
  3. Tuzni o'zgartiring - kalit qanday o'zgardi?
  4. Takrorlar sonini 1000 dan 600000 gacha oshirib, vaqtni kuzating.
  5. hash_hkdf bilan bitta asosiy kalitdan uchta kalit oling.
  6. info yorlig'ini o'zgartirib, kalitlar farq qilishini ko'ring.
  7. 16, 32 va 64 baytlik chiqishlarning boshlanishini solishtiring.
  8. Nima uchun bitta chiqishni ikkiga bo'lish noto'g'ri ekanini tushuntiring.
  9. Kalit omborini yozing va barmoq izlarini chiqaring.
  10. Paroldan kalit olishda HKDF ishlatish nima uchun xato ekanini yozing.

Xulosa #

  • Kalit hosil qilishning ikki xil vazifasi bor va ular turli vositalar talab qiladi.
  • Parolni to'ldirib yoki sha256 bilan kalit qilish - xato: entropiya past, tezlik yuqori.
  • Paroldan kalit olish uchun sekin KDF kerak: Argon2id yoki PBKDF2.
  • PBKDF2 da takrorlar soni hozir kamida 600 000 bo'lishi tavsiya etiladi.
  • Takrorlar sonini sozlama sifatida saqlang - u vaqt o'tishi bilan oshadi.
  • password_hash parol xeshi beradi, shifrlash kaliti emas.
  • HKDF yuqori entropiyali kalitdan bir nechta kalit ajratadi va u tez bo'lishi mumkin.
  • info parametri - domen ajratish: har maqsadga alohida kalit.
  • Bitta HKDF chiqishini ikkiga bo'lmang - ikki marta, turli info bilan chaqiring.
  • Kalitni jurnalga hech qachon yozmang - faqat uning barmoq izini.

Keyingi bo'limda simmetrik shifrlash va ECB rejimining nima uchun halokatli ekanini ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.