12-bo‘lim

Kalit almashinuvi

Ochiq kanal orqali umumiy kalitga kelishish - ECDH, oldinga maxfiylik va o'rtadagi odam muammosi.

🕑 14 daqiqa o‘qish 📄 964 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. G'oya
  2. Xom sirni to'g'ridan-to'g'ri ishlatmang
  3. Oldinga maxfiylik (forward secrecy)
  4. O'rtadagi odam hujumi
  5. Barmoq izini solishtirish
  6. Algoritmlarni tanlash
  7. Xulosa

Ikki tomon hech qachon uchrashmagan bo'lsa, ular umumiy simmetrik kalitni qanday tanlaydi?

10-bo'limda bir yechim ko'rdik: kalitni RSA bilan shifrlab yuborish. Lekin undan yaxshiroq usul bor - Diffie-Hellman kalit almashinuvi.

G'oya #

PHP
// Darslik uchun qat'iy kalitlar. Real tizimda har sessiyaga yangi juft.
const ALISA_MAXFIY = <<<'PEM'
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICidPlZx2J4A1sx7b97ButVhDSTQxsICIr+MxYXTse6noAoGCCqGSM49
AwEHoUQDQgAEe39CeoZQUWyQbRVU6L8lQ8S8kFR7ls40P0w/bPznljOVza657HDq
DmUa5skHMX0hJEVP8cb4DFfYjWWQSCXr7Q==
-----END EC PRIVATE KEY-----
PEM;

const ALISA_OCHIQ = <<<'PEM'
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEe39CeoZQUWyQbRVU6L8lQ8S8kFR7
ls40P0w/bPznljOVza657HDqDmUa5skHMX0hJEVP8cb4DFfYjWWQSCXr7Q==
-----END PUBLIC KEY-----
PEM;

const BOBUR_MAXFIY = <<<'PEM'
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDUTZL094fJVDFkFG5S9+7yYZ9m9ywlhnRb0Hd4JPL1coAoGCCqGSM49
AwEHoUQDQgAEc/7W8mWzxt5PAlQVNSUcWBb58kVHJq9onVftuVmK4SVkcYOO/zCz
p+8FVN/umE3wA8Pbka2NahVJPqxj+EKzWg==
-----END EC PRIVATE KEY-----
PEM;

const BOBUR_OCHIQ = <<<'PEM'
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc/7W8mWzxt5PAlQVNSUcWBb58kVH
Jq9onVftuVmK4SVkcYOO/zCzp+8FVN/umE3wA8Pbka2NahVJPqxj+EKzWg==
-----END PUBLIC KEY-----
PEM;

function korsat(string $nom, string $qiymat): void
{
    printf("%-22s %s%s", $nom . ':', $qiymat, PHP_EOL);
}
PHP
// Alisa o'z maxfiy kaliti + Boburning OCHIQ kaliti
$alisaMaxfiy = openssl_pkey_get_private(ALISA_MAXFIY);
$umumiy1 = openssl_pkey_derive(BOBUR_OCHIQ, $alisaMaxfiy, 32);

// Bobur o'z maxfiy kaliti + Alisaning OCHIQ kaliti
$boburMaxfiy = openssl_pkey_get_private(BOBUR_MAXFIY);
$umumiy2 = openssl_pkey_derive(ALISA_OCHIQ, $boburMaxfiy, 32);

korsat('Alisa hisobladi', bin2hex($umumiy1));
korsat('Bobur hisobladi', bin2hex($umumiy2));
korsat('bir xilmi', hash_equals($umumiy1, $umumiy2) ? 'HA' : 'yoq');
Natija
Alisa hisobladi:       8bd8642b109784c6c208b8710d14cdf4ab4c32960b1191fb65c28571a075b0af
Bobur hisobladi:       8bd8642b109784c6c208b8710d14cdf4ab4c32960b1191fb65c28571a075b0af
bir xilmi:             HA
Sehr shu yerda

Alisa va Bobur bir xil sirni hisoblab olishdi. Lekin bu sir hech qachon kanalda uzatilmadi.

Kanalda faqat ochiq kalitlar yurdi - ularni hamma ko'rishi mumkin.

Matematik asos:

KOD
Alisa: (Bobur_ochiq) ^ Alisa_maxfiy
Bobur: (Alisa_ochiq) ^ Bobur_maxfiy

