782 lines
38 KiB
HTML
782 lines
38 KiB
HTML
<!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<String> 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<String> 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">Хвостовой Блок (< 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>
|