8-bo‘lim

Autentifikatsiyalangan shifrlash (AEAD)

Maxfiylik va butunlikni bitta amalda olish - AES-GCM, teg va bog'langan ma'lumot.

🕑 13 daqiqa o‘qish 📄 979 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. AES-GCM
  2. Endi eng muhimi: o'zgartirish aniqlanadi
  3. Bog'langan ma'lumot (AAD)
  4. Saqlash formatini loyihalash
  5. ChaCha20-Poly1305
  6. Teg uzunligi
  7. Xulosa

7-bo'limda ko'rdik: CBC shifrmatni o'zgartirilsa, ochish xato bermaydi. Bu jiddiy muammo.

AEAD (Authenticated Encryption with Associated Data) shu muammoni yechadi: u bir amalda shifrlaydi va imzolaydi.

AES-GCM #

PHP
function korsat(string $nom, string $qiymat): void
{
    printf("%-12s %s%s", $nom . ':', $qiymat, PHP_EOL);
}

function konvert(string $matn, string $kalit, string $nonce,
                 string $aad = ''): array
{
    $teg = '';
    $shifr = openssl_encrypt($matn, 'aes-256-gcm', $kalit,
        OPENSSL_RAW_DATA, $nonce, $teg, $aad);

    return ['shifr' => $shifr, 'teg' => $teg];
}
PHP
$kalit = hex2bin(str_repeat('a7', 32));
$nonce = hex2bin(str_repeat('01', 12));

$natija = konvert('Karta: 8600123456781234', $kalit, $nonce);

korsat('shifrmatn', bin2hex($natija['shifr']));
korsat('teg', bin2hex($natija['teg']));
korsat('shifr uz.', strlen($natija['shifr']) . ' bayt');
korsat('teg uz.', strlen($natija['teg']) . ' bayt');

// Ochish
$ochiq = openssl_decrypt($natija['shifr'], 'aes-256-gcm', $kalit,
    OPENSSL_RAW_DATA, $nonce, $natija['teg']);
korsat('ochilgan', $ochiq);
Natija
shifrmatn:   b59a60f008bef894bb04cf0b6b5adccb38cf7ad71e447d
teg:         7adb9bb1e09806cf33de3ce063a8f40d
shifr uz.:   23 bayt
teg uz.:     16 bayt
ochilgan:    Karta: 8600123456781234
Uch narsa qaytadi
ElementO'lchamiMaxfiymi
ShifrmatnOchiq matn bilan bir xilHa
Teg (tag)16 baytYo'q, lekin o'zgartirib bo'lmaydi
Nonce12 baytYo'q

Teg - bu autentifikatsiya kodi. U shifrmatn (va bog'langan ma'lumot) ustidan hisoblanadi.

Diqqat: shifrmatn uzunligi ochiq matn bilan bir xil - to'ldirish yo'q. GCM ichida oqim shifri ishlaydi.

Uchala qismni ham birga saqlash kerak, aks holda ochib bo'lmaydi.

Endi eng muhimi: o'zgartirish aniqlanadi #

PHP
$kalit = hex2bin(str_repeat('a7', 32));
$nonce = hex2bin(str_repeat('01', 12));

$n = konvert('rol=user;id=1234', $kalit, $nonce);

// 1. Toza holat
$ochiq = openssl_decrypt($n['shifr'], 'aes-256-gcm', $kalit,
    OPENSSL_RAW_DATA, $nonce, $n['teg']);
korsat('ozgarishsiz', var_export($ochiq, true));

// 2. Shifrmatnning bir bayti o'zgartirildi
$buzuq = $n['shifr'];
$buzuq[5] = chr(ord($buzuq[5]) ^ 0x04);
korsat('shifr buzildi', var_export(openssl_decrypt($buzuq, 'aes-256-gcm',
    $kalit, OPENSSL_RAW_DATA, $nonce, $n['teg']), true));

// 3. Teg o'zgartirildi
$buzuqTeg = $n['teg'];
$buzuqTeg[0] = chr(ord($buzuqTeg[0]) ^ 0x01);
korsat('teg buzildi', var_export(openssl_decrypt($n['shifr'], 'aes-256-gcm',
    $kalit, OPENSSL_RAW_DATA, $nonce, $buzuqTeg), true));