Ikkalasi ham bir xil natijaga olib keladi. Kuzatuvchi esa faqat ikkita ochiq kalitni ko'radi va ulardan sirni chiqara olmaydi (diskret logarifm masalasi).

Ranglar bilan tushuntirish: ikki kishi bir xil sariq bo'yoqni oladi, har biri o'z maxfiy rangini qo'shadi va aralashmani almashadi. Keyin har biri kelgan aralashmaga yana o'z maxfiy rangini qo'shadi. Natija - ikkalasida bir xil rang. Kuzatuvchi esa aralashmani ajrata olmaydi.

Xom sirni to'g'ridan-to'g'ri ishlatmang #

PHP
$alisaMaxfiy = openssl_pkey_get_private(ALISA_MAXFIY);
$xomSir = openssl_pkey_derive(BOBUR_OCHIQ, $alisaMaxfiy, 32);

korsat('xom sir', bin2hex($xomSir));

// XOM SIRNI KALIT SIFATIDA ISHLATMANG - u bir tekis taqsimlanmagan.
// HKDF bilan o'tkazing va maqsadlarga ajrating:
$shifrKaliti = hash_hkdf('sha256', $xomSir, 32, 'sessiya-shifr-v1');
$macKaliti = hash_hkdf('sha256', $xomSir, 32, 'sessiya-mac-v1');

korsat('shifr kaliti', bin2hex($shifrKaliti));
korsat('mac kaliti', bin2hex($macKaliti));
Natija
xom sir:               8bd8642b109784c6c208b8710d14cdf4ab4c32960b1191fb65c28571a075b0af
shifr kaliti:          89f7172178009427a1cf742baed7efee1f8859b6dbd0f511632fb35c18a2de1c
mac kaliti:            340125eac02b0ae25cdf9472f1b44ac3d823083bebf39b207461916d0f7c6c7b
Nima uchun HKDF majburiy

Diffie-Hellman natijasi - egri chiziqdagi nuqtaning koordinatasi. U tasodifiy baytlar emas: uning ba'zi bitlari boshqalariga qaraganda oldindan aytiladigan.

Shifrlash algoritmlari esa bir tekis taqsimlangan kalit kutadi.

Xom sirni ishlatishHKDF orqali o'tkazish
Bitlar bir tekis emasBir tekis taqsimlangan
Bitta kalit - hamma maqsad uchunHar maqsadga alohida kalit
Kontekst bog'lanmaganinfo bilan bog'lanadi

Amaliy qoida: DH natijasini hech qachon to'g'ridan-to'g'ri kalit sifatida ishlatmang. Har doim KDF orqali o'tkazing.

TLS 1.3 da ham xuddi shunday: umumiy sir bir necha bosqichli HKDF orqali o'tib, alohida kalitlarga ajraladi.

Oldinga maxfiylik (forward secrecy) #

PHP
final class Sessiya
{
    private readonly string $kalit;

    public function __construct(string $ochiqKalit, string $maxfiyKalit)
    {
        $maxfiy = openssl_pkey_get_private($maxfiyKalit);
        if ($maxfiy === false) {
            throw new RuntimeException('maxfiy kalit oqilmadi');
        }

        $xom = openssl_pkey_derive($ochiqKalit, $maxfiy, 32);
        if ($xom === false) {
            throw new RuntimeException('umumiy sir hisoblanmadi');
        }

        $this->kalit = hash_hkdf('sha256', $xom, 32, 'sessiya-v1');
    }

    public function barmoqIzi(): string
    {
        return substr(bin2hex(hash('sha256', $this->kalit, true)), 0, 16);
    }

    public function shifrla(string $matn): string
    {
        $nonce = random_bytes(12);
        $teg = '';
        $shifr = openssl_encrypt($matn, 'aes-256-gcm', $this->kalit,
            OPENSSL_RAW_DATA, $nonce, $teg);

        return $nonce . $teg . $shifr;
    }

    public function och(string $paket): string
    {
        $matn = openssl_decrypt(substr($paket, 28), 'aes-256-gcm',
            $this->kalit, OPENSSL_RAW_DATA,
            substr($paket, 0, 12), substr($paket, 12, 16));

        if ($matn === false) {
            throw new RuntimeException('ochib bolmadi');
        }

        return $matn;
    }
}
PHP
$alisa = new Sessiya(BOBUR_OCHIQ, ALISA_MAXFIY);
$bobur = new Sessiya(ALISA_OCHIQ, BOBUR_MAXFIY);

