12-bo‘lim
Kalit almashinuvi
Ochiq kanal orqali umumiy kalitga kelishish - ECDH, oldinga maxfiylik va o'rtadagi odam muammosi.
Ushbu bo‘lim mundarijasi
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 #
// 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);
}
// 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');
Alisa hisobladi: 8bd8642b109784c6c208b8710d14cdf4ab4c32960b1191fb65c28571a075b0af
Bobur hisobladi: 8bd8642b109784c6c208b8710d14cdf4ab4c32960b1191fb65c28571a075b0af
bir xilmi: HA
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:
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 #
$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));
xom sir: 8bd8642b109784c6c208b8710d14cdf4ab4c32960b1191fb65c28571a075b0af
shifr kaliti: 89f7172178009427a1cf742baed7efee1f8859b6dbd0f511632fb35c18a2de1c
mac kaliti: 340125eac02b0ae25cdf9472f1b44ac3d823083bebf39b207461916d0f7c6c7b
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 ishlatish | HKDF orqali o'tkazish |
|---|---|
| Bitlar bir tekis emas | Bir tekis taqsimlangan |
| Bitta kalit - hamma maqsad uchun | Har maqsadga alohida kalit |
| Kontekst bog'lanmagan | info 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) #
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;
}
}
$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));
Alisa kaliti: 7750ebfdec877660
Bobur kaliti: 7750ebfdec877660
Bobur oqidi: Uchrashuv ertaga soat 10:00
Alisa oqidi: Kelishdik
Yuqoridagi misolda kalitlar doimiy (qat'iy yozilgan). Real tizimda esa ular har sessiyaga yangi bo'ladi - bu vaqtinchalik (ephemeral) kalitlar deyiladi.
Farqi katta:
| Doimiy kalitlar | Vaqtinchalik kalitlar | |
|---|---|---|
| Kalit umri | Yillar | Bir sessiya |
| Maxfiy kalit o'g'irlansa | Barcha eski yozuvlar ochiladi | Faqat joriy sessiya |
| Yozib olingan trafik | Keyinchalik ochiladi | Hech 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.
O'rtadagi odam hujumi #
// 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());
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:
$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));
hujumchi oqidi: Hisobga 1000000 som otkaz
Bobur oqidi: Hisobga 9000000 som otkaz
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:
| Yechim | Qanday ishlaydi |
|---|---|
| Sertifikat | Uchinchi ishonchli tomon kalitni tasdiqlaydi (13-bo'lim) |
| Imzo | Kalit egasining doimiy kaliti bilan imzolanadi |
| Oldindan almashish | Kalit qo'lda kiritiladi (SSH birinchi ulanish) |
| Barmoq izini solishtirish | Foydalanuvchilar 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 #
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));
Alisa: B5AB 05BB 2D3E 0F76 96D4 571C
Bobur: 1056 B007 A3D2 42BE B8BD F57A
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'ri | Noto'g'ri |
|---|---|
| Telefonda ovoz bilan o'qish | O'sha chatda yuborish |
| Yuzma-yuz QR kod skanerlash | Elektron pochta orqali |
| Tashkilotning rasmiy sayti | Hujumchi 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:
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 #
| Vazifa | Tavsiya | Izoh |
|---|---|---|
| Kalit almashinuvi | X25519 | Tez, xatoga chidamli |
| Muqobil | ECDH P-256 | Keng qo'llab-quvvatlanadi |
| Eski tizim | DH 2048+ | Sekin, katta kalitlar |
| Doimiy kalit | Ishlatmang | Oldinga maxfiylik yo'qoladi |
// 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);
}
jami egri chiziqlar: 82
prime256v1 mavjud
secp384r1 mavjud
secp521r1 mavjud
- Ikkita EC kalit jufti yarating (
openssl ecparam -genkey). - Har ikkala tomondan umumiy sirni hisoblang va tengligini tekshiring.
- Kanalda nima uzatilganini va nima uzatilmaganini yozing.
- Xom sirni HKDF orqali o'tkazing va ikkita kalitga ajrating.
- Nima uchun xom sirni to'g'ridan-to'g'ri ishlatmaslik kerakligini tushuntiring.
- Sessiya sinfini yozing va ikki tomon o'rtasida xabar almashing.
- Uchinchi kalit jufti bilan o'rtadagi odam hujumini takrorlang.
- Ikkala tomon ham hujumni sezmasligini ko'rsating.
- Ochiq kalitlarning barmoq izini hisoblang.
- 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
infoyorlig'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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.