// 4. Nonce o'zgartirildi
$buzuqNonce = hex2bin(str_repeat('02', 12));
korsat('nonce buzildi', var_export(openssl_decrypt($n['shifr'], 'aes-256-gcm',
    $kalit, OPENSSL_RAW_DATA, $buzuqNonce, $n['teg']), true));
Natija
ozgarishsiz: 'rol=user;id=1234'
shifr buzildi: false
teg buzildi: false
nonce buzildi: false
CBC bilan taqqoslang

7-bo'limdagi CBC misolida shifrmatnni o'zgartirganimizda ochish muvaffaqiyatli bo'lgan va biz rol=uwer ni olgandik.

Bu yerda esa har uchala buzilish ham false qaytardi.

BuzilishCBCAES-GCM
Shifrmatn o'zgardiOchiladi, matn buziladifalse
IV/nonce o'zgardiOchiladi, 1-blok buziladifalse
Teg o'zgardiTeg yo'qfalse

Bu "hammasi yoki hech nima" tamoyili: ochish yo to'liq to'g'ri natija beradi, yo umuman hech narsa.

Kodda buni tekshirish majburiy:

KOD
$ochiq = openssl_decrypt(...);
if ($ochiq === false) {
    throw new RuntimeException('malumot buzilgan yoki kalit notogri');
}

Bog'langan ma'lumot (AAD) #

Ba'zan ma'lumotni shifrlash kerak emas, lekin uni o'zgartirishga ham yo'l qo'ymaslik kerak.

PHP
$kalit = hex2bin(str_repeat('a7', 32));
$nonce = hex2bin(str_repeat('01', 12));

// Yozuv identifikatori ochiq qoladi, lekin AAD ga bog'lanadi
$aad = 'yozuv_id=42;jadval=kartalar';

$n = konvert('8600123456781234', $kalit, $nonce, $aad);
korsat('shifrmatn', bin2hex($n['shifr']));

// To'g'ri AAD bilan ochiladi
$ochiq = openssl_decrypt($n['shifr'], 'aes-256-gcm', $kalit,
    OPENSSL_RAW_DATA, $nonce, $n['teg'], $aad);
korsat('togri aad', var_export($ochiq, true));

// AAD o'zgartirilsa - ochilmaydi
$soxtaAad = 'yozuv_id=99;jadval=kartalar';
korsat('soxta aad', var_export(openssl_decrypt($n['shifr'], 'aes-256-gcm',
    $kalit, OPENSSL_RAW_DATA, $nonce, $n['teg'], $soxtaAad), true));

// AAD umuman berilmasa ham - ochilmaydi
korsat('aad yoq', var_export(openssl_decrypt($n['shifr'], 'aes-256-gcm',
    $kalit, OPENSSL_RAW_DATA, $nonce, $n['teg']), true));
Natija
shifrmatn:   c6cd22b458b6eb98b802c802685bdbca
togri aad:   '8600123456781234'
soxta aad:   false
aad yoq:     false
AAD qachon kerak

AAD shifrlanmaydi, lekin imzolanadi. U kontekstni shifrmatnga bog'laydi.

Klassik muammo: hujumchi ikkita yozuvning shifrlangan maydonlarini almashtirib qo'yadi.

YozuvKarta (shifrlangan)
Husanboy (id=42)9a3f...
Malika (id=99)7c1b...

AAD siz hujumchi 9a3f... ni 99-yozuvga ko'chirishi mumkin - va tizim uni bemalol ochadi.

AAD bilan esa 42-yozuvning shifrmatni faqat yozuv_id=42 konteksti bilan ochiladi.

AAD ga nima qo'yiladi:

