18-bo‘lim
Kalitlarni boshqarish
Eng kuchli algoritm ham kalit noto'g'ri saqlansa foydasiz - saqlash, almashtirish va sizib chiqqanda nima qilish.
Ushbu bo‘lim mundarijasi
Oldingi o'n yetti bo'limda algoritmlarni to'g'ri ishlatishni o'rgandik. Endi eng zaif halqaga keldik.
Amaliy statistika oddiy: kriptografiya buzilmaydi, kalitlar sizib chiqadi.
Kalit qayerda bo'lmasligi kerak #
function korsat(string $nom, string $qiymat): void
{
printf("%-24s %s%s", $nom . ':', $qiymat, PHP_EOL);
}
$joylar = [
['Manba kodida', 'HALOKATLI', 'git tarixida abadiy qoladi'],
['Git ga qoshilgan .env', 'HALOKATLI', 'har bir nusxada bor'],
['Docker image ichida', 'JUDA YOMON', 'registrda hamma koradi'],
['Jurnal fayllarida', 'JUDA YOMON', 'markaziy jurnalga ketadi'],
['Xato hisobotlarida', 'JUDA YOMON', 'uchinchi tomon xizmatida'],
['Mijoz tomonida (JS)', 'HALOKATLI', 'har kim koradi'],
['Muhit ozgaruvchisida', 'qabul qilinadi', 'jarayon royxatida korinishi mumkin'],
['Huquqi 600 boigan faylda', 'yaxshi', 'faqat ilova foydalanuvchisi'],
['Sirlar boshqaruvchisida', 'ENG YAXSHI', 'audit, almashtirish, cheklov'],
['HSM yoki KMS da', 'ENG YAXSHI', 'kalit umuman chiqmaydi'],
];
printf("%-24s %-16s %s%s", 'QAYERDA', 'BAHO', 'IZOH', PHP_EOL);
echo str_repeat('-', 78), PHP_EOL;
foreach ($joylar as $j) {
printf("%-24s %-16s %s%s", ...[...$j, PHP_EOL]);
}
QAYERDA BAHO IZOH
------------------------------------------------------------------------------
Manba kodida HALOKATLI git tarixida abadiy qoladi
Git ga qoshilgan .env HALOKATLI har bir nusxada bor
Docker image ichida JUDA YOMON registrda hamma koradi
Jurnal fayllarida JUDA YOMON markaziy jurnalga ketadi
Xato hisobotlarida JUDA YOMON uchinchi tomon xizmatida
Mijoz tomonida (JS) HALOKATLI har kim koradi
Muhit ozgaruvchisida qabul qilinadi jarayon royxatida korinishi mumkin
Huquqi 600 boigan faylda yaxshi faqat ilova foydalanuvchisi
Sirlar boshqaruvchisida ENG YAXSHI audit, almashtirish, cheklov
HSM yoki KMS da ENG YAXSHI kalit umuman chiqmaydi
Kalit bir marta kommit qilinsa, uni oddiy o'chirish yetarli emas:
git rm .env
git commit -m "kalitni olib tashladim"
Bu kalitni tarixdan olib tashlamaydi. U hali ham:
| Qayerda qoladi | Kim ko'ra oladi |
|---|---|
| Eski kommitlarda | Repozitoriyga kirgan har kim |
| Barcha klonlarda | Har bir dasturchi |
| Fork larda | Ular ustidan nazorat yo'q |
| CI keshlarida | CI xizmati |
| GitHub kesh sahifalarida | Ba'zan hamma |
Tarixni tozalash (git filter-repo) mumkin, lekin u
barcha kommit xeshlarini o'zgartiradi va hamma klonni
buzadi.
To'g'ri javob bitta: kalitni darhol almashtiring. Tarixni tozalash - ikkinchi darajali ish.
Kalitni to'g'ri yuklash #
final class KalitOmbori
{
/** @var array<string, string> */
private array $kalitlar = [];
/** @param array<string, string> $muhit */
public function __construct(array $muhit)
{
foreach ($muhit as $nom => $qiymat) {
if (!str_starts_with($nom, 'APP_KEY_')) {
continue;
}
$baytlar = $this->tekshirVaOch($nom, $qiymat);
$this->kalitlar[substr($nom, 8)] = $baytlar;
}
}
private function tekshirVaOch(string $nom, string $qiymat): string
{
if ($qiymat === '') {
throw new RuntimeException("{$nom} bosh");
}
// Muhit o'zgaruvchisi matn, kalit esa baytlar - base64 orqali
$baytlar = base64_decode($qiymat, true);
if ($baytlar === false) {
throw new RuntimeException("{$nom} base64 emas");
}
if (strlen($baytlar) !== 32) {
throw new RuntimeException(
"{$nom} uzunligi " . strlen($baytlar) . " bayt, 32 kutilgan");
}
// Ochiq namunalar bilan chalkashmasin
if ($baytlar === str_repeat("\x00", 32)) {
throw new RuntimeException("{$nom} - nol kalit, sozlash unutilgan");
}
return $baytlar;
}
public function kalit(string $nom): string
{
return $this->kalitlar[$nom]
?? throw new RuntimeException("kalit topilmadi: {$nom}");
}
/** Jurnalga yozish uchun xavfsiz identifikator */
public function barmoqIzi(string $nom): string
{
return substr(bin2hex(hash_hmac('sha256', 'barmoq-izi',
$this->kalit($nom), true)), 0, 16);
}
/** @return list<string> */
public function nomlar(): array
{
return array_keys($this->kalitlar);
}
}
// Amalda: getenv() dan keladi. Bu yerda qat'iy namunalar.
$muhit = [
'APP_KEY_V1' => base64_encode(hex2bin(str_repeat('11', 32))),
'APP_KEY_V2' => base64_encode(hex2bin(str_repeat('22', 32))),
'DB_HOST' => '127.0.0.1', // e'tiborga olinmaydi
];
$ombor = new KalitOmbori($muhit);
korsat('yuklangan kalitlar', implode(', ', $ombor->nomlar()));
korsat('V1 barmoq izi', $ombor->barmoqIzi('V1'));
korsat('V2 barmoq izi', $ombor->barmoqIzi('V2'));
// Xato holatlari
foreach ([
'qisqa kalit' => ['APP_KEY_X' => base64_encode('qisqa')],
'base64 emas' => ['APP_KEY_X' => '!!! bu base64 emas !!!'],
'bosh' => ['APP_KEY_X' => ''],
'nol kalit' => ['APP_KEY_X' => base64_encode(str_repeat("\x00", 32))],
] as $nom => $yomon) {
try {
new KalitOmbori($yomon);
printf('%-14s OTDI - KUTILMAGAN%s', $nom, PHP_EOL);
} catch (RuntimeException $x) {
printf('%-14s RAD: %s%s', $nom, $x->getMessage(), PHP_EOL);
}
}
yuklangan kalitlar: V1, V2
V1 barmoq izi: b7c4de66026dc508
V2 barmoq izi: 93a2942d2a156842
qisqa kalit RAD: APP_KEY_X uzunligi 5 bayt, 32 kutilgan
base64 emas RAD: APP_KEY_X base64 emas
bosh RAD: APP_KEY_X bosh
nol kalit RAD: APP_KEY_X - nol kalit, sozlash unutilgan
Yuqoridagi tekshiruvlar ilova ishga tushganda bajariladi, so'rov paytida emas.
Bu muhim farq:
| Qachon tekshiriladi | Natija |
|---|---|
| So'rov paytida | Xato ishlab chiqarishda, foydalanuvchi oldida |
| Ishga tushishda | Ilova umuman ko'tarilmaydi - deploy to'xtaydi |
Ikkinchisi ancha yaxshi: noto'g'ri sozlangan versiya foydalanuvchiga yetib bormaydi.
Nol kalit tekshiruvi ham tasodifiy emas. Namuna
.env.example faylida ko'pincha APP_KEY= bo'sh yoki
nollar bilan to'ldirilgan bo'ladi va uni almashtirish
unutiladi.
Barmoq izi - kalitni oshkor qilmasdan aniqlash #
$ombor = new KalitOmbori([
'APP_KEY_V1' => base64_encode(hex2bin(str_repeat('11', 32))),
'APP_KEY_V2' => base64_encode(hex2bin(str_repeat('22', 32))),
]);
// Jurnalga bunday yozish MUMKIN
echo "[info] shifrlash kaliti: ", $ombor->barmoqIzi('V1'), PHP_EOL;
echo "[info] imzo kaliti: ", $ombor->barmoqIzi('V2'), PHP_EOL;
// Bunday yozish MUMKIN EMAS
echo PHP_EOL, 'XATO USUL (hech qachon qilmang):', PHP_EOL;
echo ' error_log("kalit: " . $kalit);', PHP_EOL;
echo ' var_dump($sozlama); // ichida kalit bor', PHP_EOL;
echo ' throw new Exception("kalit notogri: {$kalit}");', PHP_EOL;
[info] shifrlash kaliti: b7c4de66026dc508
[info] imzo kaliti: 93a2942d2a156842
XATO USUL (hech qachon qilmang):
error_log("kalit: " . $kalit);
var_dump($sozlama); // ichida kalit bor
throw new Exception("kalit notogri: {$kalit}");
Kalit almashtirish #
final class VersiyaliShifr
{
/** @param array<int, string> $kalitlar */
public function __construct(
private readonly array $kalitlar,
private readonly int $joriy,
) {
if (!isset($kalitlar[$joriy])) {
throw new InvalidArgumentException('joriy versiya kalitlari yoq');
}
}
public function shifrla(string $matn): string
{
$nonce = random_bytes(12);
$teg = '';
$shifr = openssl_encrypt($matn, 'aes-256-gcm',
$this->kalitlar[$this->joriy], OPENSSL_RAW_DATA, $nonce, $teg,
(string) $this->joriy); // versiya AAD ga ham kiradi
return $this->joriy . ':' . base64_encode($nonce . $teg . $shifr);
}
public function och(string $saqlangan): string
{
[$versiya, $b64] = explode(':', $saqlangan, 2);
$v = (int) $versiya;
if (!isset($this->kalitlar[$v])) {
throw new RuntimeException("kalit versiyasi {$v} mavjud emas");
}
$x = base64_decode($b64);
$matn = openssl_decrypt(substr($x, 28), 'aes-256-gcm',
$this->kalitlar[$v], OPENSSL_RAW_DATA,
substr($x, 0, 12), substr($x, 12, 16), $versiya);
if ($matn === false) {
throw new RuntimeException('ochib bolmadi');
}
return $matn;
}
public function eskirganmi(string $saqlangan): bool
{
return (int) explode(':', $saqlangan, 2)[0] !== $this->joriy;
}
}
$kalitlar = [
1 => hex2bin(str_repeat('11', 32)),
2 => hex2bin(str_repeat('22', 32)),
];
// Eski kalit joriy bo'lgan davr
$eski = new VersiyaliShifr($kalitlar, 1);
$yozuv = $eski->shifrla('Maxfiy hisobot');
korsat('versiya', explode(':', $yozuv)[0]);
// Kalit almashtirildi
$yangi = new VersiyaliShifr($kalitlar, 2);
korsat('eski yozuv ochildi', $yangi->och($yozuv));
korsat('eskirganmi', $yangi->eskirganmi($yozuv) ? 'ha' : 'yoq');
// O'qish paytida ko'chirish
$kochirilgan = $yangi->eskirganmi($yozuv)
? $yangi->shifrla($yangi->och($yozuv))
: $yozuv;
korsat('yangi versiya', explode(':', $kochirilgan)[0]);
korsat('mazmuni saqlandi', $yangi->och($kochirilgan));
versiya: 1
eski yozuv ochildi: Maxfiy hisobot
eskirganmi: ha
yangi versiya: 2
mazmuni saqlandi: Maxfiy hisobot
Bu 4-bo'limdagi password_needs_rehash bilan bir xil
g'oya:
$malumot = $shifr->och($yozuv);
if ($shifr->eskirganmi($yozuv)) {
$ombor->yangila($id, $shifr->shifrla($malumot));
}
Foydasi: butun bazani bir vaqtda qayta shifrlash kerak emas. Ma'lumot o'qilgan sari ko'chib boradi.
Kamchiligi: kamdan-kam o'qiladigan yozuvlar uzoq vaqt eski kalitda qoladi. Shuning uchun ma'lum muddatdan keyin qolganlarini fon jarayonida majburiy ko'chirish kerak.
Ikkalasini birga ishlatish eng amaliy yechim.
Kalit yo'qolganda nima bo'ladi #
$kalitlar = [2 => hex2bin(str_repeat('22', 32))]; // 1-versiya yo'q!
$shifr = new VersiyaliShifr($kalitlar, 2);
// 1-versiya bilan shifrlangan eski yozuv
$eskiYozuv = '1:' . base64_encode(random_bytes(64));
try {
$shifr->och($eskiYozuv);
} catch (RuntimeException $x) {
korsat('natija', 'RAD: ' . $x->getMessage());
}
echo PHP_EOL, 'Kalit yoqolsa - malumot ham YOQOLADI.', PHP_EOL;
echo 'Zaxira nusxa ham yordam bermaydi: u ham shifrlangan.', PHP_EOL;
natija: RAD: kalit versiyasi 1 mavjud emas
Kalit yoqolsa - malumot ham YOQOLADI.
Zaxira nusxa ham yordam bermaydi: u ham shifrlangan.
Ma'lumot zaxirasi va kalit zaxirasi bir joyda saqlanmasligi kerak:
| Bir joyda | Alohida |
|---|---|
| O'g'ri ikkalasini oladi | Faqat bittasi foyda bermaydi |
| Shifrlashning ma'nosi yo'qoladi | Himoya saqlanadi |
Lekin qarama-qarshi xavf ham bor: kalit zaxirasi yo'qolsa, ma'lumot butunlay yo'qoladi.
Amaliy yechim:
| Chora | Izoh |
|---|---|
| Kalitni kamida ikki mustaqil joyda saqlash | Bittasi yo'qolsa ikkinchisi qoladi |
| Tiklashni muntazam sinash | Zaxira ishlashini bilish |
| Kalitni bo'lish (secret sharing) | N dan K qismi yetarli |
| Qog'ozda, seyfda | Eng muhim kalitlar uchun |
Uchinchi variant Shamir sxemasi deb ataladi: kalit beshta qismga bo'linadi, tiklash uchun uchtasi yetarli. Hech kim yolg'iz kalitga ega bo'lmaydi.
Kod ichidagi sirlarni topish #
// Soddalashtirilgan qidiruvchi - kodda qolib ketgan sirlarni aniqlaydi
function sirlarniQidir(string $kod): array
{
$naqshlar = [
'AWS kaliti' => '/AKIA[0-9A-Z]{16}/',
'Ochiq maxfiy kalit' => '/-----BEGIN [A-Z ]*PRIVATE KEY-----/',
'Uzun hex kalit' => '/[\'"][0-9a-f]{64}[\'"]/',
'Qattiq yozilgan parol' => '/(parol|password|passwd)\s*=\s*[\'"][^\'"]{8,}[\'"]/i',
'Slack tokeni' => '/xox[baprs]-[0-9a-zA-Z-]{10,}/',
'Umumiy API kaliti' => '/(api[_-]?key|secret)\s*=\s*[\'"][^\'"]{16,}[\'"]/i',
];
$topilgan = [];
foreach ($naqshlar as $nom => $naqsh) {
if (preg_match($naqsh, $kod)) {
$topilgan[] = $nom;
}
}
return $topilgan;
}
$namunalar = [
'toza kod' => '$kalit = getenv("APP_KEY");',
'qattiq parol' => '$parol = "SuperMaxfiy123";',
'hex kalit' => '$k = "' . str_repeat('ab', 32) . '";',
'api kaliti' => '$api_key = "sk_live_abcdefghijklmnop";',
'pem kalit' => "-----BEGIN PRIVATE KEY-----\nMIIE...",
];
foreach ($namunalar as $nom => $kod) {
$t = sirlarniQidir($kod);
printf('%-14s %s%s', $nom,
$t === [] ? 'toza' : 'TOPILDI: ' . implode(', ', $t), PHP_EOL);
}
toza kod toza
qattiq parol TOPILDI: Qattiq yozilgan parol
hex kalit TOPILDI: Uzun hex kalit
api kaliti TOPILDI: Umumiy API kaliti
pem kalit TOPILDI: Ochiq maxfiy kalit
Yuqoridagi kod tamoyilni ko'rsatadi. Amalda tayyor vositalar ishlatiladi va ular kommitgacha ishga tushadi:
| Qayerda | Vosita turi |
|---|---|
| Dasturchi mashinasida | pre-commit ilgagi |
| CI quvurida | Sirlarni skanerlash bosqichi |
| Repozitoriy darajasida | Platformaning o'z skaneri |
| Tarixni tekshirish | Butun tarix bo'yicha skanerlash |
Eng samarali joy - birinchi qator: kommit umuman yaratilmasa, tarixni tozalash kerak bo'lmaydi.
Yolg'on ijobiy natijalar bo'ladi (test ma'lumotlari, namunalar). Ularni ro'yxatga qo'shish mumkin, lekin har istisnoni ko'rib chiqish kerak - aynan shu yerda haqiqiy sirlar yashirinib qoladi.
Kalitlarni ajratish #
// Bitta asosiy kalitdan hamma maqsad uchun kalitlar (6-bo'lim)
$asosiy = hex2bin(str_repeat('42', 32));
$maqsadlar = [
'sessiya-imzosi', 'fayl-shifri', 'vebxuk-imzosi',
'kuki-shifri', 'parol-qalampiri',
];
printf("%-18s %s%s", 'MAQSAD', 'BARMOQ IZI', PHP_EOL);
echo str_repeat('-', 38), PHP_EOL;
foreach ($maqsadlar as $m) {
$k = hash_hkdf('sha256', $asosiy, 32, 'v1:' . $m);
printf("%-18s %s%s", $m,
substr(bin2hex(hash('sha256', $k, true)), 0, 16), PHP_EOL);
}
echo PHP_EOL, 'Bitta kalit sizsa - faqat bitta maqsad zarar koradi', PHP_EOL;
MAQSAD BARMOQ IZI
--------------------------------------
sessiya-imzosi 99a000e9ba95ac2a
fayl-shifri d388387f84356e95
vebxuk-imzosi d875500a33e07732
kuki-shifri 02579e8585bf8278
parol-qalampiri a39a81bed34201e0
Bitta kalit sizsa - faqat bitta maqsad zarar koradi
Tekshiruv ro'yxati #
| Savol | Javob "yo'q" bo'lsa |
|---|---|
| Kalitlar kodda yo'qmi | Darhol almashtiring |
.env gitignore dami | Tekshiring va tarixni skanerlang |
| Har muhitda alohida kalitmi | Ajrating (dev / test / prod) |
| Kalit uzunligi tekshiriladimi | Ishga tushish tekshiruvini qo'shing |
| Jurnalga kalit tushmaydimi | Barmoq iziga o'ting |
| Almashtirish sinab ko'rilganmi | Sinab ko'ring - u ishlamasligi mumkin |
| Kalit zaxirasi bormi | Yarating va tiklashni sinang |
| Sizib chiqish rejasi bormi | Yozing - hodisa paytida vaqt bo'lmaydi |
- Kalit saqlanmasligi kerak bo'lgan beshta joyni sanang.
- Nima uchun
git rmkalitni tarixdan olib tashlamasligini tushuntiring. - Muhit o'zgaruvchisidan kalit yuklaydigan sinf yozing.
- To'rtta xato holatini (qisqa, base64 emas, bo'sh, nol) tekshiring.
- Kalit barmoq izini hisoblang va uni jurnalga yozing.
- Versiyali shifrlash sinfini yozing.
- Kalitni almashtiring va eski yozuvni yangi versiyaga ko'chiring.
- Kalit yo'qolgan holatni modellashtiring.
- Kodda qolib ketgan sirlarni topuvchi oddiy skaner yozing.
- Tekshiruv ro'yxatini o'z loyihangizga qo'llang.
Xulosa #
- Kriptografiya buzilmaydi, kalitlar sizib chiqadi.
- Kalit hech qachon kodda, git da, jurnalda yoki mijoz tomonida bo'lmasligi kerak.
git rmkalitni tarixdan olib tashlamaydi - yagona to'g'ri javob almashtirish.- Kalitni ishga tushishda tekshiring: uzunlik, format, namuna qiymat emasligi.
- Jurnalga kalitning o'zini emas, barmoq izini yozing.
- Almashtirish - jarayon: yangi versiya joriy bo'ladi, eskisi ochish uchun qoladi.
- O'qish paytida ko'chirish butun bazani bir vaqtda qayta shifrlashdan qutqaradi.
- Kalit yo'qolsa - ma'lumot ham yo'qoladi; zaxira nusxa yordam bermaydi.
- Kalit zaxirasi ma'lumot zaxirasidan alohida saqlanadi, lekin u ham bo'lishi shart.
- Sirlarni skanerlash kommitgacha ishlashi kerak - keyin kech bo'ladi.
Keyingi bo'limda barcha bo'limlardagi xatolarni bir joyga to'playmiz.
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.