9-bo‘lim
Nonce va IV - bir marta ishlatiladigan qiymatlar
AEAD ning eng nozik sharti va uni buzish nima uchun shifrni butunlay yo'q qiladi.
Ushbu bo‘lim mundarijasi
Nonce - number used once, ya'ni bir marta ishlatiladigan son.
Bu nom shartni to'liq bayon qiladi: bir xil kalit bilan bitta nonce ikki marta ishlatilmasligi kerak.
Bu shartni buzish - AEAD dagi eng halokatli xato.
Nonce nima uchun kerak #
function korsat(string $nom, string $qiymat): void
{
printf("%-14s %s%s", $nom . ':', $qiymat, PHP_EOL);
}
function gcm(string $matn, string $kalit, string $nonce): string
{
$teg = '';
return openssl_encrypt($matn, 'aes-256-gcm', $kalit,
OPENSSL_RAW_DATA, $nonce, $teg);
}
$kalit = hex2bin(str_repeat('5e', 32));
$matn = 'Hisob raqami: 20208000';
// Har xil nonce - har xil shifrmatn
foreach (['01', '02', '03'] as $b) {
$nonce = hex2bin(str_repeat($b, 12));
printf('nonce %s...: %s%s', $b . $b,
bin2hex(gcm($matn, $kalit, $nonce)), PHP_EOL);
}
nonce 0101...: 61ad75932c6f06132b64c1a16e81702b645ac51ae342
nonce 0202...: 5173c851bbce2413a17bd78fe1ced3dbee2a91bcfd2a
nonce 0303...: 9e9f62fc971c7e916f20af125d3a5a88bd456317404a
Bir xil matn, bir xil kalit - lekin uchta butunlay boshqa shifrmatn. Buni nonce ta'minlaydi.
Endi shartni buzamiz #
$kalit = hex2bin(str_repeat('5e', 32));
$nonce = hex2bin(str_repeat('99', 12)); // BIR XIL nonce
$xabar1 = 'Toladi: 1000000 som';
$xabar2 = 'Toladi: 2500000 som';
$c1 = gcm($xabar1, $kalit, $nonce);
$c2 = gcm($xabar2, $kalit, $nonce);
korsat('shifr 1', bin2hex($c1));
korsat('shifr 2', bin2hex($c2));
// Ikki shifrmatnni XOR qilamiz
$xorShifr = $c1 ^ $c2;
$xorMatn = $xabar1 ^ $xabar2;
korsat('c1 XOR c2', bin2hex($xorShifr));
korsat('p1 XOR p2', bin2hex($xorMatn));
var_dump(hash_equals($xorShifr, $xorMatn));
shifr 1: e6fa511aa699b9ce689f16a9c7f6417f2e484f
shifr 2: e6fa511aa699b9ce6b9a16a9c7f6417f2e484f
c1 XOR c2: 00000000000000000305000000000000000000
p1 XOR p2: 00000000000000000305000000000000000000
bool(true)
Bu tasodif emas - bu oqim shifrining matematik xususiyati.
GCM (va CTR, ChaCha20) shunday ishlaydi:
shifrmatn = ochiq_matn XOR oqim(kalit, nonce)
Kalit va nonce bir xil bo'lsa, oqim ham bir xil.
Demak:
c1 XOR c2 = (p1 XOR oqim) XOR (p2 XOR oqim) = p1 XOR p2
oqim qisqaradi. Kalit umuman kerak emas.
Endi hujumchi bitta ochiq matnni bilsa (yoki taxmin qilsa), ikkinchisini darhol oladi:
$kalit = hex2bin(str_repeat('5e', 32));
$nonce = hex2bin(str_repeat('99', 12));
// Hujumchi 1-xabarni biladi (masalan, o'zi yuborgan)
$malum = 'Toladi: 1000000 som';
$sir = 'Toladi: 2500000 som';
$c1 = gcm($malum, $kalit, $nonce);
$c2 = gcm($sir, $kalit, $nonce);
// Hujumchida faqat c1, c2 va $malum bor - KALIT YO'Q
$tiklangan = ($c1 ^ $c2) ^ $malum;
korsat('malum matn', $malum);
korsat('tiklangan', $tiklangan);
korsat('haqiqiy sir', $sir);
var_dump($tiklangan === $sir);
malum matn: Toladi: 1000000 som
tiklangan: Toladi: 2500000 som
haqiqiy sir: Toladi: 2500000 som
bool(true)
Yuqoridagi kodda kalit ishlatilmadi. Faqat ikkita shifrmatn va bitta ma'lum ochiq matn.
Bu eng oson kriptografik hujumlardan biri va u faqat bitta xatodan kelib chiqadi: nonce takrorlangan.
Amaliy oqibatlari:
| Vaziyat | Hujumchi nima biladi |
|---|---|
| Ikki xabar, bittasi ma'lum | Ikkinchisi to'liq ochiladi |
| Ikki xabar, ikkalasi noma'lum | p1 XOR p2 - tuzilma, umumiy qismlar |
| Ko'p xabar, bir xil nonce | Statistik tahlil bilan hammasi ochiladi |
| Shablonli xabarlar | Farq qiladigan qismlar darhol ko'rinadi |
Uchinchi qatorga e'tibor bering: hujumchi hech qanday ochiq matnni bilmasa ham, xabarlarning umumiy qismlarini ko'radi (XOR natijasidagi nol baytlar).
GCM da bundan ham yomoni bor: nonce takrorlanishi autentifikatsiya kalitini oshkor qilishga olib keladi. Shundan keyin hujumchi istalgan xabarni soxtalashtira oladi - shifrni ochmasa ham.
Tasodifiy nonce va tug'ilgan kun chegarasi #
// 12 baytlik (96 bit) nonce - GCM standarti
$bitlar = 96;
echo 'nonce uzunligi: ', $bitlar, ' bit', PHP_EOL;
echo 'variantlar: 2^', $bitlar, PHP_EOL;
// Tug'ilgan kun paradoksi: ~2^(n/2) dan keyin to'qnashuv ehtimoli sezilarli
$xavfsiz = 2 ** ($bitlar / 2);
printf('xavfsiz chegara: 2^%d = %s xabar%s',
$bitlar / 2, number_format($xavfsiz, 0, '.', ' '), PHP_EOL);
// Amaliy tavsiya - undan ancha past
$tavsiya = 2 ** 32;
printf('amaliy tavsiya: 2^32 = %s xabar (bitta kalit uchun)%s',
number_format($tavsiya, 0, '.', ' '), PHP_EOL);
// Soniyasiga 1000 xabar bo'lsa
$kunlar = $tavsiya / 1000 / 86400;
printf('1000 xabar/sek tezlikda: %.0f kun%s', $kunlar, PHP_EOL);
nonce uzunligi: 96 bit
variantlar: 2^96
xavfsiz chegara: 2^48 = 281 474 976 710 656 xabar
amaliy tavsiya: 2^32 = 4 294 967 296 xabar (bitta kalit uchun)
1000 xabar/sek tezlikda: 50 kun
Tasodifiy 12 baytlik nonce deyarli har doim to'g'ri tanlov:
| Xabarlar soni (bitta kalit) | Baho |
|---|---|
| Millionlarcha | Xavfsiz |
| Milliardlarcha (2³²) | Chegaraga yaqin - kalitni almashtiring |
| Undan ko'p | Kalitni majburiy almashtiring |
Amalda bu chegaraga yetish qiyin: soniyasiga 1000 xabar tezlikda ham 130 yildan ko'proq vaqt kerak.
Lekin kalitni baribir vaqti-vaqti bilan almashtiring (18-bo'lim) - bu boshqa sabablarga ko'ra ham foydali.
Hisoblagichli nonce va uning tuzog'i #
// XATO: har jarayon o'z hisoblagichini nolga qo'yadi
final class XatoHisoblagich
{
private int $son = 0;
public function keyingi(): string
{
return str_pad(pack('J', $this->son++), 12, "\x00", STR_PAD_LEFT);
}
}
$server1 = new XatoHisoblagich();
$server2 = new XatoHisoblagich(); // ikkinchi server / qayta ishga tushirish
echo 'server 1: ', bin2hex($server1->keyingi()), PHP_EOL;
echo 'server 1: ', bin2hex($server1->keyingi()), PHP_EOL;
echo 'server 2: ', bin2hex($server2->keyingi()), PHP_EOL;
echo PHP_EOL, 'server 2 ning birinchi nonce i server 1 nikini TAKRORLADI',
PHP_EOL;
server 1: 000000000000000000000000
server 1: 000000000000000000000001
server 2: 000000000000000000000000
server 2 ning birinchi nonce i server 1 nikini TAKRORLADI
Hisoblagichli nonce nazariy jihatdan tasodifiydan yaxshiroq (to'qnashuv umuman bo'lmaydi), lekin amalda xavfliroq:
| Vaziyat | Nima bo'ladi |
|---|---|
| Server qayta ishga tushdi | Hisoblagich noldan boshlanadi |
| Ikkinchi nusxa (replika) ishga tushdi | Ikkalasi bir xil ketma-ketlik |
| Bazadan tiklandi (zaxiradan) | Eski hisoblagich qaytdi |
| Konteyner ko'chirildi | Holat yo'qoldi |
| Bir necha ish jarayoni (worker) | Har biri o'z hisoblagichi |
Hisoblagichni ishonchli saqlash uchun har shifrlashda bazaga yozish kerak - bu sekin va baribir poyga holatiga ochiq.
Amaliy tavsiya:
| Muhit | Tanlov |
|---|---|
| Veb ilova, ko'p jarayon | Tasodifiy nonce |
| Bitta jarayon, ketma-ket oqim (masalan fayl) | Hisoblagich |
| Taqsimlangan tizim | Tasodifiy + kalitni tez-tez almashtirish |
| Har xabarga alohida kalit | Nonce nolga teng bo'lishi ham mumkin |
Oxirgi qator qiziq: agar kalit har xabar uchun yangi bo'lsa, nonce takrorlanishi muammo emas.
To'g'ri yechim: har konvertga tasodifiy nonce #
final class XavfsizShifr
{
private const NONCE = 12;
public function __construct(private readonly string $kalit) {}
public function shifrla(string $matn): string
{
// Nonce har chaqiruvda YANGI va TASODIFIY
$nonce = random_bytes(self::NONCE);
$teg = '';
$shifr = openssl_encrypt($matn, 'aes-256-gcm', $this->kalit,
OPENSSL_RAW_DATA, $nonce, $teg);
// Nonce shifrmatn bilan birga saqlanadi - u maxfiy emas
return $nonce . $teg . $shifr;
}
public function och(string $paket): string
{
if (strlen($paket) < self::NONCE + 16) {
throw new RuntimeException('paket juda qisqa');
}
$nonce = substr($paket, 0, self::NONCE);
$teg = substr($paket, self::NONCE, 16);
$shifr = substr($paket, self::NONCE + 16);
$matn = openssl_decrypt($shifr, 'aes-256-gcm', $this->kalit,
OPENSSL_RAW_DATA, $nonce, $teg);
if ($matn === false) {
throw new RuntimeException('butunlik tekshiruvi otmadi');
}
return $matn;
}
}
$shifr = new XavfsizShifr(hex2bin(str_repeat('5e', 32)));
$matn = 'Toladi: 1000000 som';
// Bir xil matnni 5 marta shifrlaymiz
$paketlar = [];
for ($i = 0; $i < 5; $i++) {
$paketlar[] = $shifr->shifrla($matn);
}
// Hammasi har xilmi?
echo 'noyob paketlar: ', count(array_unique($paketlar)), ' / 5', PHP_EOL;
// Nonce lar ham har xilmi?
$noncelar = array_map(static fn(string $p): string => substr($p, 0, 12),
$paketlar);
echo 'noyob noncelar: ', count(array_unique($noncelar)), ' / 5', PHP_EOL;
// Hammasi to'g'ri ochiladimi?
$ochildi = array_map(static fn(string $p): string => $shifr->och($p),
$paketlar);
echo 'hammasi ochildi: ',
count(array_unique($ochildi)) === 1 && $ochildi[0] === $matn
? 'ha' : 'yoq', PHP_EOL;
noyob paketlar: 5 / 5
noyob noncelar: 5 / 5
hammasi ochildi: ha
CBC dagi IV boshqacha shart qo'yadi #
$kalit = hex2bin(str_repeat('11', 32));
// CBC da IV faqat noyob emas, TAXMIN QILIB BO'LMAYDIGAN bo'lishi kerak
$matn = 'Hisob: 20208000';
// Ketma-ket IV - noyob, lekin taxmin qilinadi
foreach ([1, 2, 3] as $n) {
$iv = str_pad(pack('J', $n), 16, "\x00", STR_PAD_LEFT);
$s = openssl_encrypt($matn, 'aes-256-cbc', $kalit, OPENSSL_RAW_DATA, $iv);
printf('IV=%d -> %s%s', $n, substr(bin2hex($s), 0, 32), PHP_EOL);
}
echo PHP_EOL, 'Bu IV lar NOYOB, lekin keyingisini taxmin qilish oson', PHP_EOL;
IV=1 -> ca04539173ce6f2c8258ccd11b4c4aac
IV=2 -> 0f69ba9b038f5515f2d56cf23dd6baae
IV=3 -> 8a56703bf033808339680adf7bcdb34e
Bu IV lar NOYOB, lekin keyingisini taxmin qilish oson
| Rejim | IV/nonce sharti |
|---|---|
| CBC | Noyob va taxmin qilib bo'lmaydigan |
| CTR, GCM | Faqat noyob (taxmin qilinishi mumkin) |
CBC da keyingi IV ni bilgan hujumchi tanlangan ochiq matn hujumini o'tkaza oladi: u shifrlanadigan matnni shunday tanlaydiki, natijaga qarab taxminini tekshira oladi.
TLS 1.0 dagi mashhur BEAST hujumi aynan shunga asoslangan edi (u yerda keyingi IV oldingi shifrblok edi).
Amaliy qoida ikkalasi uchun ham bir xil:
random_bytes() ishlating. U ikkala shartni ham
bajaradi.
Nonce va IV: xulosa jadvali #
$jadval = [
['Uzunlik', '12 bayt', '16 bayt'],
['Noyob bolishi', 'MAJBURIY', 'MAJBURIY'],
['Taxminlanmasligi', 'shart emas', 'MAJBURIY'],
['Maxfiymi', 'yoq', 'yoq'],
['Qayerda saqlanadi', 'shifrmatn bilan', 'shifrmatn bilan'],
['Takrorlansa', 'HALOKAT', 'jiddiy zaiflik'],
];
printf("%-20s %-16s %s%s", '', 'GCM nonce', 'CBC IV', PHP_EOL);
echo str_repeat('-', 56), PHP_EOL;
foreach ($jadval as $q) {
printf("%-20s %-16s %s%s", ...[...$q, PHP_EOL]);
}
GCM nonce CBC IV
--------------------------------------------------------
Uzunlik 12 bayt 16 bayt
Noyob bolishi MAJBURIY MAJBURIY
Taxminlanmasligi shart emas MAJBURIY
Maxfiymi yoq yoq
Qayerda saqlanadi shifrmatn bilan shifrmatn bilan
Takrorlansa HALOKAT jiddiy zaiflik
- Bir xil matnni uch xil nonce bilan shifrlang va natijalarni solishtiring.
- Xuddi shu matnlarni bir xil nonce bilan shifrlang.
c1 XOR c2vap1 XOR p2teng ekanini tekshiring.- Ma'lum ochiq matndan foydalanib, ikkinchisini kalitsiz tiklang.
- Nima uchun bu hujum ishlashini o'z so'zlaringiz bilan tushuntiring.
- 96 bitlik nonce uchun tug'ilgan kun chegarasini hisoblang.
- Ikkita mustaqil hisoblagich yarating va ular takrorlanishini ko'rsating.
- Har shifrlashda
random_bytesbilan nonce yaratuvchi sinf yozing. - Bir xil matnni besh marta shifrlang - paketlar noyobmi?
- CBC va GCM uchun IV/nonce shartlari farqini yozing.
Xulosa #
- Nonce - bir xil kalit bilan ikki marta ishlatilmasligi kerak bo'lgan qiymat.
- U maxfiy emas va shifrmatn bilan birga saqlanadi.
- Nonce takrorlansa, oqim shifri bir xil oqim ishlab chiqaradi.
- Natijada
c1 XOR c2 = p1 XOR p2- kalit umuman kerak emas. - Bitta ochiq matn ma'lum bo'lsa, ikkinchisi to'liq ochiladi.
- GCM da nonce takrorlanishi autentifikatsiya kalitini ham oshkor qiladi.
- Tasodifiy 12 baytlik nonce - eng oddiy va xavfsiz tanlov.
- Bitta kalit uchun amaliy chegara - taxminan 2³² xabar.
- Hisoblagichli nonce qayta ishga tushish, replika va zaxiradan tiklashda takrorlanadi.
- CBC da IV noyob va taxmin qilib bo'lmaydigan bo'lishi kerak -
random_bytesikkalasini ham beradi.
Keyingi bo'limda ochiq kalitli kriptografiya 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.