2-bo‘lim
Baytlar, kodlash va xavfsiz solishtirish
Kriptografiya matn bilan emas, baytlar bilan ishlaydi - va ularni solishtirish alohida ehtiyot talab qiladi.
Ushbu bo‘lim mundarijasi
Kriptografik funksiyalar matn emas, baytlar qaytaradi. Bu farqni tushunmaslik ko'p xatoga sabab bo'ladi.
Bu bo'limda baytlar bilan ishlash va ularni xavfsiz solishtirish bilan tanishamiz.
Bayt va belgi bir xil emas #
function korsat(string $nom, string $qiymat): void
{
printf("%-14s %s%s", $nom . ':', $qiymat, PHP_EOL);
}
$lotin = 'Salom';
$kirill = 'Салом';
$emoji = 'Salom 👋';
foreach (['lotin' => $lotin, 'kirill' => $kirill, 'emoji' => $emoji] as $n => $s) {
printf("%-8s strlen=%-3d mb_strlen=%-3d%s", $n,
strlen($s), mb_strlen($s, 'UTF-8'), PHP_EOL);
}
lotin strlen=5 mb_strlen=5
kirill strlen=10 mb_strlen=5
emoji strlen=10 mb_strlen=7
strlen kerakstrlen baytlarni sanaydi, mb_strlen esa belgilarni.
Kriptografik funksiyalar bayt bilan ishlaydi:
| Talab | To'g'ri o'lchov |
|---|---|
| AES-256 kaliti | 32 bayt |
| GCM nonce | 12 bayt |
| SHA-256 natijasi | 32 bayt |
Agar kalitni foydalanuvchi kiritgan matndan olsangiz,
mb_strlen bilan tekshirish xato: "Салом" beshta belgi,
lekin o'n bayt.
PHP satrlari ikkilik xavfsiz (binary safe) - ular
ichida nol bayt (\x00) ham bo'lishi mumkin va strlen uni
to'g'ri sanaydi.
$baytlar = "\x00\x01\xff\x00";
echo 'uzunlik: ', strlen($baytlar), PHP_EOL;
echo 'hex: ', bin2hex($baytlar), PHP_EOL;
echo 'nol bayt bormi: ',
str_contains($baytlar, "\x00") ? 'ha' : 'yoq', PHP_EOL;
uzunlik: 4
hex: 0001ff00
nol bayt bormi: ha
Nima uchun hex va base64 kerak #
$xesh = hash('sha256', 'parol123', true); // true = xom baytlar
echo 'xom baytlar uzunligi: ', strlen($xesh), PHP_EOL;
echo 'hex: ', bin2hex($xesh), PHP_EOL;
echo 'hex uzunligi: ', strlen(bin2hex($xesh)), PHP_EOL;
echo 'base64: ', base64_encode($xesh), PHP_EOL;
echo 'base64 uzunligi: ', strlen(base64_encode($xesh)), PHP_EOL;
xom baytlar uzunligi: 32
hex: a203ba4e9c565ec07ecedc94a54194aa0b237cf65203aca0e1bd420c93f2cc28
hex uzunligi: 64
base64: ogO6TpxWXsB+ztyUpUGUqgsjfPZSA6yg4b1CDJPyzCg=
base64 uzunligi: 44
| Format | Uzunlik (32 bayt uchun) | Qachon |
|---|---|---|
| Xom baytlar | 32 | Xotirada, ichki hisob-kitob |
| Hex | 64 | Jurnal, ko'z bilan solishtirish |
| Base64 | 44 | JSON, HTTP sarlavha, URL |
Base64 hex dan qisqaroq, lekin hex o'qish uchun qulayroq.
Diqqat: hash() funksiyasining uchinchi argumenti false
bo'lsa (standart holat), u hex satr qaytaradi. Ko'p
kodda bu chalkashlik keltirib chiqaradi - strlen 64
beradi, 32 emas.
// Uchinchi argument farqi
$hexShakl = hash('sha256', 'test');
$xomShakl = hash('sha256', 'test', true);
echo 'standart (hex): ', strlen($hexShakl), ' bayt', PHP_EOL;
echo 'xom (binary): ', strlen($xomShakl), ' bayt', PHP_EOL;
var_dump($hexShakl === bin2hex($xomShakl));
standart (hex): 64 bayt
xom (binary): 32 bayt
bool(true)
URL uchun base64 #
$baytlar = hex2bin('fbff3e7d0a');
$oddiy = base64_encode($baytlar);
echo 'oddiy base64: ', $oddiy, PHP_EOL;
// URL da +, / va = muammo tug'diradi
$url = rtrim(strtr($oddiy, '+/', '-_'), '=');
echo 'base64url: ', $url, PHP_EOL;
// qaytarish
$qayta = base64_decode(strtr($url, '-_', '+/'));
var_dump(bin2hex($qayta));
oddiy base64: +/8+fQo=
base64url: -_8-fQo
string(10) "fbff3e7d0a"
Endi eng muhim qism: solishtirish #
// FAQAT NAMOYISH UCHUN: nechta bayt solishtirilganini sanaydi
function sanovchiSolishtir(string $a, string $b, ?int &$solishtirildi): bool
{
$solishtirildi = 0;
if (strlen($a) !== strlen($b)) {
return false;
}
for ($i = 0, $n = strlen($a); $i < $n; $i++) {
$solishtirildi++;
if ($a[$i] !== $b[$i]) {
return false; // BIRINCHI farqda to'xtaydi
}
}
return true;
}
$togri = 'a1b2c3d4e5f6a7b8';
$urinishlar = [
'xxxxxxxxxxxxxxxx', // birinchi bayt xato
'a1xxxxxxxxxxxxxx', // ikkinchi bayt xato
'a1b2c3d4xxxxxxxx', // yarmi to'g'ri
'a1b2c3d4e5f6a7b8', // to'liq to'g'ri
];
foreach ($urinishlar as $u) {
sanovchiSolishtir($togri, $u, $n);
printf('%-18s %2d bayt solishtirildi%s', substr($u, 0, 10) . '...',
$n, PHP_EOL);
}
xxxxxxxxxx... 1 bayt solishtirildi
a1xxxxxxxx... 3 bayt solishtirildi
a1b2c3d4xx... 9 bayt solishtirildi
a1b2c3d4e5... 16 bayt solishtirildi
Yuqoridagi jadvalga qarang: qancha ko'p bayt to'g'ri bo'lsa, funksiya shuncha uzoq ishlaydi.
Real hujumda hujumchi baytlar sonini ko'rmaydi, lekin vaqtni o'lchay oladi. Farq juda kichik (nanosekundlar), lekin:
| Sharoit | Natija |
|---|---|
| Bir marta o'lchash | Shovqin ichida yo'qoladi |
| Minglab marta o'lchash va o'rtacha olish | Farq ko'rinadi |
Shunday qilib hujumchi tokenni bayt-bayt topib boradi: avval birinchi baytni, keyin ikkinchisini. 16 baytlik token uchun 256×16 = 4096 urinish yetadi - 256¹⁶ o'rniga.
Bu vaqt bo'yicha yon kanal (timing side channel) hujumi.
Yechim: hash_equals #
$togri = 'a1b2c3d4e5f6a7b8';
// hash_equals HAR DOIM barcha baytni solishtiradi
var_dump(hash_equals($togri, 'xxxxxxxxxxxxxxxx'));
var_dump(hash_equals($togri, 'a1b2c3d4xxxxxxxx'));
var_dump(hash_equals($togri, 'a1b2c3d4e5f6a7b8'));
// uzunlik farq qilsa - darhol false
var_dump(hash_equals($togri, 'qisqa'));
bool(false)
bool(false)
bool(true)
bool(false)
hash_equals qachon ishlatiladi| Solishtirilayotgan narsa | Funksiya |
|---|---|
| HMAC yoki imzo | hash_equals |
| API kaliti, token | hash_equals |
| Sessiya identifikatori | hash_equals |
| Parolni tiklash kodi | hash_equals |
| CSRF tokeni | hash_equals |
| Parol xeshi | password_verify (o'zi doimiy vaqtda) |
| Oddiy matn (foydalanuvchi ismi) | === yetarli |
Qoida oddiy: maxfiy qiymat solishtirilayotgan bo'lsa,
hash_equals ishlating.
Nomi chalkash: funksiya faqat xesh uchun emas, istalgan ikki satr uchun ishlaydi.
Diqqat: uzunliklar farq qilsa u darhol false qaytaradi.
Ya'ni uzunlik yashirilmaydi - lekin bu odatda muammo
emas, chunki token uzunligi ochiq ma'lumot.
== va tur o'yinlari #
PHP da == satrlarni ba'zan son sifatida solishtiradi:
// Ikkalasining ham MD5 xeshi "0e" bilan boshlanadi
$a = '240610708';
$b = 'QNKCDZO';
echo 'md5(a): ', md5($a), PHP_EOL;
echo 'md5(b): ', md5($b), PHP_EOL;
var_dump(md5($a) == md5($b)); // == bilan
var_dump(md5($a) === md5($b)); // === bilan
md5(a): 0e462097431906509019562988736854
md5(b): 0e830400451993494058024219903391
bool(true)
bool(false)
0e462097... va 0e830400... - ikkalasi ham 0e bilan
boshlanib, qolgani faqat raqamlardan iborat.
PHP 7 va undan oldingi versiyalarda == bunday satrlarni
ilmiy yozuvdagi son deb qabul qilardi: 0e462097...
= 0 × 10⁴⁶²⁰⁹⁷ = 0. Ikkala tomon ham nolga teng bo'lib
chiqardi.
PHP 8 da == ning xulqi o'zgardi: satr va satr
solishtirilganda ular satr sifatida qiyoslanadi. Lekin:
| Holat | PHP 8 da natija |
|---|---|
'0e123' == '0e456' | Ikkalasi ham raqamli satr - true |
'abc' == 0 | false (PHP 8 da tuzatilgan) |
md5($a) == md5($b) | Yuqoridagi natijaga qarang |
Xulosa o'zgarmaydi: maxfiy qiymatlarni hech qachon ==
bilan solishtirmang. hash_equals yoki hech bo'lmasa
=== ishlating.
hash_equals qanday ishlaydi #
// Tamoyilni ko'rsatuvchi soddalashtirilgan versiya
function doimiyVaqtSolishtir(string $a, string $b): bool
{
if (strlen($a) !== strlen($b)) {
return false;
}
$farq = 0;
for ($i = 0, $n = strlen($a); $i < $n; $i++) {
// XOR: bir xil baytlar 0 beradi, farqli baytlar 0 dan katta
$farq |= ord($a[$i]) ^ ord($b[$i]);
}
// hech qayerda erta return yo'q
return $farq === 0;
}
$togri = 'a1b2c3d4e5f6a7b8';
var_dump(doimiyVaqtSolishtir($togri, 'a1b2c3d4e5f6a7b8'));
var_dump(doimiyVaqtSolishtir($togri, 'a1b2c3d4e5f6a7b9'));
var_dump(doimiyVaqtSolishtir($togri, 'xxxxxxxxxxxxxxxx'));
var_dump(doimiyVaqtSolishtir($togri, 'qisqa'));
bool(true)
bool(false)
bool(false)
bool(false)
hash_equals ni ishlatingYuqoridagi kod tamoyilni ko'rsatish uchun yozilgan.
Real kodda hash_equals afzal, chunki:
| Sabab | Izoh |
|---|---|
| C darajasida yozilgan | PHP darajasidagi optimallashtirishlar aralashmaydi |
| Kompilyator uni "soddalashtirmaydi" | PHP kodini JIT o'zgartirishi mumkin |
| Sinovdan o'tgan | Yillar davomida tekshirilgan |
| Qisqaroq | Bir qator |
Bu 1-bo'limdagi qoidaning takrori: primitivni o'zingiz yozmang.
Xavfsiz token yaratish #
// Token uzunligi - baytlarda o'lchanadi
$tokenBaytlari = random_bytes(32);
echo 'baytlar: ', strlen($tokenBaytlari), PHP_EOL;
echo 'hex: ', strlen(bin2hex($tokenBaytlari)), ' belgi', PHP_EOL;
echo 'base64: ', strlen(base64_encode($tokenBaytlari)), ' belgi', PHP_EOL;
echo 'base64url: ', strlen(rtrim(strtr(
base64_encode($tokenBaytlari), '+/', '-_'), '=')), ' belgi', PHP_EOL;
// Har chaqiruv boshqa natija beradi
$a = bin2hex(random_bytes(16));
$b = bin2hex(random_bytes(16));
var_dump($a === $b);
baytlar: 32
hex: 64 belgi
base64: 44 belgi
base64url: 43 belgi
bool(false)
rand() va mt_rand() token uchun yaramaydi| Funksiya | Kriptografik | Qachon |
|---|---|---|
rand(), mt_rand() | Yo'q | O'yin, simulyatsiya |
uniqid() | Yo'q | Hech qachon (vaqtga asoslangan) |
shuffle(), array_rand() | Yo'q | Ko'rgazma |
random_bytes() | Ha | Token, kalit, nonce |
random_int() | Ha | Tasodifiy son |
mt_rand Mersenne Twister generatoridan foydalanadi. Uning
ichki holatini bir necha chiqishdan tiklash mumkin - ya'ni
hujumchi keyingi tokenlarni oldindan hisoblab qo'yadi.
uniqid() esa mikrosekundlardagi vaqtga asoslangan - uni
taxmin qilish yanada oson.
Bu mavzu 15-bo'limda batafsil ko'riladi.
- Lotin, kirill va emoji satrlar uchun
strlenvamb_strlenni solishtiring. - Nol bayt ichidagi satr yarating va uzunligini tekshiring.
- SHA-256 ni xom va hex shaklda oling, uzunliklarini yozing.
- Bir xil baytlarni hex, base64 va base64url da chiqaring.
- Base64url ni qayta oddiy base64 ga aylantirib, dastlabki baytlarni oling.
- Sanovchi solishtiruvchi yozing va turli kiritmalar uchun bayt sonini o'lchang.
- Nima uchun bu son hujumchiga foydali ekanini tushuntiring.
hash_equalsbilan xuddi shu solishtirishlarni bajaring.md5('240610708') == md5('QNKCDZO')natijasini o'z PHP versiyangizda tekshiring.random_bytesvamt_randfarqini uch qatorda yozing.
Xulosa #
- Kriptografiya baytlar bilan ishlaydi -
strlen,mb_strlenemas. - PHP satrlari ikkilik xavfsiz: ular nol bayt saqlashi mumkin.
hash()standart holda hex satr, uchinchi argumenttruebo'lsa xom baytlar qaytaradi.- Hex - o'qish uchun, base64 - uzatish uchun, xom baytlar - hisob-kitob uchun.
- URL uchun
+/belgilari-_ga almashtiriladi (base64url). - Erta to'xtaydigan solishtirish vaqt orqali ma'lumot oshkor qiladi.
- Bu hujum tokenni bayt-bayt topishga imkon beradi: 256×16 urinish, 256¹⁶ o'rniga.
- Maxfiy qiymatlar uchun har doim
hash_equalsishlating. ==PHP da tur o'yinlariga sabab bo'ladi - kamida===ishlating.- Token va kalitlar faqat
random_bytes/random_intbilan yaratiladi.
Keyingi bo'limda xesh funksiyalari 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.