Ma'lumotNima uchun
Yozuv identifikatoriKo'chirishning oldini oladi
Jadval/kolonka nomiBoshqa maydonga ko'chirishni to'xtatadi
Foydalanuvchi idBoshqa hisobga ko'chirishni to'xtatadi
Format versiyasiEski formatga tushirishni to'xtatadi
Vaqt oralig'iEski yozuvni qayta ishlatishni cheklaydi
AEAD - bitta amalda maxfiylik va butunlik Kirish ochiq matn AAD (ochiq) kalit + nonce AES-GCM shifrlash + autentifikatsiya Chiqish shifrmatn teg (16 bayt) uzunlik = ochiq matn Ochishda tekshiriladi ✗ shifrmatn o'zgargan → false ✗ teg o'zgargan → false ✗ nonce o'zgargan → false ✗ AAD mos kelmagan → false hammasi yoki hech nima Saqlash formati - hammasi birga versiya nonce (12 B) teg (16 B) shifrmatn (o'zgaruvchan uzunlik) Nima uchun "encrypt-then-MAC" AEAD ichida MAC shifrmatn ustidan hisoblanadi - ochiq matn ustidan emas. Shuning uchun buzilgan ma'lumot ochilmasdan turib rad etiladi - shifrlash kodi unga umuman tegmaydi.
AEAD - shifrlash va imzolashni birlashtirgan, xato qilish qiyin bo'lgan interfeys

Saqlash formatini loyihalash #

PHP
final class Konvert
{
    private const VERSIYA = "\x01";
    private const NONCE = 12;
    private const TEG = 16;

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

    public function yop(string $matn, string $aad = ''): string
    {
        $nonce = random_bytes(self::NONCE);
        $teg = '';

        $shifr = openssl_encrypt($matn, 'aes-256-gcm', $this->kalit,
            OPENSSL_RAW_DATA, $nonce, $teg, $aad);

        if ($shifr === false) {
            throw new RuntimeException('shifrlash muvaffaqiyatsiz');
        }

        return self::VERSIYA . $nonce . $teg . $shifr;
    }

    public function och(string $konvert, string $aad = ''): string
    {
        $eng = 1 + self::NONCE + self::TEG;
        if (strlen($konvert) < $eng) {
            throw new RuntimeException('konvert juda qisqa');
        }

        if ($konvert[0] !== self::VERSIYA) {
            throw new RuntimeException(
                'nomalum versiya: ' . bin2hex($konvert[0]));
        }

        $nonce = substr($konvert, 1, self::NONCE);
        $teg = substr($konvert, 1 + self::NONCE, self::TEG);
        $shifr = substr($konvert, $eng);

        $matn = openssl_decrypt($shifr, 'aes-256-gcm', $this->kalit,
            OPENSSL_RAW_DATA, $nonce, $teg, $aad);

        if ($matn === false) {
            throw new RuntimeException('butunlik tekshiruvi otmadi');
        }

        return $matn;
    }
}
PHP
$konvert = new Konvert(hex2bin(str_repeat('a7', 32)));

$yopiq = $konvert->yop('Maxfiy hisobot', 'yozuv=7');

echo 'konvert uzunligi: ', strlen($yopiq), ' bayt', PHP_EOL;
echo 'versiya bayti:    ', bin2hex($yopiq[0]), PHP_EOL;
echo 'ochilgan:         ', $konvert->och($yopiq, 'yozuv=7'), PHP_EOL;

// Har chaqiruvda yangi nonce - shifrmatn ham boshqacha
$ikkinchi = $konvert->yop('Maxfiy hisobot', 'yozuv=7');
echo 'ikki konvert bir xilmi: ',
    $yopiq === $ikkinchi ? 'ha' : 'YOQ', PHP_EOL;
echo 'ikkalasi ham ochiladi:  ',
    $konvert->och($ikkinchi, 'yozuv=7'), PHP_EOL;
Natija
konvert uzunligi: 43 bayt
versiya bayti:    01
ochilgan:         Maxfiy hisobot
ikki konvert bir xilmi: YOQ
ikkalasi ham ochiladi:  Maxfiy hisobot

Xato holatlari aniq xabar beradi:

PHP
$konvert = new Konvert(hex2bin(str_repeat('a7', 32)));
$yopiq = $konvert->yop('Maxfiy hisobot', 'yozuv=7');