korsat('Alisa kaliti', $alisa->barmoqIzi());
korsat('Bobur kaliti', $bobur->barmoqIzi());

// Alisa shifrlaydi, Bobur ochadi
$paket = $alisa->shifrla('Uchrashuv ertaga soat 10:00');
korsat('Bobur oqidi', $bobur->och($paket));

// Bobur javob beradi
$javob = $bobur->shifrla('Kelishdik');
korsat('Alisa oqidi', $alisa->och($javob));
Natija
Alisa kaliti:          7750ebfdec877660
Bobur kaliti:          7750ebfdec877660
Bobur oqidi:           Uchrashuv ertaga soat 10:00
Alisa oqidi:           Kelishdik
Vaqtinchalik kalitlar - oldinga maxfiylik

Yuqoridagi misolda kalitlar doimiy (qat'iy yozilgan). Real tizimda esa ular har sessiyaga yangi bo'ladi - bu vaqtinchalik (ephemeral) kalitlar deyiladi.

Farqi katta:

Doimiy kalitlarVaqtinchalik kalitlar
Kalit umriYillarBir sessiya
Maxfiy kalit o'g'irlansaBarcha eski yozuvlar ochiladiFaqat joriy sessiya
Yozib olingan trafikKeyinchalik ochiladiHech qachon ochilmaydi

Ikkinchi qatorga e'tibor bering. Hujumchi bugun shifrlangan trafikni yozib olib, besh yildan keyin serverning maxfiy kalitini qo'lga kiritsa:

  • Doimiy kalitlar bilan - hamma narsani ochadi
  • Vaqtinchalik kalitlar bilan - hech narsa ochilmaydi, chunki o'sha sessiyaning kalitlari allaqachon o'chirilgan

Bu oldinga maxfiylik (forward secrecy). Aynan shu sabab TLS 1.3 da faqat vaqtinchalik kalitlarga ruxsat berilgan.

Diffie-Hellman - umumiy sir kanalda uzatilmaydi Alisa maxfiy_A (hech kimga) ochiq_A derive(ochiq_B, maxfiy_A) = umumiy sir Bobur maxfiy_B (hech kimga) ochiq_B derive(ochiq_A, maxfiy_B) = umumiy sir Ochiq kanal ochiq_A ochiq_B Kuzatuvchi nima ko'radi Faqat ochiq_A va ochiq_B ni. Ulardan umumiy sirni chiqarish - amalda imkonsiz. Lekin: DH o'zi AUTENTIFIKATSIYA BERMAYDI Faol hujumchi ochiq kalitlarni almashtirib qo'yishi mumkin - va ikkala tomon ham sezmaydi. Yechim: ochiq kalitlar imzolanadi (11-bo'lim) yoki sertifikat bilan tasdiqlanadi (13-bo'lim). TLS aynan shunday qiladi: ECDHE kalit almashinuvi + sertifikat bilan imzolangan javob.
DH maxfiylikni beradi, autentiklikni esa imzo qo'shadi

O'rtadagi odam hujumi #

PHP
// Hujumchining o'z kalit jufti
$hujumMaxfiy = <<<'PEM'
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAg//U7lDWR+ujaZDQsoUa9SYA6G2Carjfn3jKfaDxPzoAoGCCqGSM49
AwEHoUQDQgAELnAcXHNotq7eBO+aiXOMCZmHm+mmpw9BJWbDjLgSUSQleE6u/0lk
GQ4rZZHVrXkkes8T3yW3cfMrAnbAkuR5ZQ==
-----END EC PRIVATE KEY-----
PEM;

$hujumOchiq = <<<'PEM'
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELnAcXHNotq7eBO+aiXOMCZmHm+mm
pw9BJWbDjLgSUSQleE6u/0lkGQ4rZZHVrXkkes8T3yW3cfMrAnbAkuR5ZQ==
-----END PUBLIC KEY-----
PEM;

// Hujumchi Alisaga o'z kalitini "Boburniki" deb beradi
$alisa = new Sessiya($hujumOchiq, ALISA_MAXFIY);
$hujumAlisaBilan = new Sessiya(ALISA_OCHIQ, $hujumMaxfiy);

// Va Boburga ham o'z kalitini "Alisaniki" deb beradi
$bobur = new Sessiya($hujumOchiq, BOBUR_MAXFIY);
$hujumBoburBilan = new Sessiya(BOBUR_OCHIQ, $hujumMaxfiy);

korsat('Alisa <-> hujumchi', $alisa->barmoqIzi());
korsat('hujumchi <-> Alisa', $hujumAlisaBilan->barmoqIzi());
korsat('Bobur <-> hujumchi', $bobur->barmoqIzi());
korsat('hujumchi <-> Bobur', $hujumBoburBilan->barmoqIzi());
Natija
Alisa <-> hujumchi:    d351c10c1c691452
hujumchi <-> Alisa:    d351c10c1c691452
Bobur <-> hujumchi:    d2331614475cf699
hujumchi <-> Bobur:    d2331614475cf699

Ikki mustaqil sessiya hosil bo'ldi. Endi hujumchi xabarlarni o'qiy va o'zgartira oladi:

PHP
$hujumMaxfiy = <<<'PEM'
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAg//U7lDWR+ujaZDQsoUa9SYA6G2Carjfn3jKfaDxPzoAoGCCqGSM49
AwEHoUQDQgAELnAcXHNotq7eBO+aiXOMCZmHm+mmpw9BJWbDjLgSUSQleE6u/0lk
GQ4rZZHVrXkkes8T3yW3cfMrAnbAkuR5ZQ==
-----END EC PRIVATE KEY-----
PEM;

$hujumOchiq = <<<'PEM'
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELnAcXHNotq7eBO+aiXOMCZmHm+mm
pw9BJWbDjLgSUSQleE6u/0lkGQ4rZZHVrXkkes8T3yW3cfMrAnbAkuR5ZQ==
-----END PUBLIC KEY-----
PEM;

$alisa = new Sessiya($hujumOchiq, ALISA_MAXFIY);
$hujumAlisaBilan = new Sessiya(ALISA_OCHIQ, $hujumMaxfiy);
$bobur = new Sessiya($hujumOchiq, BOBUR_MAXFIY);
$hujumBoburBilan = new Sessiya(BOBUR_OCHIQ, $hujumMaxfiy);

// 1. Alisa Boburga yozadi (deb o'ylaydi)
$paket = $alisa->shifrla('Hisobga 1000000 som otkaz');

// 2. Hujumchi uni ochadi
$oqilgan = $hujumAlisaBilan->och($paket);
korsat('hujumchi oqidi', $oqilgan);

// 3. Hujumchi o'zgartiradi va Boburga qayta shifrlab yuboradi
$soxta = str_replace('1000000', '9000000', $oqilgan);
$yangiPaket = $hujumBoburBilan->shifrla($soxta);

// 4. Bobur ochadi - hech narsa sezmaydi
korsat('Bobur oqidi', $bobur->och($yangiPaket));
Natija
hujumchi oqidi:        Hisobga 1000000 som otkaz
Bobur oqidi:           Hisobga 9000000 som otkaz
DH yolg'iz o'zi yetarli emas

Har ikkala tomon ham "shifrlangan kanal" da ekaniga ishondi. Butunlik tekshiruvi ham o'tdi - chunki hujumchi o'z kalitlari bilan to'g'ri shifrladi.

Muammo shifrlashda emas: ochiq kalit kimniki ekani tekshirilmadi.

Yechimlar:

YechimQanday ishlaydi
SertifikatUchinchi ishonchli tomon kalitni tasdiqlaydi (13-bo'lim)
ImzoKalit egasining doimiy kaliti bilan imzolanadi
Oldindan almashishKalit qo'lda kiritiladi (SSH birinchi ulanish)
Barmoq izini solishtirishFoydalanuvchilar boshqa kanal orqali tekshiradi

Oxirgisi messenjer ilovalarida ishlatiladi: "xavfsizlik raqamini solishtiring" degan funksiya aynan shu.

Umumiy qoida: maxfiylik va autentiklik - ikki alohida masala. Birinchisi ikkinchisini bermaydi.

Barmoq izini solishtirish #

PHP
function barmoqIzi(string $ochiqKalit): string
{
    // PEM sarlavhalarini olib tashlaymiz, faqat kalit baytlari qoladi
    $baytlar = base64_decode(preg_replace('/-----[^-]+-----|\s+/', '',
        $ochiqKalit));

    $xesh = hash('sha256', $baytlar, true);

    // odam o'qiy oladigan shakl
    return implode(' ', str_split(strtoupper(
        substr(bin2hex($xesh), 0, 24)), 4));
}

korsat('Alisa', barmoqIzi(ALISA_OCHIQ));
korsat('Bobur', barmoqIzi(BOBUR_OCHIQ));
Natija
Alisa:                 B5AB 05BB 2D3E 0F76 96D4 571C
Bobur:                 1056 B007 A3D2 42BE B8BD F57A
Bu raqamlarni qanday solishtirish kerak

Barmoq izi - ochiq kalitning qisqa xeshi. Uni telefonda o'qib berish yoki uchrashganda tekshirish mumkin.

Muhim shart: solishtirish boshqa kanal orqali bo'lishi kerak.

To'g'riNoto'g'ri
Telefonda ovoz bilan o'qishO'sha chatda yuborish
Yuzma-yuz QR kod skanerlashElektron pochta orqali
Tashkilotning rasmiy saytiHujumchi nazoratidagi kanal

Agar hujumchi kanalni nazorat qilayotgan bo'lsa, u barmoq izini ham almashtiradi. Shuning uchun mustaqil kanal shart.

SSH da bu shunday ko'rinadi:

KOD
The authenticity of host 'server' can't be established.
ED25519 key fingerprint is SHA256:xxxxx.
Are you sure you want to continue connecting?

Ko'pchilik bu savolga o'ylamasdan yes deb javob beradi - va aynan shu payt o'rtadagi odam hujumiga ochiq qoladi. Birinchi ulanishda barmoq izini haqiqatan tekshirish kerak.

Algoritmlarni tanlash #

VazifaTavsiyaIzoh
Kalit almashinuviX25519Tez, xatoga chidamli
MuqobilECDH P-256Keng qo'llab-quvvatlanadi
Eski tizimDH 2048+Sekin, katta kalitlar
Doimiy kalitIshlatmangOldinga maxfiylik yo'qoladi
PHP
// PHP da mavjud egri chiziqlar
$egrilar = openssl_get_curve_names();

echo 'jami egri chiziqlar: ', count($egrilar), PHP_EOL;

foreach (['prime256v1', 'secp384r1', 'secp521r1'] as $e) {
    printf('%-12s %s%s', $e,
        in_array($e, $egrilar, true) ? 'mavjud' : 'yoq', PHP_EOL);
}
Natija
jami egri chiziqlar: 82
prime256v1   mavjud
secp384r1    mavjud
secp521r1    mavjud
Amaliy topshiriq
  1. Ikkita EC kalit jufti yarating (openssl ecparam -genkey).
  2. Har ikkala tomondan umumiy sirni hisoblang va tengligini tekshiring.
  3. Kanalda nima uzatilganini va nima uzatilmaganini yozing.
  4. Xom sirni HKDF orqali o'tkazing va ikkita kalitga ajrating.
  5. Nima uchun xom sirni to'g'ridan-to'g'ri ishlatmaslik kerakligini tushuntiring.
  6. Sessiya sinfini yozing va ikki tomon o'rtasida xabar almashing.
  7. Uchinchi kalit jufti bilan o'rtadagi odam hujumini takrorlang.
  8. Ikkala tomon ham hujumni sezmasligini ko'rsating.
  9. Ochiq kalitlarning barmoq izini hisoblang.
  10. Barmoq izini qaysi kanal orqali solishtirish kerakligini yozing.

Xulosa #

  • Diffie-Hellman ikki tomonga ochiq kanal orqali umumiy sirga kelishish imkonini beradi.
  • Umumiy sir hech qachon uzatilmaydi - u har tomonda mustaqil hisoblanadi.
  • Kanalda faqat ochiq kalitlar yuradi.
  • DH natijasi bir tekis taqsimlanmagan - uni HKDF orqali o'tkazish shart.
  • HKDF info yorlig'i bilan har maqsadga alohida kalit ajratiladi.
  • Vaqtinchalik kalitlar oldinga maxfiylikni beradi: eski trafik keyin ham ochilmaydi.
  • Doimiy kalit o'g'irlansa - u bilan shifrlangan barcha eski yozuvlar ochiladi.
  • DH autentifikatsiya bermaydi: hujumchi ochiq kalitlarni almashtira oladi.
  • Natijada ikkita mustaqil shifrlangan sessiya paydo bo'ladi va tomonlar sezmaydi.
  • Yechim - sertifikat, imzo yoki barmoq izini mustaqil kanal orqali solishtirish.

Keyingi bo'limda ochiq kalitni kimga tegishli ekanini tasdiqlaydigan sertifikatlar bilan tanishamiz.

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.