Chepuhagram/srv/site/about-secutity/index.html

782 lines
38 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Архитектура Безопасности и Шифрования — Чепухаграм</title>
<meta name="description" content="Подробный разбор сквозного шифрования (E2EE) мессенджера Чепухаграм. Узнайте об использовании протоколов X25519, AES-256-GCM, PBKDF2 и поблочной защите медиафайлов.">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #0b071a;
--bg-card: #14102d;
--accent-color: #7c4dff;
--accent-light: #9e75ff;
--accent-glow: rgba(124, 77, 255, 0.3);
--text-primary: #ffffff;
--text-secondary: #94a3b8;
--glass-bg: rgba(255, 255, 255, 0.03);
--glass-border: rgba(255, 255, 255, 0.08);
--code-bg: #110c26;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
overflow-x: hidden;
line-height: 1.75;
}
h1, h2, h3, h4, .logo {
font-family: 'Outfit', sans-serif;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-color);
}
::-webkit-scrollbar-thumb {
background: var(--glass-border);
border-radius: 4px;
transition: background 0.3s;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-color);
}
.container {
width: 100%;
max-width: 1000px;
margin: 0 auto;
padding: 0 24px;
}
/* Header Navigation */
header {
background: rgba(11, 7, 26, 0.8);
backdrop-filter: blur(16px);
border-bottom: 1px solid var(--glass-border);
padding: 16px 0;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
}
.logo-dot {
width: 8px;
height: 8px;
background-color: var(--accent-color);
border-radius: 50%;
box-shadow: 0 0 10px var(--accent-color);
}
.btn-back {
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: color 0.3s;
display: flex;
align-items: center;
gap: 6px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
padding: 8px 16px;
border-radius: 20px;
}
.btn-back:hover {
color: var(--text-primary);
border-color: rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.06);
}
/* Hero Section */
.hero {
padding: 80px 0 40px 0;
background: radial-gradient(circle at 50% -20%, rgba(124, 77, 255, 0.12) 0%, transparent 60%);
}
.hero h1 {
font-size: 46px;
font-weight: 700;
margin-bottom: 16px;
line-height: 1.2;
background: linear-gradient(135deg, #ffffff 40%, #d1c4e9 100%);
-webkit-text-fill-color: transparent;
}
.hero-meta {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 40px;
}
/* Article Content */
.content-section {
padding-bottom: 80px;
}
.intro-text {
font-size: 18px;
font-weight: 300;
color: #e2e8f0;
margin-bottom: 40px;
line-height: 1.8;
}
h2 {
font-size: 28px;
font-weight: 700;
margin: 54px 0 24px 0;
border-left: 4px solid var(--accent-color);
padding-left: 16px;
color: #f8fafc;
}
h3 {
font-size: 20px;
font-weight: 600;
margin: 32px 0 16px 0;
color: #f1f5f9;
}
p {
margin-bottom: 24px;
color: #cbd5e1;
}
ul, ol {
margin-bottom: 28px;
padding-left: 24px;
color: #cbd5e1;
}
li {
margin-bottom: 10px;
}
/* Tech Spec Grid */
.spec-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin: 40px 0;
}
.spec-card {
background: var(--bg-card);
border: 1px solid var(--glass-border);
border-radius: 16px;
padding: 28px 24px;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
transition: transform 0.3s;
}
.spec-card:hover {
transform: translateY(-4px);
}
.spec-card .icon {
font-size: 32px;
margin-bottom: 16px;
display: inline-block;
}
.spec-card h3 {
font-size: 16px;
color: var(--text-primary);
margin: 0 0 8px 0;
font-weight: 600;
}
.spec-card p {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 0;
line-height: 1.5;
}
/* Cryptographic Exchange Diagram (CSS representation) */
.dh-diagram {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-card);
border: 1px solid var(--glass-border);
border-radius: 24px;
padding: 36px 30px;
margin: 40px 0;
gap: 20px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
}
.dh-entity {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
width: 32%;
}
.dh-avatar {
width: 54px;
height: 54px;
background: linear-gradient(135deg, var(--accent-color), #b388ff);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 15px;
box-shadow: 0 4px 15px var(--accent-glow);
}
.dh-avatar.bob {
background: linear-gradient(135deg, #00b0ff, #00e5ff);
box-shadow: 0 4px 15px rgba(0, 176, 255, 0.3);
}
.dh-key {
padding: 8px 12px;
border-radius: 8px;
font-size: 11px;
text-align: center;
width: 100%;
font-weight: 500;
font-family: 'Fira Code', monospace;
}
.dh-key.private {
background: rgba(239, 83, 80, 0.12);
border: 1px solid rgba(239, 83, 80, 0.25);
color: #ef5350;
}
.dh-key.public {
background: rgba(76, 217, 100, 0.12);
border: 1px solid rgba(76, 217, 100, 0.25);
color: #4cd964;
}
.dh-key.shared {
background: rgba(124, 77, 255, 0.15);
border: 1px solid rgba(124, 77, 255, 0.3);
color: #b388ff;
font-size: 12px;
font-weight: 600;
}
.dh-channel {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
font-size: 11px;
color: var(--text-secondary);
}
.dh-arrow {
background: rgba(255, 255, 255, 0.04);
padding: 6px 12px;
border-radius: 20px;
border: 1px solid var(--glass-border);
width: 100%;
text-align: center;
font-weight: 500;
}
/* File Block Stream Encryption Diagram */
.block-stream-diagram {
display: flex;
flex-direction: column;
align-items: center;
background: var(--bg-card);
border: 1px solid var(--glass-border);
border-radius: 24px;
padding: 36px 30px;
margin: 40px 0;
gap: 20px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
}
.stream-file {
background: linear-gradient(135deg, var(--accent-color) 0%, #5e35b1 100%);
padding: 12px 36px;
border-radius: 12px;
font-weight: 600;
box-shadow: 0 4px 15px var(--accent-glow);
font-size: 14px;
}
.stream-split {
font-size: 12px;
color: var(--text-secondary);
font-style: italic;
}
.stream-blocks {
display: flex;
gap: 16px;
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
.stream-block {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--glass-border);
border-radius: 14px;
padding: 18px;
flex: 1;
min-width: 220px;
text-align: center;
}
.stream-block.tail {
border-style: dashed;
border-color: rgba(255, 255, 255, 0.2);
}
.block-title {
font-size: 13px;
font-weight: 600;
display: block;
margin-bottom: 8px;
color: #fff;
}
.block-crypto {
font-size: 11px;
color: var(--accent-light);
margin-bottom: 10px;
font-weight: 500;
}
.block-result {
font-size: 11px;
color: var(--text-secondary);
background: rgba(0, 0, 0, 0.25);
padding: 8px 10px;
border-radius: 8px;
line-height: 1.4;
font-family: 'Fira Code', monospace;
}
/* Encryption Steps Visualizer */
.steps {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 24px;
padding: 40px;
margin: 40px 0;
position: relative;
}
.step-item {
display: flex;
gap: 24px;
margin-bottom: 32px;
position: relative;
}
.step-item:last-child {
margin-bottom: 0;
}
.step-item:not(:last-child)::after {
content: '';
position: absolute;
top: 36px;
left: 18px;
width: 2px;
height: calc(100% - 4px);
background: var(--glass-border);
}
.step-num {
width: 38px;
height: 38px;
background: var(--accent-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 15px;
flex-shrink: 0;
box-shadow: 0 0 10px var(--accent-glow);
}
.step-content h3 {
font-size: 18px;
margin: 0 0 8px 0;
font-weight: 600;
}
.step-content p {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 0;
}
/* Code Block Container */
.code-container {
background: var(--code-bg);
border: 1px solid var(--glass-border);
border-radius: 16px;
padding: 24px;
margin: 32px 0;
overflow-x: auto;
position: relative;
}
.code-container::before {
content: 'Криптографический Стек';
position: absolute;
top: 8px;
right: 16px;
font-size: 10px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 1px;
}
code {
font-family: 'Fira Code', monospace;
font-size: 13px;
color: #e2e8f0;
}
/* Callout Box */
.callout {
background: rgba(124, 77, 255, 0.08);
border-left: 4px solid var(--accent-color);
padding: 20px 24px;
border-radius: 0 16px 16px 0;
margin: 32px 0;
}
.callout p {
margin-bottom: 0;
font-size: 14px;
color: #e2e8f0;
}
/* Footer */
footer {
border-top: 1px solid var(--glass-border);
padding: 45px 0;
text-align: center;
background: #06040e;
}
.footer-logo {
font-size: 18px;
font-weight: 700;
color: white;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
@media (max-width: 768px) {
.dh-diagram {
flex-direction: column;
gap: 30px;
padding: 30px 20px;
}
.dh-entity {
width: 100%;
}
.dh-channel {
width: 100%;
}
.block-stream-diagram {
padding: 30px 20px;
}
.stream-blocks {
flex-direction: column;
}
.hero h1 {
font-size: 34px;
}
}
</style>
</head>
<body>
<!-- Header Navigation -->
<header>
<div class="container header-content">
<a href="../" class="logo" id="headerLogo">
<span class="logo-dot"></span>Чепухаграм
</a>
<a href="../" class="btn-back" id="backLink">
На главную
</a>
</div>
</header>
<!-- Hero Header -->
<section class="hero">
<div class="container">
<h1 id="titleHeader">Безопасность, Сквозное Шифрование и Архитектура Системы</h1>
<div class="hero-meta" id="metaDate">Обновлено: Июнь 2026 • Документация Чепухаграм</div>
<p class="intro-text" id="introDesc">В мессенджере Чепухаграм конфиденциальность переписки обеспечивается строгими математическими законами. Никакие третьи лица, включая разработчика и администратора сервера, не могут получить доступ к содержимому ваших чатов. Данный документ подробно описывает криптографический стек, жизненный цикл сообщений и механизмы синхронизации данных.</p>
</div>
</section>
<!-- Main Content -->
<div class="container content-section">
<!-- Technical Specifications -->
<div class="spec-grid" id="specGrid">
<div class="spec-card">
<span class="icon">🔑</span>
<h3>X25519 (ECDH)</h3>
<p>Согласование общего ключа на базе эллиптических кривых (Curve25519) без передачи секрета по сети.</p>
</div>
<div class="spec-card">
<span class="icon">⚙️</span>
<h3>AES-256-GCM</h3>
<p>Симметричное шифрование блоков данных с проверкой целостности и подлинности (AEAD).</p>
</div>
<div class="spec-card">
<span class="icon">🔒</span>
<h3>PBKDF2-HMAC-SHA256</h3>
<p>Криптографическая деривация ключей из мастер-пароля с использованием 600,000 итераций.</p>
</div>
<div class="spec-card">
<span class="icon">📡</span>
<h3>WebSockets & Drift DB</h3>
<p>Реалтайм-доставка сообщений и локальное структурированное хранение истории в зашифрованном виде.</p>
</div>
</div>
<h2 id="e2eeHeader">1. Сквозное шифрование (End-to-End Encryption)</h2>
<p>С сквозным шифрованием (E2EE) ваши сообщения кодируются непосредственно перед отправкой на вашем устройстве и могут быть декодированы только на устройстве получателя. Серверная часть мессенджера Чепухаграм полностью изолирована от ключевой информации. Сервер функционирует исключительно как маршрутизатор зашифрованных бинарных пакетов и не способен восстановить исходный текст.</p>
<!-- ECDH Exchange Visual Diagram -->
<h3>Схема согласования ключей (X25519 / ECDH)</h3>
<p>Для создания защищенного канала используется протокол Диффи-Хеллмана на эллиптических кривых. Ниже представлена схема независимого вычисления общего секрета:</p>
<div class="dh-diagram">
<div class="dh-entity">
<div class="dh-avatar">Вы</div>
<div class="dh-key private">Ваш приватный ключ A (dA)</div>
<div class="dh-key public">Ваш публичный ключ A (QA = dA * G)</div>
<div class="dh-key shared">Общий секрет (K = dA * QB)</div>
</div>
<div class="dh-channel">
<div class="dh-arrow">публичный ключ A →</div>
<div class="dh-arrow">← публичный ключ B</div>
<p style="margin: 8px 0 0 0; text-align: center; font-size: 11px;">(сервер транслирует только открытые ключи)</p>
</div>
<div class="dh-entity">
<div class="dh-avatar bob">Собеседник</div>
<div class="dh-key private">Приватный ключ B (dB)</div>
<div class="dh-key public">Публичный ключ B (QB = dB * G)</div>
<div class="dh-key shared">Общий секрет (K = dB * QA)</div>
</div>
</div>
<div class="steps" id="stepsSection">
<div class="step-item">
<div class="step-num">1</div>
<div class="step-content">
<h3>Генерация локальной пары ключей</h3>
<p>При первом входе или настройке аккаунта приложение генерирует на устройстве пару асимметричных ключей X25519. Приватный ключ надёжно сохраняется во внутреннем изолированном хранилище ОС (Secure Storage / KeyStore / Keychain), а публичный отправляется в базу данных сервера для обеспечения доступности вашего контакта другим пользователям.</p>
</div>
</div>
<div class="step-item">
<div class="step-num">2</div>
<div class="step-content">
<h3>Вычисление общего секрета (Shared Secret)</h3>
<p>Когда вы открываете чат с пользователем, клиентское приложение автоматически запрашивает публичный ключ собеседника с сервера. Используя его совместно со своим приватным ключом, алгоритм ECDH вычисляет уникальный симметричный ключ (общий секрет). Он никогда не передаётся по сети — обе стороны получают абсолютно одинаковый ключ математическим путём.</p>
</div>
</div>
<div class="step-item">
<div class="step-num">3</div>
<div class="step-content">
<h3>Симметричное шифрование AES-256-GCM</h3>
<p>Полученный общий секрет передается в алгоритм шифрования AES-256 в режиме Galois/Counter Mode. Каждое сообщение шифруется с использованием уникального вектора инициализации (Nonce), что делает невозможным проведение атак на основе повторяющихся шифротекстов.</p>
</div>
</div>
</div>
<h2 id="messagesCryptoHeader">2. Шифрование и деривация текстовых сообщений</h2>
<p>Каждое текстовое сообщение шифруется с генерацией случайного 12-байтового вектора инициализации (IV / Nonce). Режим GCM гарантирует аутентифицированное шифрование (AEAD): к шифротексту добавляется 16-байтовый тег аутентификации (MAC), подтверждающий, что данные не были изменены при транзите через сервер.</p>
<p>Итоговая структура пакета сообщения на сервере выглядит следующим образом:</p>
<div class="block-result" style="margin-bottom: 24px; text-align: center;">
[Nonce (12 байт)] + [MAC Тег (16 байт)] + [Шифротекст (N байт)] => Кодирование в Base64
</div>
<p>Пример реализации алгоритмов шифрования и расшифрования в приложении на Dart с использованием криптографических библиотек:</p>
<div class="code-container" id="messageCode">
<pre><code>// Шифрование строки текста на общем ключе
Future&lt;String&gt; encryptMessage(String plainText, SecretKey sharedKey) async {
final algorithm = AesGcm.with256bits();
final clearTextBytes = utf8.encode(plainText);
// Генерация случайного вектора инициализации (Nonce)
final secretBox = await algorithm.encrypt(
clearTextBytes,
secretKey: sharedKey,
);
// Объединяем nonce, mac-тег и зашифрованные байты в один пакет
final combinedBytes = BytesBuilder()
..add(secretBox.nonce)
..add(secretBox.mac.bytes)
..add(secretBox.cipherText);
return base64Encode(combinedBytes.toBytes());
}
// Дешифрование сообщения на клиенте
Future&lt;String&gt; decryptMessage(String base64Data, SecretKey sharedKey) async {
final rawData = base64Decode(base64Data);
if (rawData.length < 28) throw Exception("Пакет данных слишком мал");
final nonce = rawData.sublist(0, 12);
final mac = rawData.sublist(12, 28);
final cipherText = rawData.sublist(28);
final algorithm = AesGcm.with256bits();
final decryptedBytes = await algorithm.decrypt(
SecretBox(cipherText, nonce: nonce, mac: Mac(mac)),
secretKey: sharedKey,
);
return utf8.decode(decryptedBytes);
}</code></pre>
</div>
<h2 id="filesCryptoHeader">3. Поблочное шифрование медиафайлов (Крипто-стриминг)</h2>
<p>Шифрование больших файлов целиком в оперативной памяти мобильных устройств приводит к её переполнению и сбоям. Кроме того, это лишает возможности начать воспроизведение видео/аудио до окончания полной загрузки файла. В мессенджере Чепухаграм внедрена технология <strong>поблочного крипто-стриминга</strong>:</p>
<ol>
<li><strong>Генерация ключа файла:</strong> Для каждого медиафайла генерируется индивидуальный случайный 256-битный ключ (<code>fileKey</code>). Это позволяет безопасно делиться ключом конкретного файла, не компрометируя общий секрет чата.</li>
<li><strong>Шифрование ключа:</strong> Ключ <code>fileKey</code> зашифровывается с помощью AES-GCM на основном общем ключе чата (<code>sharedSecret</code>). Этот зашифрованный ключ прикрепляется к сообщению как поле <code>encrypted_key</code>.</li>
<li><strong>Поблочное чтение и шифрование:</strong> Исходный файл считывается потоком частями по 64 КБ. Каждый блок шифруется алгоритмом AES-256-GCM независимо.</li>
<li><strong>Форматирование блоков:</strong> К зашифрованным блокам добавляется структура:
<div class="block-result" style="margin: 12px 0; text-align: center;">
[Длина блока (4 байта)] + [Nonce (12 байт)] + [Зашифрованные данные (64 КБ)] + [Тег аутентификации MAC (16 байт)]
</div>
</li>
<li><strong>Передача на Google Drive:</strong> Зашифрованный поток отправляется напрямую в облачное хранилище через сервер. Сервер имеет доступ только к зашифрованному потоку байтов и не знает ключ <code>fileKey</code>.</li>
</ol>
<!-- Media block stream diagram -->
<div class="block-stream-diagram">
<div class="stream-file">Исходный медиафайл (Изображение, Видео, Голосовая заметка)</div>
<div class="stream-split">👇 Разделение на чанки в потоке по 64 КБ</div>
<div class="stream-blocks">
<div class="stream-block">
<span class="block-title">Блок 1 (64 КБ)</span>
<div class="block-crypto">🔒 AES-256-GCM (на ключе fileKey)</div>
<div class="block-result">Длина (4б) + Nonce (12б) + Данные + MAC (16б)</div>
</div>
<div class="stream-block">
<span class="block-title">Блок 2 (64 КБ)</span>
<div class="block-crypto">🔒 AES-256-GCM (на ключе fileKey)</div>
<div class="block-result">Длина (4б) + Nonce (12б) + Данные + MAC (16б)</div>
</div>
<div class="stream-block tail">
<span class="block-title">Хвостовой Блок (&lt; 64 КБ)</span>
<div class="block-crypto">🔒 AES-256-GCM (на ключе fileKey)</div>
<div class="block-result">Длина (4б) + Nonce (12б) + Данные + MAC (16б)</div>
</div>
</div>
</div>
<div class="callout" id="fileSecurityNote">
<p><strong>Преимущество стриминга:</strong> При скачивании получатель расшифровывает блоки на лету по мере их поступления по сети. Плеер приложения начинает воспроизведение видео- или аудиофайла мгновенно, не дожидаясь полной загрузки сотен мегабайт с Google Drive.</p>
</div>
<h2 id="masterPassHeader">4. Защита ключей и облачное резервное копирование (Backup)</h2>
<p>Потеря устройства или очистка приложения без резервной копии привела бы к безвозвратной потере всей переписки, так как приватный ключ X25519 существует в единственном экземпляре на смартфоне. Для решения этой проблемы Чепухаграм реализует безопасную схему резервного копирования ключей:</p>
<ul>
<li><strong>Мастер-пароль:</strong> Пользователь задаёт сложный мастер-пароль при регистрации или первой инициализации профиля. Мастер-пароль не хранится на сервере ни в каком виде.</li>
<li><strong>Деривация PBKDF2:</strong> Из пароля и предопределённой криптографической соли вычисляется производный ключ шифрования резервной копии. Используется стандарт PBKDF2 с хэш-функцией HMAC-SHA256 и <strong>600,000 итерациями</strong>. Такое число итераций делает невозможным брутфорс (перебор паролей) на графических процессорах и специализированных чипах.</li>
<li><strong>Шифрование приватного ключа:</strong> Локальный приватный ключ X25519 шифруется с помощью AES-256-GCM на деривированном ключе PBKDF2.</li>
<li><strong>Сохранение копии:</strong> Зашифрованная строка приватного ключа (<code>encrypted_private_key</code>) отправляется на сервер. При авторизации на новом устройстве пользователь вводит свой мастер-пароль, приложение заново производит 600,000 итераций деривации ключа, скачивает защищённый контейнер с сервера и успешно расшифровывает приватный ключ локально.</li>
</ul>
<h2 id="sessionSecurityHeader">5. Управление сессиями и синхронизация (Multi-Device Sync)</h2>
<p>Чепухаграм спроектирован для бесшовной работы на нескольких устройствах одного пользователя. Это требует сложной логики синхронизации сообщений, прочтений и статусов через реалтайм WebSocket-каналы и локальную СУБД (Drift SQLite):</p>
<h3>Доставка событий о прочтении (Read Receipts)</h3>
<p>При чтении переписки на одном устройстве, состояние непрочитанных сообщений синхронизируется на всех остальных девайсах пользователя с точностью до чата:</p>
<ul>
<li>Когда пользователь открывает чат или прокручивает его в самый низ (где виден последний элемент), клиент отправляет на сервер WebSocket-событие <code>read_all_chat</code> с указанием ID контакта.</li>
<li>Сервер обновляет статус всех непрочитанных сообщений в базе данных и рассылает WebSocket-пакеты <code>all_chat_read</code> всем активным сессиям (устройствам) читателя и автора сообщений.</li>
<li>Все устройства читателя мгновенно обновляют локальную базу данных сообщений, а также сбрасывают счётчик непрочитанных сообщений конкретного чата в провайдере `ContactProvider` в <code>0</code>, очищая бейджи непрочитанных в списке чатов.</li>
<li>Если сообщения прочитываются поштучно (по мере попадания в зону видимости экрана), отправляется событие <code>read_receipt</code>. Сервер вычисляет количество оставшихся непрочитанных сообщений **строго в рамках данного чата** и рассылает его устройствам, предотвращая некорректное суммирование глобального счётчика.</li>
</ul>
<h3>Смена мастер-пароля шифрования секретного ключа и отзыв авторизации (Force Logout)</h3>
<p>Выход из сессий на других устройствах происходит исключительно при смене мастер-пароля шифрования секретного ключа (или при полном сбросе ключей). Это гарантирует своевременное прекращение доступа к аккаунту при изменении ключевого секрета:</p>
<ol>
<li>Во время изменения мастер-пароля шифрования приватного ключа сервер определяет идентификатор сессии (<code>session_id</code>) устройства, инициировавшего операцию.</li>
<li>Сервер находит и удаляет все остальные активные сессии этого пользователя из таблицы <code>sessions</code> базы данных.</li>
<li>Для каждой отключенной сессии бэкенд вызывает метод <code>kill_session_socket</code> в менеджере WebSocket-соединений.</li>
<li>На эти устройства отправляется WebSocket-пакет <code>{"type": "session_terminated"}</code>, после чего сокеты принудительно закрываются.</li>
<li>Устройства, получившие данный сигнал, немедленно удаляют локальный файл базы данных сообщений (<code>chat_app.db</code>), токены авторизации, приватный ключ и возвращаются на экран входа.</li>
</ol>
<div class="callout" style="background: rgba(239, 83, 80, 0.05); border-left-color: #ef5350;">
<p><strong>Безопасность при выходе:</strong> При логауте база данных SQLite на компьютере или телефоне полностью стирается на уровне файловой системы. Это исключает возможность физического извлечения переписки с диска устройства после выхода из учётной записи.</p>
</div>
</div>
<!-- Footer -->
<footer>
<div class="container">
<a href="../" class="footer-logo">
<span class="logo-dot"></span>Чепухаграм
</a>
<p class="footer-text">Документ подготовлен разработчиком Чепухаграм. Все алгоритмы и протоколы шифрования соответствуют спецификациям современных стандартов безопасности RFC 7748 и NIST SP 800-38D.</p>
</div>
</footer>
</body>
</html>