$sinovlar = [
    'notogri aad' => static fn() => $konvert->och($yopiq, 'yozuv=8'),
    'buzilgan bayt' => static function () use ($konvert, $yopiq) {
        $b = $yopiq;
        $b[40] = chr(ord($b[40]) ^ 0xff);
        return $konvert->och($b, 'yozuv=7');
    },
    'qisqa konvert' => static fn() => $konvert->och('abc'),
    'notogri versiya' => static fn() => $konvert->och("\x09" . substr($yopiq, 1)),
    'boshqa kalit' => static function () use ($yopiq) {
        return (new Konvert(hex2bin(str_repeat('b8', 32))))->och($yopiq, 'yozuv=7');
    },
];

foreach ($sinovlar as $nom => $sinov) {
    try {
        $sinov();
        printf('%-16s OTDI - KUTILMAGAN%s', $nom, PHP_EOL);
    } catch (RuntimeException $x) {
        printf('%-16s RAD: %s%s', $nom, $x->getMessage(), PHP_EOL);
    }
}
Natija
notogri aad      RAD: butunlik tekshiruvi otmadi
buzilgan bayt    RAD: butunlik tekshiruvi otmadi
qisqa konvert    RAD: konvert juda qisqa
notogri versiya  RAD: nomalum versiya: 09
boshqa kalit     RAD: butunlik tekshiruvi otmadi
Versiya bayti nima uchun kerak

Formatning birinchi bayti - versiya. U hozir hech narsa qilmaydi, lekin kelajakda hal qiluvchi bo'ladi.

Algoritm yoki format o'zgarganda:

VersiyasizVersiya bilan
Eski ma'lumotni ochib bo'lmaydiEski versiyani tanib, eski usul bilan ochiladi
Butun bazani bir vaqtda ko'chirish kerakAsta-sekin ko'chirish mumkin
Ikki format aralashsa - chalkashlikFormat o'zi haqida aytadi

Bir baytlik xarajat, lekin u tizimni kelajakka tayyorlaydi.

Diqqat: versiya baytini AAD ga ham qo'shish yanada yaxshiroq - shunda uni o'zgartirib bo'lmaydi (downgrade hujumidan himoya).

ChaCha20-Poly1305 #

PHP
$kalit = hex2bin(str_repeat('c3', 32));
$nonce = hex2bin(str_repeat('04', 12));
$teg = '';

$shifr = openssl_encrypt('Maxfiy xabar', 'chacha20-poly1305', $kalit,
    OPENSSL_RAW_DATA, $nonce, $teg);

korsat('shifrmatn', bin2hex($shifr));
korsat('teg', bin2hex($teg));

$ochiq = openssl_decrypt($shifr, 'chacha20-poly1305', $kalit,
    OPENSSL_RAW_DATA, $nonce, $teg);
korsat('ochilgan', $ochiq);

// Buzilgan holat
$buzuq = $shifr;
$buzuq[0] = chr(ord($buzuq[0]) ^ 0x01);
korsat('buzilgan', var_export(openssl_decrypt($buzuq, 'chacha20-poly1305',
    $kalit, OPENSSL_RAW_DATA, $nonce, $teg), true));
Natija
shifrmatn:   035744fd9e012ca0006e68eb
teg:         c0cacc1bc3ecb01301bd51e9f3ce750a
ochilgan:    Maxfiy xabar
buzilgan:    false
AES-GCM yoki ChaCha20-Poly1305
MezonAES-GCMChaCha20-Poly1305
Uskuna tezlatgichi bo'lsaTezroqSekinroq
Uskuna tezlatgichi yo'qSekinroqTezroq
Doimiy vaqtda ishlashUskunaga bog'liqHar doim
Nonce takrorlanishiga chidamlilikYomonYomon (XChaCha yaxshiroq)
Nonce uzunligi12 bayt12 bayt

Amaliy tavsiya:

  • Server kodida (zamonaviy protsessor) - AES-GCM
  • Mobil, IoT, eski uskunada - ChaCha20-Poly1305
  • Ikkalasi ham ishonchli; noto'g'ri tanlov yo'q

