8-bo‘lim
Autentifikatsiyalangan shifrlash (AEAD)
Maxfiylik va butunlikni bitta amalda olish - AES-GCM, teg va bog'langan ma'lumot.
Ushbu bo‘lim mundarijasi
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 #
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];
}
$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);
shifrmatn: b59a60f008bef894bb04cf0b6b5adccb38cf7ad71e447d
teg: 7adb9bb1e09806cf33de3ce063a8f40d
shifr uz.: 23 bayt
teg uz.: 16 bayt
ochilgan: Karta: 8600123456781234
| Element | O'lchami | Maxfiymi |
|---|---|---|
| Shifrmatn | Ochiq matn bilan bir xil | Ha |
| Teg (tag) | 16 bayt | Yo'q, lekin o'zgartirib bo'lmaydi |
| Nonce | 12 bayt | Yo'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 #
$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));
ozgarishsiz: 'rol=user;id=1234'
shifr buzildi: false
teg buzildi: false
nonce buzildi: false
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.
| Buzilish | CBC | AES-GCM |
|---|---|---|
| Shifrmatn o'zgardi | Ochiladi, matn buziladi | false |
| IV/nonce o'zgardi | Ochiladi, 1-blok buziladi | false |
| Teg o'zgardi | Teg yo'q | false |
Bu "hammasi yoki hech nima" tamoyili: ochish yo to'liq to'g'ri natija beradi, yo umuman hech narsa.
Kodda buni tekshirish majburiy:
$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.
$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));
shifrmatn: c6cd22b458b6eb98b802c802685bdbca
togri aad: '8600123456781234'
soxta aad: false
aad yoq: false
AAD shifrlanmaydi, lekin imzolanadi. U kontekstni shifrmatnga bog'laydi.
Klassik muammo: hujumchi ikkita yozuvning shifrlangan maydonlarini almashtirib qo'yadi.
| Yozuv | Karta (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'lumot | Nima uchun |
|---|---|
| Yozuv identifikatori | Ko'chirishning oldini oladi |
| Jadval/kolonka nomi | Boshqa maydonga ko'chirishni to'xtatadi |
| Foydalanuvchi id | Boshqa hisobga ko'chirishni to'xtatadi |
| Format versiyasi | Eski formatga tushirishni to'xtatadi |
| Vaqt oralig'i | Eski yozuvni qayta ishlatishni cheklaydi |
Saqlash formatini loyihalash #
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;
}
}
$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;
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:
$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);
}
}
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
Formatning birinchi bayti - versiya. U hozir hech narsa qilmaydi, lekin kelajakda hal qiluvchi bo'ladi.
Algoritm yoki format o'zgarganda:
| Versiyasiz | Versiya bilan |
|---|---|
| Eski ma'lumotni ochib bo'lmaydi | Eski versiyani tanib, eski usul bilan ochiladi |
| Butun bazani bir vaqtda ko'chirish kerak | Asta-sekin ko'chirish mumkin |
| Ikki format aralashsa - chalkashlik | Format 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 #
$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));
shifrmatn: 035744fd9e012ca0006e68eb
teg: c0cacc1bc3ecb01301bd51e9f3ce750a
ochilgan: Maxfiy xabar
buzilgan: false
| Mezon | AES-GCM | ChaCha20-Poly1305 |
|---|---|---|
| Uskuna tezlatgichi bo'lsa | Tezroq | Sekinroq |
| Uskuna tezlatgichi yo'q | Sekinroq | Tezroq |
| Doimiy vaqtda ishlash | Uskunaga bog'liq | Har doim |
| Nonce takrorlanishiga chidamlilik | Yomon | Yomon (XChaCha yaxshiroq) |
| Nonce uzunligi | 12 bayt | 12 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 #
$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);
}
teg 16 bayt (128 bit): d0fe3c0946c3ed62c4b75e1644e11ca4
teg 12 bayt ( 96 bit): d0fe3c0946c3ed62c4b75e16
teg 8 bayt ( 64 bit): d0fe3c0946c3ed62
teg 4 bayt ( 32 bit): d0fe3c09
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 uzunligi | Taxmin 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.
- AES-GCM bilan matnni shifrlang va shifrmatn, teg uzunliklarini yozing.
- Shifrmatnning bir baytini o'zgartirib, ochishga urinib ko'ring.
- Xuddi shu sinovni teg va nonce uchun takrorlang.
- CBC dagi natija bilan solishtiring - farqni yozing.
- AAD qo'shing va uni o'zgartirib ochishga urinib ko'ring.
- AAD nima uchun yozuv identifikatorini o'z ichiga olishi kerakligini tushuntiring.
- Versiya + nonce + teg + shifrmatn formatida konvert sinfini yozing.
- Beshta xato holatini sinab, har biri aniq xabar berishini tekshiring.
- ChaCha20-Poly1305 bilan xuddi shu amallarni bajaring.
- 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
falseqaytaradi. - Bu "hammasi yoki hech nima" tamoyili;
falseni 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.
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.