20-bo‘lim
Amaliy loyiha - sirlar sandig'i
O'n to'qqiz bo'limni bitta ishlaydigan tizimda birlashtirish - shifrlangan omborcha, kalit almashtirish va buzilishni aniqlash.
Ushbu bo‘lim mundarijasi
Yakuniy bo'limda sirlar sandig'i quramiz: parollar, API kalitlari va boshqa maxfiy qiymatlarni shifrlangan holda saqlaydigan omborcha.
Har qism oldingi bo'limlardan keladi:
| Qism | Bo'lim |
|---|---|
| Asosiy paroldan kalit (PBKDF2) | 6 |
| Har sirga alohida kalit (konvert sxemasi) | 16 |
| AEAD shifrlash va AAD | 8 |
| Tasodifiy nonce | 9, 15 |
| Nom bo'yicha qidiruv (ko'r indeks) | 16 |
| Doimiy vaqtda solishtirish | 2 |
| HMAC zanjirli audit jurnali | 5 |
| Kalit versiyalari va almashtirish | 18 |
1-qism: asosiy kalit #
function korsat(string $nom, string $qiymat): void
{
printf("%-24s %s%s", $nom . ':', $qiymat, PHP_EOL);
}
final class AsosiyKalit
{
// DIQQAT: darslikda tez ishlashi uchun past qiymat.
// Ishlab chiqarishda kamida 600 000 (6-bo'lim).
private const TAKRORLAR = 100000;
private const TUZ_UZUNLIGI = 16;
private function __construct(
public readonly string $kek,
public readonly string $tuz,
) {}
public static function yangi(string $parol): self
{
return self::paroldan($parol, random_bytes(self::TUZ_UZUNLIGI));
}
public static function paroldan(string $parol, string $tuz): self
{
if (strlen($parol) < 12) {
throw new InvalidArgumentException(
'asosiy parol kamida 12 belgi bolishi kerak');
}
$xom = hash_pbkdf2('sha256', $parol, $tuz,
self::TAKRORLAR, 32, true);
return new self($xom, $tuz);
}
/** Har maqsadga alohida kalit (6-bo'lim) */
public function maqsadKaliti(string $maqsad): string
{
return hash_hkdf('sha256', $this->kek, 32, 'sandiq-v1:' . $maqsad);
}
public function barmoqIzi(): string
{
return substr(bin2hex(hash_hmac('sha256', 'barmoq',
$this->kek, true)), 0, 16);
}
}
// Qat'iy tuz - natija takrorlanishi uchun
$tuz = hex2bin(str_repeat('5a', 16));
$asosiy = AsosiyKalit::paroldan('juda-uzun-asosiy-parol', $tuz);
korsat('KEK barmoq izi', $asosiy->barmoqIzi());
korsat('shifr kaliti', substr(bin2hex($asosiy->maqsadKaliti('shifr')), 0, 24) . '...');
korsat('indeks kaliti', substr(bin2hex($asosiy->maqsadKaliti('indeks')), 0, 24) . '...');
korsat('jurnal kaliti', substr(bin2hex($asosiy->maqsadKaliti('jurnal')), 0, 24) . '...');
// Bir xil parol + bir xil tuz = bir xil kalit
$yana = AsosiyKalit::paroldan('juda-uzun-asosiy-parol', $tuz);
korsat('takrorlanadimi', $yana->barmoqIzi() === $asosiy->barmoqIzi() ? 'ha' : 'yoq');
// Qisqa parol rad etiladi
try {
AsosiyKalit::paroldan('qisqa', $tuz);
} catch (InvalidArgumentException $x) {
korsat('qisqa parol', 'RAD: ' . $x->getMessage());
}
KEK barmoq izi: 197620d2623dabcd
shifr kaliti: 44e316c5944393147306259d...
indeks kaliti: 9bc03971652c8e8777eaee70...
jurnal kaliti: a11feb7dd884197b17aaea7d...
takrorlanadimi: ha
qisqa parol: RAD: asosiy parol kamida 12 belgi bolishi kerak
2-qism: konvert va sir yozuvi #
final readonly class SirYozuvi
{
public function __construct(
public string $nom,
public string $indeks,
public string $oralganKalit,
public string $shifrlangan,
public int $versiya,
public int $yaratilgan,
) {}
}
final class Sandiq
{
/** @var array<string, SirYozuvi> */
private array $yozuvlar = [];
public function __construct(
private AsosiyKalit $asosiy,
private readonly AuditJurnali $jurnal,
private int $versiya = 1,
) {}
public function qosh(string $nom, string $qiymat, int $vaqt): void
{
// 1. Har sirga ALOHIDA ma'lumot kaliti (16-bo'lim)
$dek = random_bytes(32);
// 2. Sirni DEK bilan shifrlaymiz, AAD ga nom va versiya
$nonce = random_bytes(12);
$teg = '';
$shifr = openssl_encrypt($qiymat, 'aes-256-gcm', $dek,
OPENSSL_RAW_DATA, $nonce, $teg, "{$nom}|v{$this->versiya}");
if ($shifr === false) {
throw new RuntimeException('shifrlash muvaffaqiyatsiz');
}
// 3. DEK ni asosiy kalit bilan o'raymiz
$kekNonce = random_bytes(12);
$kekTeg = '';
$oralgan = openssl_encrypt($dek, 'aes-256-gcm',
$this->asosiy->maqsadKaliti('shifr'), OPENSSL_RAW_DATA,
$kekNonce, $kekTeg, "dek|{$nom}");
if ($oralgan === false) {
throw new RuntimeException('DEK oralmadi');
}
$indeks = $this->indeks($nom);
$this->yozuvlar[$indeks] = new SirYozuvi(
nom: $nom,
indeks: $indeks,
oralganKalit: $kekNonce . $kekTeg . $oralgan,
shifrlangan: $nonce . $teg . $shifr,
versiya: $this->versiya,
yaratilgan: $vaqt,
);
$this->jurnal->yoz('qoshildi', $nom, $vaqt);
}
public function ol(string $nom, int $vaqt): string
{
$yozuv = $this->yozuvlar[$this->indeks($nom)] ?? null;
if ($yozuv === null) {
$this->jurnal->yoz('topilmadi', $nom, $vaqt);
throw new RuntimeException('sir topilmadi');
}
// 1. DEK ni ochamiz
$o = $yozuv->oralganKalit;
$dek = openssl_decrypt(substr($o, 28), 'aes-256-gcm',
$this->asosiy->maqsadKaliti('shifr'), OPENSSL_RAW_DATA,
substr($o, 0, 12), substr($o, 12, 16), "dek|{$yozuv->nom}");
if ($dek === false) {
$this->jurnal->yoz('DEK ochilmadi', $nom, $vaqt);
throw new RuntimeException('sandiq buzilgan yoki parol notogri');
}
// 2. Sirni ochamiz
$s = $yozuv->shifrlangan;
$qiymat = openssl_decrypt(substr($s, 28), 'aes-256-gcm', $dek,
OPENSSL_RAW_DATA, substr($s, 0, 12), substr($s, 12, 16),
"{$yozuv->nom}|v{$yozuv->versiya}");
if ($qiymat === false) {
$this->jurnal->yoz('BUZILGAN', $nom, $vaqt);
throw new RuntimeException('butunlik tekshiruvi otmadi');
}
$this->jurnal->yoz('oqildi', $nom, $vaqt);
return $qiymat;
}
/** Ko'r indeks - nom bo'yicha qidiruv uchun (16-bo'lim) */
private function indeks(string $nom): string
{
return bin2hex(substr(hash_hmac('sha256',
mb_strtolower(trim($nom), 'UTF-8'),
$this->asosiy->maqsadKaliti('indeks'), true), 0, 8));
}
/** @return list<array{nom: string, versiya: int}> */
public function royxat(): array
{
$natija = [];
foreach ($this->yozuvlar as $y) {
$natija[] = ['nom' => $y->nom, 'versiya' => $y->versiya];
}
usort($natija, static fn(array $a, array $b): int
=> strcmp($a['nom'], $b['nom']));
return $natija;
}
public function soni(): int
{
return count($this->yozuvlar);
}
/** Buzilishni sinash uchun - odatda bunday metod bo'lmaydi */
public function baytniBuz(string $nom, int $orin): void
{
$i = $this->indeks($nom);
$y = $this->yozuvlar[$i];
$s = $y->shifrlangan;
$s[$orin] = chr(ord($s[$orin]) ^ 0xff);
$this->yozuvlar[$i] = new SirYozuvi($y->nom, $y->indeks,
$y->oralganKalit, $s, $y->versiya, $y->yaratilgan);
}
}
3-qism: buzilishni sezadigan audit jurnali #
final class AuditJurnali
{
/** @var list<array{vaqt: int, amal: string, nom: string, mac: string}> */
private array $qatorlar = [];
private string $oxirgiMac;
public function __construct(private readonly string $kalit)
{
// Zanjir boshlanishi
$this->oxirgiMac = str_repeat("\x00", 32);
}
public function yoz(string $amal, string $nom, int $vaqt): void
{
$qator = "{$vaqt}|{$amal}|{$nom}";
// Har yozuv OLDINGI MAC ni ham qamrab oladi - zanjir hosil bo'ladi
$mac = hash_hmac('sha256', $this->oxirgiMac . $qator,
$this->kalit, true);
$this->qatorlar[] = [
'vaqt' => $vaqt, 'amal' => $amal,
'nom' => $nom, 'mac' => $mac,
];
$this->oxirgiMac = $mac;
}
/** Zanjirni boshidan tekshiradi */
public function butunmi(): bool
{
$oldingi = str_repeat("\x00", 32);
foreach ($this->qatorlar as $q) {
$kutilgan = hash_hmac('sha256',
$oldingi . "{$q['vaqt']}|{$q['amal']}|{$q['nom']}",
$this->kalit, true);
if (!hash_equals($kutilgan, $q['mac'])) {
return false;
}
$oldingi = $q['mac'];
}
return true;
}
public function soni(): int
{
return count($this->qatorlar);
}
/** @return list<string> */
public function korinish(): array
{
$natija = [];
foreach ($this->qatorlar as $i => $q) {
$natija[] = sprintf('%d. %s %-12s %s', $i + 1,
gmdate('H:i:s', $q['vaqt']), $q['amal'], $q['nom']);
}
return $natija;
}
/** Sinash uchun: o'rtadagi yozuvni o'chirib yuborish */
public function yozuvniOchir(int $orin): void
{
array_splice($this->qatorlar, $orin, 1);
}
}
function sandiqYarat(int $vaqt): array
{
$asosiy = AsosiyKalit::paroldan('juda-uzun-asosiy-parol',
hex2bin(str_repeat('5a', 16)));
$jurnal = new AuditJurnali($asosiy->maqsadKaliti('jurnal'));
$sandiq = new Sandiq($asosiy, $jurnal);
$sandiq->qosh('baza-paroli', 'S3rv3r!Parol#2025', $vaqt);
$sandiq->qosh('api-kaliti', 'sk_live_abcdefghijklmnop', $vaqt + 1);
$sandiq->qosh('smtp-paroli', 'pochta-maxfiy-2025', $vaqt + 2);
return [$sandiq, $jurnal, $asosiy];
}
$vaqt = 1735689600;
[$sandiq, $jurnal] = sandiqYarat($vaqt);
korsat('sandiqdagi sirlar', $sandiq->soni() . ' ta');
foreach ($sandiq->royxat() as $s) {
printf(' %-14s (v%d)%s', $s['nom'], $s['versiya'], PHP_EOL);
}
echo PHP_EOL;
korsat('api-kaliti', $sandiq->ol('api-kaliti', $vaqt + 10));
korsat('katta harf bilan', $sandiq->ol('API-KALITI', $vaqt + 11));
korsat('jurnal yozuvlari', $jurnal->soni() . ' ta');
korsat('jurnal butunmi', $jurnal->butunmi() ? 'ha' : 'YOQ');
sandiqdagi sirlar: 3 ta
api-kaliti (v1)
baza-paroli (v1)
smtp-paroli (v1)
api-kaliti: sk_live_abcdefghijklmnop
katta harf bilan: sk_live_abcdefghijklmnop
jurnal yozuvlari: 5 ta
jurnal butunmi: ha
Buzilishni aniqlash #
$vaqt = 1735689600;
[$sandiq, $jurnal] = sandiqYarat($vaqt);
// Sirlardan biri diskda buzildi
$sandiq->baytniBuz('smtp-paroli', 30);
foreach (['baza-paroli', 'smtp-paroli', 'api-kaliti'] as $nom) {
try {
$q = $sandiq->ol($nom, $vaqt + 20);
printf('%-14s OK (%d belgi)%s', $nom, strlen($q), PHP_EOL);
} catch (RuntimeException $x) {
printf('%-14s RAD: %s%s', $nom, $x->getMessage(), PHP_EOL);
}
}
echo PHP_EOL;
korsat('jurnalda yozuv', $jurnal->soni() . ' ta');
echo 'Buzilish jurnalga tushdi:', PHP_EOL;
foreach ($jurnal->korinish() as $q) {
if (str_contains($q, 'BUZILGAN')) {
echo ' ', $q, PHP_EOL;
}
}
baza-paroli OK (17 belgi)
smtp-paroli RAD: butunlik tekshiruvi otmadi
api-kaliti OK (24 belgi)
jurnalda yozuv: 6 ta
Buzilish jurnalga tushdi:
5. 00:00:20 BUZILGAN smtp-paroli
Faqat smtp-paroli rad etildi. Qolgan ikkitasi normal
ochildi.
Sabab - har sirning o'z DEK i bor. Bitta yozuvning buzilishi boshqalariga tegmaydi.
Agar hammasi bitta kalit bilan bitta blokda shifrlangan bo'lsa, bitta bayt buzilishi butun sandiqni yaroqsiz qilardi.
Bu konvert sxemasining yana bir foydasi (16-bo'lim).
Audit jurnalining buzilishini aniqlash #
$vaqt = 1735689600;
[$sandiq, $jurnal] = sandiqYarat($vaqt);
$sandiq->ol('api-kaliti', $vaqt + 10);
$sandiq->ol('baza-paroli', $vaqt + 11);
korsat('yozuvlar soni', $jurnal->soni() . ' ta');
korsat('zanjir butunmi', $jurnal->butunmi() ? 'ha' : 'YOQ');
echo PHP_EOL, 'Jurnal:', PHP_EOL;
foreach ($jurnal->korinish() as $q) {
echo ' ', $q, PHP_EOL;
}
// Hujumchi o'z izini o'chirmoqchi bo'ladi
$jurnal->yozuvniOchir(3);
echo PHP_EOL, 'Ortadagi yozuv ochirilgandan keyin:', PHP_EOL;
korsat('yozuvlar soni', $jurnal->soni() . ' ta');
korsat('zanjir butunmi', $jurnal->butunmi() ? 'ha' : 'YOQ - BUZILGAN');
yozuvlar soni: 5 ta
zanjir butunmi: ha
Jurnal:
1. 00:00:00 qoshildi baza-paroli
2. 00:00:01 qoshildi api-kaliti
3. 00:00:02 qoshildi smtp-paroli
4. 00:00:10 oqildi api-kaliti
5. 00:00:11 oqildi baza-paroli
Ortadagi yozuv ochirilgandan keyin:
yozuvlar soni: 4 ta
zanjir butunmi: YOQ - BUZILGAN
Har yozuvning MAC i oldingi yozuvning MAC ini ham qamrab oladi:
mac[i] = HMAC(kalit, mac[i-1] + qator[i])
Demak o'rtadagi yozuvni o'chirish yoki o'zgartirish undan keyingi barcha MAC larni yaroqsiz qiladi.
Hujumchi izini yashirish uchun butun zanjirni qayta hisoblashi kerak - buning uchun esa jurnal kaliti kerak.
Bu buzilishni sezadigan (tamper-evident) jurnal. Diqqat: u buzilishning oldini olmaydi, faqat uni aniqlaydi.
To'liq himoya uchun jurnal:
| Chora | Nima beradi |
|---|---|
| Faqat qo'shiladigan (append-only) saqlash | O'chirishni qiyinlashtiradi |
| Boshqa serverga nusxalash | Mahalliy o'zgarishni aniqlaydi |
| MAC ni tashqi tizimga yuborish | Butun faylni almashtirishni aniqlaydi |
| Har kun oxirgi MAC ni e'lon qilish | Orqaga qaytarib yozishni imkonsiz qiladi |
Asosiy parolni almashtirish #
final class ParolAlmashtiruvchi
{
/**
* Faqat O'RALGAN DEK lar qayta o'raladi.
* Sirlarning o'zi umuman tegilmaydi.
*/
public static function almashtir(
string $oralganKalit,
string $nom,
AsosiyKalit $eski,
AsosiyKalit $yangi,
): string {
// 1. Eski kalit bilan DEK ni ochamiz
$dek = openssl_decrypt(substr($oralganKalit, 28), 'aes-256-gcm',
$eski->maqsadKaliti('shifr'), OPENSSL_RAW_DATA,
substr($oralganKalit, 0, 12), substr($oralganKalit, 12, 16),
"dek|{$nom}");
if ($dek === false) {
throw new RuntimeException('eski parol notogri');
}
// 2. Yangi kalit bilan qayta o'raymiz
$nonce = random_bytes(12);
$teg = '';
$qayta = openssl_encrypt($dek, 'aes-256-gcm',
$yangi->maqsadKaliti('shifr'), OPENSSL_RAW_DATA,
$nonce, $teg, "dek|{$nom}");
return $nonce . $teg . $qayta;
}
}
$eski = AsosiyKalit::paroldan('juda-uzun-asosiy-parol',
hex2bin(str_repeat('5a', 16)));
$yangi = AsosiyKalit::paroldan('yangi-juda-uzun-parol-2025',
hex2bin(str_repeat('6b', 16)));
korsat('eski KEK', $eski->barmoqIzi());
korsat('yangi KEK', $yangi->barmoqIzi());
// Bitta sir uchun DEK yasaymiz va o'raymiz
$dek = hex2bin(str_repeat('99', 32));
$nonce = hex2bin(str_repeat('01', 12));
$teg = '';
$oralgan = $nonce . $teg;
$shifr = openssl_encrypt($dek, 'aes-256-gcm',
$eski->maqsadKaliti('shifr'), OPENSSL_RAW_DATA, $nonce, $teg, 'dek|test');
$oralgan = $nonce . $teg . $shifr;
// Qayta o'rash
$yangiOralgan = ParolAlmashtiruvchi::almashtir($oralgan, 'test', $eski, $yangi);
korsat('oralgan DEK uzunligi', strlen($yangiOralgan) . ' bayt');
korsat('eski bilan ochiladimi',
openssl_decrypt(substr($yangiOralgan, 28), 'aes-256-gcm',
$eski->maqsadKaliti('shifr'), OPENSSL_RAW_DATA,
substr($yangiOralgan, 0, 12), substr($yangiOralgan, 12, 16),
'dek|test') === false ? 'yoq - togri' : 'HA - XATO');
$ochilgan = openssl_decrypt(substr($yangiOralgan, 28), 'aes-256-gcm',
$yangi->maqsadKaliti('shifr'), OPENSSL_RAW_DATA,
substr($yangiOralgan, 0, 12), substr($yangiOralgan, 12, 16), 'dek|test');
korsat('yangi bilan ochiladimi', $ochilgan !== false ? 'ha' : 'yoq');
korsat('DEK saqlandimi', hash_equals($dek, (string) $ochilgan) ? 'ha' : 'yoq');
eski KEK: 197620d2623dabcd
yangi KEK: 45fdebbf83db5051
oralgan DEK uzunligi: 60 bayt
eski bilan ochiladimi: yoq - togri
yangi bilan ochiladimi: ha
DEK saqlandimi: ha
Parol almashtirishda faqat 60 bayt qayta shifrlanadi (o'ralgan DEK), sirning o'zi qanchalik katta bo'lishidan qat'i nazar.
| Sirlar soni | To'g'ridan-to'g'ri | Konvert sxemasi |
|---|---|---|
| 100 | Hammasini qayta shifrlash | 100 × 60 bayt |
| 1 000 000 | Soatlab ishlov | Daqiqalar |
| 10 GB fayl | 10 GB qayta yozish | 60 bayt |
Oxirgi qator sxemaning butun ma'nosini ko'rsatadi.
Xavfsizlik xossalari #
$xossalar = [
['Sirlar diskda ochiq emas', 'AES-256-GCM', 'ha'],
['Ozgartirish aniqlanadi', 'GCM tegi', 'ha'],
['Bir sir buzilsa qolgani ishlaydi', 'har sirga DEK', 'ha'],
['Nom boyicha qidiruv', 'HMAC kor indeks', 'ha'],
['Sir nomlari diskda ochiqmi', 'indeks - HMAC', 'yoq'],
['Jurnal buzilishi aniqlanadi', 'HMAC zanjiri', 'ha'],
['Parol almashtirish tez', 'konvert sxemasi', 'ha'],
['Parol bilmasdan ochish', 'PBKDF2 600k', 'yoq'],
['Server buzilsa himoya', 'kalit xotirada', 'YOQ'],
['Zaxira nusxa xavfsizmi', 'shifrlangan', 'ha'],
];
printf("%-34s %-20s %s%s", 'XOSSA', 'QANDAY', 'BORMI', PHP_EOL);
echo str_repeat('-', 64), PHP_EOL;
foreach ($xossalar as $x) {
printf("%-34s %-20s %s%s", ...[...$x, PHP_EOL]);
}
XOSSA QANDAY BORMI
----------------------------------------------------------------
Sirlar diskda ochiq emas AES-256-GCM ha
Ozgartirish aniqlanadi GCM tegi ha
Bir sir buzilsa qolgani ishlaydi har sirga DEK ha
Nom boyicha qidiruv HMAC kor indeks ha
Sir nomlari diskda ochiqmi indeks - HMAC yoq
Jurnal buzilishi aniqlanadi HMAC zanjiri ha
Parol almashtirish tez konvert sxemasi ha
Parol bilmasdan ochish PBKDF2 600k yoq
Server buzilsa himoya kalit xotirada YOQ
Zaxira nusxa xavfsizmi shifrlangan ha
"Server buzilsa himoya" - YO'Q.
Sandiq ishlab turganda asosiy kalit xotirada bo'ladi. Hujumchi ilova jarayoniga kirsa, u kalitni o'qiy oladi.
Bu 16-bo'limdagi tahdid modelining takrori. Sandiq quyidagilardan himoyalaydi:
| Himoyalaydi | Himoyalamaydi |
|---|---|
| O'g'irlangan disk yoki zaxira | Ishlab turgan serverga kirish |
| Fayl tizimiga o'qish huquqi | Ilova zaifligi orqali kirish |
| Nusxa ko'chirilgan ma'lumot | Klaviatura kuzatuvchisi |
| Ma'lumotni jimgina o'zgartirish | Xotira dumpi |
Real sirlar boshqaruvchilari (Vault, KMS) bu bo'shliqni qisman yopadi: kalit umuman ilova jarayoniga tushmaydi, shifrlash alohida xizmatda bajariladi.
Lekin u yerda ham hujumchi ilova nomidan so'rov yubora oladi. Farqi shundaki, bu jurnalga tushadi va cheklovlar qo'llanadi.
Yakuniy sinov #
$vaqt = 1735689600;
[$sandiq, $jurnal, $asosiy] = sandiqYarat($vaqt);
$sinovlar = [];
// 1. Barcha sirlar to'g'ri ochiladi
$hammasi = true;
foreach (['baza-paroli', 'api-kaliti', 'smtp-paroli'] as $n) {
try {
$sandiq->ol($n, $vaqt + 5);
} catch (RuntimeException) {
$hammasi = false;
}
}
$sinovlar['barcha sirlar ochiladi'] = $hammasi;
// 2. Mavjud bo'lmagan sir rad etiladi
try {
$sandiq->ol('yoq-bunday', $vaqt + 6);
$sinovlar['nomavjud sir rad etiladi'] = false;
} catch (RuntimeException) {
$sinovlar['nomavjud sir rad etiladi'] = true;
}
// 3. Buzilgan sir rad etiladi
$sandiq->baytniBuz('api-kaliti', 30);
try {
$sandiq->ol('api-kaliti', $vaqt + 7);
$sinovlar['buzilgan sir rad etiladi'] = false;
} catch (RuntimeException) {
$sinovlar['buzilgan sir rad etiladi'] = true;
}
// 4. Buzilgandan keyin ham boshqalari ishlaydi
try {
$sandiq->ol('baza-paroli', $vaqt + 8);
$sinovlar['qolganlari ishlaydi'] = true;
} catch (RuntimeException) {
$sinovlar['qolganlari ishlaydi'] = false;
}
// 5. Jurnal zanjiri butun
$sinovlar['jurnal zanjiri butun'] = $jurnal->butunmi();
// 6. Jurnal buzilsa aniqlanadi
$jurnal->yozuvniOchir(2);
$sinovlar['jurnal buzilishi aniqlanadi'] = !$jurnal->butunmi();
foreach ($sinovlar as $nom => $natija) {
printf('%-30s %s%s', $nom, $natija ? 'OTDI' : 'YIQILDI', PHP_EOL);
}
printf('%sjami: %d/%d%s', PHP_EOL,
count(array_filter($sinovlar)), count($sinovlar), PHP_EOL);
barcha sirlar ochiladi OTDI
nomavjud sir rad etiladi OTDI
buzilgan sir rad etiladi OTDI
qolganlari ishlaydi OTDI
jurnal zanjiri butun OTDI
jurnal buzilishi aniqlanadi OTDI
jami: 6/6
- Ushbu sandiqni noldan yozing (kod ko'chirmang, qayta yozing).
- PBKDF2 takrorlarini 600 000 ga oshiring va vaqtni o'lchang.
- Yangi sir turi qo'shing (masalan fayl mazmuni).
- Sirni o'chirish amalini qo'shing va uni jurnalga yozing.
- Bir sirni buzib, faqat o'sha rad etilishini tekshiring.
- Jurnal zanjirining o'rtasidagi yozuvni o'zgartiring.
- Zanjir buzilganini aniqlaydigan testni yozing.
- Asosiy parolni almashtiring va barcha sirlar ochilishini tekshiring.
- Sandiq faylga saqlanadigan va o'qiladigan qilib kengaytiring.
- Tahdid modelini yozing: bu sandiq nimadan himoyalaydi va nimadan yo'q.
Xulosa #
- Sandiq ikki qatlamli kalit ishlatadi: paroldan KEK, har sirga DEK.
- Asosiy paroldan kalit PBKDF2 bilan olinadi (ishlab chiqarishda 600 000+ takror).
- KEK dan HKDF orqali uchta ajratilgan kalit chiqariladi: shifr, indeks, jurnal.
- Har sir o'z DEK i bilan shifrlanadi - bir yozuvning buzilishi qolganlariga tegmaydi.
- AAD ga nom va versiya kiritilgani sirlarni almashtirishga yo'l qo'ymaydi.
- Ko'r indeks nom bo'yicha qidiruvni beradi, nomlarni esa ochiq saqlamaydi.
- Audit jurnali HMAC zanjiri bilan bog'langan - o'rtadagi yozuvni o'chirish aniqlanadi.
- Zanjir buzilishning oldini olmaydi, faqat uni aniqlaydi.
- Parol almashtirilganda faqat o'ralgan DEK lar yangilanadi - sirlar tegilmaydi.
- Sandiq ishlab turgan serverga kirishdan himoya bermaydi - bu chegara har doim yodda tutilsin.
Darslik shu bilan yakunlandi. Eng muhim xulosa 1-bo'limdan o'zgarmadi: algoritmni o'zingiz yozmang, lekin uni to'g'ri birlashtirishni biling.
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.