Muhimi - ikkalasi ham nonce takrorlanishiga chidamsiz. Bu keyingi bo'limning mavzusi.

Teg uzunligi #

PHP
$kalit = hex2bin(str_repeat('a7', 32));
$nonce = hex2bin(str_repeat('01', 12));

foreach ([16, 12, 8, 4] as $uz) {
    $teg = '';
    openssl_encrypt('test', 'aes-256-gcm', $kalit,
        OPENSSL_RAW_DATA, $nonce, $teg, '', $uz);
    printf('teg %2d bayt (%3d bit): %s%s', $uz, $uz * 8,
        bin2hex($teg), PHP_EOL);
}
Natija
teg 16 bayt (128 bit): d0fe3c0946c3ed62c4b75e1644e11ca4
teg 12 bayt ( 96 bit): d0fe3c0946c3ed62c4b75e16
teg  8 bayt ( 64 bit): d0fe3c0946c3ed62
teg  4 bayt ( 32 bit): d0fe3c09
Tegni qisqartirmang

openssl_encrypt teg uzunligini o'zgartirishga ruxsat beradi. Buni qilmang.

Teg qanchalik qisqa bo'lsa, hujumchining tasodifan to'g'ri teg topish ehtimoli shuncha yuqori:

Teg uzunligiTaxmin ehtimoli
16 bayt (128 bit)1 / 2¹²⁸
8 bayt (64 bit)1 / 2⁶⁴
4 bayt (32 bit)1 / 4 milliard

Oxirgi qatorga e'tibor bering: hujumchi 4 milliard urinishda bitta soxta xabarni o'tkaza oladi. Avtomatlashtirilgan tizimda bu bir necha soatlik ish.

GCM da qisqa teg yana bir xavf tug'diradi: muvaffaqiyatli soxtalashtirish autentifikatsiya kaliti haqida ma'lumot oshkor qiladi.

Har doim 16 bayt ishlating - bu standart qiymat.

Amaliy topshiriq
  1. AES-GCM bilan matnni shifrlang va shifrmatn, teg uzunliklarini yozing.
  2. Shifrmatnning bir baytini o'zgartirib, ochishga urinib ko'ring.
  3. Xuddi shu sinovni teg va nonce uchun takrorlang.
  4. CBC dagi natija bilan solishtiring - farqni yozing.
  5. AAD qo'shing va uni o'zgartirib ochishga urinib ko'ring.
  6. AAD nima uchun yozuv identifikatorini o'z ichiga olishi kerakligini tushuntiring.
  7. Versiya + nonce + teg + shifrmatn formatida konvert sinfini yozing.
  8. Beshta xato holatini sinab, har biri aniq xabar berishini tekshiring.
  9. ChaCha20-Poly1305 bilan xuddi shu amallarni bajaring.
  10. Tegni 4 baytga qisqartiring va nima uchun bu xavfli ekanini hisoblab yozing.

Xulosa #

  • AEAD bir amalda shifrlaydi va butunlikni ta'minlaydi.
  • AES-GCM uchta narsa beradi: shifrmatn, teg va nonce - uchalasi ham saqlanadi.
  • Shifrmatn uzunligi ochiq matn bilan bir xil - to'ldirish yo'q.
  • Shifrmatn, teg, nonce yoki AAD o'zgarsa - ochish false qaytaradi.
  • Bu "hammasi yoki hech nima" tamoyili; false ni tekshirish majburiy.
  • AAD shifrlanmaydi, lekin imzolanadi - u kontekstni shifrmatnga bog'laydi.
  • AAD yozuvlar orasida shifrmatnni ko'chirishning oldini oladi.
  • Saqlash formatiga versiya bayti qo'shing - u kelajakdagi migratsiyani osonlashtiradi.
  • AES-GCM yoki ChaCha20-Poly1305 - ikkalasi ham to'g'ri tanlov.
  • Tegni hech qachon qisqartirmang: 16 bayt standart va zarur.

Keyingi bo'limda AEAD ning eng nozik joyi - nonce haqida gaplashamiz.

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.