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.

🕑 14 daqiqa o‘qish 📄 944 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Nonce nima uchun kerak
  2. Endi shartni buzamiz
  3. Tasodifiy nonce va tug'ilgan kun chegarasi
  4. Hisoblagichli nonce va uning tuzog'i
  5. To'g'ri yechim: har konvertga tasodifiy nonce
  6. CBC dagi IV boshqacha shart qo'yadi
  7. Nonce va IV: xulosa jadvali
  8. Xulosa

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 #

PHP
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);
}
PHP
$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);
}
Natija
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 #

PHP
$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));
Natija
shifr 1:       e6fa511aa699b9ce689f16a9c7f6417f2e484f
shifr 2:       e6fa511aa699b9ce6b9a16a9c7f6417f2e484f
c1 XOR c2:     00000000000000000305000000000000000000
p1 XOR p2:     00000000000000000305000000000000000000
bool(true)
Shifrmatnlarning XOR i ochiq matnlarning XOR iga teng

Bu tasodif emas - bu oqim shifrining matematik xususiyati.

GCM (va CTR, ChaCha20) shunday ishlaydi:

KOD
shifrmatn = ochiq_matn XOR oqim(kalit, nonce)

Kalit va nonce bir xil bo'lsa, oqim ham bir xil. Demak:

KOD
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:

PHP
$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);
Natija
malum matn:    Toladi: 1000000 som
tiklangan:     Toladi: 2500000 som
haqiqiy sir:   Toladi: 2500000 som
bool(true)
Kalitsiz sir ochildi

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:

VaziyatHujumchi nima biladi
Ikki xabar, bittasi ma'lumIkkinchisi to'liq ochiladi
Ikki xabar, ikkalasi noma'lump1 XOR p2 - tuzilma, umumiy qismlar
Ko'p xabar, bir xil nonceStatistik tahlil bilan hammasi ochiladi
Shablonli xabarlarFarq 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.

Nonce takrorlanganda nima bo'ladi To'g'ri: har xabarga o'z nonce i xabar 1 oqim(k, nonce-1) xabar 2 oqim(k, nonce-2) Oqimlar HAR XIL c1 XOR c2 hech narsa bermaydi Xato: bir xil nonce xabar 1 oqim(k, nonce) xabar 2 oqim(k, nonce) Oqimlar BIR XIL c1 XOR c2 = p1 XOR p2 Oqibat 1. Bitta ochiq matn ma'lum bo'lsa - ikkinchisi to'liq ochiladi (kalitsiz). 2. GCM da bundan tashqari autentifikatsiya kaliti ham oshkor bo'ladi - soxta xabar yasash mumkin. Nonce ni qanday tanlash Tasodifiy (12 bayt): oddiy va xavfsiz - bitta kalit uchun ~2³² xabargacha Hisoblagich: nazariy jihatdan eng yaxshi, lekin holatni saqlash kerak (baza, fayl) Hech qachon: qat'iy qiymat, vaqt belgisi yolg'iz, tasodifiy bo'lmagan hisoblagich
Bitta takrorlangan nonce butun shifrlashni bekor qiladi

Tasodifiy nonce va tug'ilgan kun chegarasi #

PHP
// 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);
Natija
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
Amaliy qoida

Tasodifiy 12 baytlik nonce deyarli har doim to'g'ri tanlov:

Xabarlar soni (bitta kalit)Baho
MillionlarchaXavfsiz
Milliardlarcha (2³²)Chegaraga yaqin - kalitni almashtiring
Undan ko'pKalitni 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 #

PHP
// 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;
Natija
server 1: 000000000000000000000000
server 1: 000000000000000000000001
server 2: 000000000000000000000000

server 2 ning birinchi nonce i server 1 nikini TAKRORLADI
Hisoblagich - eng ko'p xato qilinadigan joy

Hisoblagichli nonce nazariy jihatdan tasodifiydan yaxshiroq (to'qnashuv umuman bo'lmaydi), lekin amalda xavfliroq:

VaziyatNima bo'ladi
Server qayta ishga tushdiHisoblagich noldan boshlanadi
Ikkinchi nusxa (replika) ishga tushdiIkkalasi bir xil ketma-ketlik
Bazadan tiklandi (zaxiradan)Eski hisoblagich qaytdi
Konteyner ko'chirildiHolat 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:

MuhitTanlov
Veb ilova, ko'p jarayonTasodifiy nonce
Bitta jarayon, ketma-ket oqim (masalan fayl)Hisoblagich
Taqsimlangan tizimTasodifiy + kalitni tez-tez almashtirish
Har xabarga alohida kalitNonce 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 #

PHP
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;
    }
}
PHP
$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;
Natija
noyob paketlar: 5 / 5
noyob noncelar: 5 / 5
hammasi ochildi: ha

CBC dagi IV boshqacha shart qo'yadi #

PHP
$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;
Natija
IV=1 -> ca04539173ce6f2c8258ccd11b4c4aac
IV=2 -> 0f69ba9b038f5515f2d56cf23dd6baae
IV=3 -> 8a56703bf033808339680adf7bcdb34e

Bu IV lar NOYOB, lekin keyingisini taxmin qilish oson
CBC: noyoblik yetarli emas
RejimIV/nonce sharti
CBCNoyob va taxmin qilib bo'lmaydigan
CTR, GCMFaqat 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 #

PHP
$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]);
}
Natija
                     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
Amaliy topshiriq
  1. Bir xil matnni uch xil nonce bilan shifrlang va natijalarni solishtiring.
  2. Xuddi shu matnlarni bir xil nonce bilan shifrlang.
  3. c1 XOR c2 va p1 XOR p2 teng ekanini tekshiring.
  4. Ma'lum ochiq matndan foydalanib, ikkinchisini kalitsiz tiklang.
  5. Nima uchun bu hujum ishlashini o'z so'zlaringiz bilan tushuntiring.
  6. 96 bitlik nonce uchun tug'ilgan kun chegarasini hisoblang.
  7. Ikkita mustaqil hisoblagich yarating va ular takrorlanishini ko'rsating.
  8. Har shifrlashda random_bytes bilan nonce yaratuvchi sinf yozing.
  9. Bir xil matnni besh marta shifrlang - paketlar noyobmi?
  10. 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_bytes ikkalasini ham beradi.

Keyingi bo'limda ochiq kalitli kriptografiya 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.