21-06-2026+22-50
This commit is contained in:
parent
4363fdf699
commit
680771f75e
|
|
@ -60,24 +60,33 @@ class CryptoService {
|
|||
String encryptedPrivateKey,
|
||||
String masterPassword,
|
||||
) async {
|
||||
final encryptedData = base64Decode(encryptedPrivateKey);
|
||||
|
||||
// Разделяем nonce и зашифрованные данные
|
||||
final nonce = encryptedData.sublist(0, 12); // GCM nonce = 12 bytes
|
||||
final macBytes = encryptedData.sublist(12, 28);
|
||||
final cipherText = encryptedData.sublist(28);
|
||||
|
||||
// 1. Попытка дешифрации с новым значением итераций (600 000)
|
||||
try {
|
||||
final encryptedData = base64Decode(encryptedPrivateKey);
|
||||
|
||||
// Разделяем nonce и зашифрованные данные
|
||||
final nonce = encryptedData.sublist(0, 12); // GCM nonce = 12 bytes
|
||||
final macBytes = encryptedData.sublist(12, 28);
|
||||
final cipherText = encryptedData.sublist(28);
|
||||
|
||||
final masterKey = await _deriveKeyFromPassword(masterPassword);
|
||||
|
||||
final masterKey = await _deriveKeyFromPassword(masterPassword, iterations: 600000);
|
||||
final decrypted = await aesGcm.decrypt(
|
||||
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
|
||||
secretKey: masterKey,
|
||||
);
|
||||
|
||||
return base64Encode(decrypted);
|
||||
} catch (e) {
|
||||
throw Exception('Неверный мастер-пароль или поврежденные данные');
|
||||
} catch (_) {
|
||||
// 2. Если не удалось, пробуем старое значение (10 000) для обратной совместимости
|
||||
try {
|
||||
final masterKey = await _deriveKeyFromPassword(masterPassword, iterations: 10000);
|
||||
final decrypted = await aesGcm.decrypt(
|
||||
SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)),
|
||||
secretKey: masterKey,
|
||||
);
|
||||
return base64Encode(decrypted);
|
||||
} catch (e) {
|
||||
throw Exception('Неверный мастер-пароль или поврежденные данные');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,10 +107,10 @@ class CryptoService {
|
|||
return base64Encode(encryptedData);
|
||||
}
|
||||
|
||||
Future<SecretKey> _deriveKeyFromPassword(String password) async {
|
||||
Future<SecretKey> _deriveKeyFromPassword(String password, {int iterations = 600000}) async {
|
||||
final pbkdf2 = Pbkdf2(
|
||||
macAlgorithm: Hmac.sha256(),
|
||||
iterations: 10000,
|
||||
iterations: iterations,
|
||||
bits: 256,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -489,6 +489,11 @@ class AuthProvider extends ChangeNotifier {
|
|||
|
||||
// Метод для начала с чистого листа (новые ключи)
|
||||
Future<void> resetKeys() async {
|
||||
try {
|
||||
await ApiService().clearOtherSessions();
|
||||
} catch (e) {
|
||||
print("Error clearing other sessions on key reset: $e");
|
||||
}
|
||||
await _storage.delete(key: 'private_key');
|
||||
try {
|
||||
final allKeys = await _storage.readAll();
|
||||
|
|
|
|||
|
|
@ -1535,7 +1535,7 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
|||
sharedSecret,
|
||||
);
|
||||
|
||||
final content50 = newText.length > 50 ? newText.substring(0, 50) : newText;
|
||||
final content50 = newText.length > 500 ? newText.substring(0, 500) : newText;
|
||||
final encryptedContent50 = await _cryptoService.encryptMessage(
|
||||
content50,
|
||||
sharedSecret,
|
||||
|
|
@ -2726,7 +2726,7 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
|||
);
|
||||
|
||||
String previewText = rawText.isNotEmpty ? rawText : "[Фото]";
|
||||
if (previewText.length > 50) previewText = previewText.substring(0, 50);
|
||||
if (previewText.length > 500) previewText = previewText.substring(0, 500);
|
||||
encryptedContent50 = await _cryptoService.encryptMessage(
|
||||
previewText,
|
||||
sharedSecret,
|
||||
|
|
|
|||
|
|
@ -175,6 +175,12 @@ class _SecuritySettingsScreenState extends State<SecuritySettingsScreen> {
|
|||
);
|
||||
if (!success) throw Exception('Сервер отклонил обновление крипто-ключа.');
|
||||
|
||||
try {
|
||||
await ApiService().clearOtherSessions();
|
||||
} catch (e) {
|
||||
print("Error clearing other sessions on encryption password change: $e");
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
|
|
@ -509,6 +515,30 @@ class _SecuritySettingsScreenState extends State<SecuritySettingsScreen> {
|
|||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Предупреждение: при изменении пароля шифрования все остальные активные сессии на других устройствах будут автоматически завершены.',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ async def send_system_notification(db: AsyncSession, receiver_id: int, plain_tex
|
|||
# 2. Шифруем текст сообщения, используя сохраненный ключ
|
||||
encrypted_content = encrypt_system_message(plain_text, u_public_key)
|
||||
|
||||
preview_text = plain_text if len(plain_text) <= 50 else f"{plain_text[:47]}..."
|
||||
preview_text = plain_text if len(plain_text) <= 500 else f"{plain_text[:497]}..."
|
||||
encrypted_preview = encrypt_system_message(preview_text, u_public_key)
|
||||
|
||||
# 3. Записываем сообщение в историю базы данных
|
||||
|
|
|
|||
|
|
@ -0,0 +1,719 @@
|
|||
<!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-background-clip: text;
|
||||
-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: 'Dart / PointyCastle / Cryptography';
|
||||
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="../index.html" class="logo" id="headerLogo">
|
||||
<span class="logo-dot"></span>Чепухаграм
|
||||
</a>
|
||||
<a href="../index.html" class="btn-back" id="backLink">
|
||||
← На главную
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero Header -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1 id="titleHeader">Безопасность и сквозное шифрование (E2EE)</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</h3>
|
||||
<p>Протокол Диффи-Хеллмана для генерации общего ключа (ECDH)</p>
|
||||
</div>
|
||||
<div class="spec-card">
|
||||
<span class="icon">⚙️</span>
|
||||
<h3>AES-256-GCM</h3>
|
||||
<p>Симметричное шифрование с проверкой целостности данных</p>
|
||||
</div>
|
||||
<div class="spec-card">
|
||||
<span class="icon">🔒</span>
|
||||
<h3>PBKDF2</h3>
|
||||
<p>Криптографическая деривация ключей на базе мастер-пароля (600,000 ит.)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="e2eeHeader">Принцип сквозного шифрования (E2EE)</h2>
|
||||
<p>Шифрование «end-to-end» означает, что шифрование информации происходит непосредственно на устройстве отправителя, а дешифрование — только на устройстве получателя. Серверная часть выполняет лишь функцию почтальона — пересылает зашифрованные пакеты байт, не имея ключей для их расшифровки.</p>
|
||||
|
||||
<!-- ECDH Exchange Visual Diagram -->
|
||||
<h3>Схема согласования ключей (X25519)</h3>
|
||||
<div class="dh-diagram">
|
||||
<div class="dh-entity">
|
||||
<div class="dh-avatar">Вы</div>
|
||||
<div class="dh-key private">Приватный ключ A</div>
|
||||
<div class="dh-key public">Публичный ключ A</div>
|
||||
<div class="dh-key shared">Общий секрет (K)</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</div>
|
||||
<div class="dh-key public">Публичный ключ B</div>
|
||||
<div class="dh-key shared">Общий секрет (K)</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 (приватный и публичный). Публичный ключ отправляется на сервер для того, чтобы другие пользователи могли начать с вами чат.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-content">
|
||||
<h3>Деривация общего секрета (Shared Secret)</h3>
|
||||
<p>Когда вы открываете чат, приложение берет ваш приватный ключ X25519 и публичный ключ собеседника, вычисляя общий секрет по алгоритму ECDH. Этот секретный ключ никогда не передается по сети — обе стороны вычисляют его независимо на своих девайсах.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-content">
|
||||
<h3>Симметричное шифрование</h3>
|
||||
<p>Все отправляемые текстовые сообщения шифруются по стандарту AES-256-GCM с уникальным вектором инициализации (Nonce). Полученный шифротекст передается на сервер.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="messagesCryptoHeader">Шифрование текстовых сообщений</h2>
|
||||
<p>Каждое текстовое сообщение кодируется в массив байтов, после чего для него генерируется случайный 12-байтовый вектор инициализации (nonce). Данные шифруются на ключе sharedSecret по алгоритму AES-256-GCM. Результат представляет собой склеенный массив байтов: <code>nonce (12 байт) + mac (16 байт) + ciphertext (зашифрованный текст)</code>, закодированный в Base64.</p>
|
||||
|
||||
<div class="code-container" id="messageCode">
|
||||
<pre><code>// Схематичный пример дешифровки сообщения на клиенте
|
||||
Future<String> decryptMessage(String base64Data, SecretKey sharedKey) async {
|
||||
final data = base64Decode(base64Data);
|
||||
|
||||
final nonce = data.sublist(0, 12);
|
||||
final mac = data.sublist(12, 28);
|
||||
final cipherText = data.sublist(28);
|
||||
|
||||
final decrypted = await aesGcm.decrypt(
|
||||
SecretBox(cipherText, nonce: nonce, mac: Mac(mac)),
|
||||
secretKey: sharedKey,
|
||||
);
|
||||
|
||||
return utf8.decode(decrypted);
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="filesCryptoHeader">Поблочное шифрование медиафайлов</h2>
|
||||
<p>Для предотвращения утечек данных при отправке медиафайлов (картинок, видео, голосовых заметок и документов), в Чепухаграм внедрена продвинутая поблочная система шифрования:</p>
|
||||
<ol>
|
||||
<li>При выборе файла генерируется случайный симметричный ключ файла (<code>fileKey</code>).</li>
|
||||
<li>Ключ <code>fileKey</code> шифруется на общем ключе чата (<code>sharedSecret</code>) и отправляется на сервер как поле <code>encrypted_key</code>.</li>
|
||||
<li>Сам файл считывается потоком и шифруется блоками по 64 КБ с использованием <code>fileKey</code>. К каждому зашифрованному блоку добавляется 4-байтовый заголовок, содержащий точную длину блока, и уникальный вектор инициализации с тегом аутентификации MAC.</li>
|
||||
</ol>
|
||||
|
||||
<!-- Media block stream diagram -->
|
||||
<h3>Архитектура поблочного крипто-стрима</h3>
|
||||
<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> Такой подход позволяет осуществлять потоковую дешифрацию медиа во время загрузки (стриминг), благодаря чему видео и аудиозаписи начинают воспроизводиться еще до полной загрузки файла.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="masterPassHeader">Резервная копия ключей и мастер-пароль</h2>
|
||||
<p>Поскольку приватный ключ X25519 хранится в изолированном защищенном хранилище (Secure Storage) вашего смартфона, при переустановке приложения вы можете потерять доступ к переписке. Для предотвращения этого в Чепухаграм создана система защищенных резервных копий:</p>
|
||||
<ul>
|
||||
<li>Вы придумываете сложный <strong>Мастер-пароль</strong>.</li>
|
||||
<li>На основе этого пароля с помощью алгоритма <strong>PBKDF2 (600 000 итераций, HMAC-SHA256, соль "chepuhagram_salt")</strong> генерируется ключ шифрования резервной копии.</li>
|
||||
<li>Приватный ключ X25519 шифруется на этом ключе и отправляется на сервер как <code>encrypted_private_key</code>.</li>
|
||||
<li>При входе на новом устройстве вы вводите мастер-пароль, приложение скачивает копию ключа, расшифровывает её на вашем устройстве, и вы снова можете читать все ваши чаты. Сервер видит только зашифрованный массив байт и не может его декодировать.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="sessionSecurityHeader">Безопасность сессий и автозавершение</h2>
|
||||
<p>Чепухаграм поддерживает одновременный вход на нескольких устройствах. Вся сессионная активность полностью подконтрольна пользователю:</p>
|
||||
<p>При любом изменении пароля сквозного шифрования (или при полном сбросе ключей) все остальные активные сессии на сторонних девайсах автоматически инвалидируются на сервере и прекращают работу. Это защищает ваши чаты от несанкционированного доступа с ранее авторизованных устройств.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<div class="container">
|
||||
<a href="../index.html" class="footer-logo">
|
||||
<span class="logo-dot"></span>Чепухаграм
|
||||
</a>
|
||||
<p class="footer-text">Документ подготовлен техническим отделом Чепухаграм. Все криптографические операции соответствуют современным стандартам безопасности.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,946 @@
|
|||
<!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). Общайтесь безопасно, обменивайтесь файлами, совершайте WebRTC звонки и отправляйте видео-кружочки.">
|
||||
<!-- 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&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #0b071a;
|
||||
--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);
|
||||
--card-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.5);
|
||||
--phone-bg: #130f26;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
overflow-x: hidden;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
h1, h2, h3, .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);
|
||||
}
|
||||
|
||||
/* Layout Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* Navigation Header */
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
background: rgba(11, 7, 26, 0.75);
|
||||
backdrop-filter: blur(16px);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
padding: 16px 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 12px var(--accent-color);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-cta {
|
||||
background: linear-gradient(135deg, var(--accent-color) 0%, #5e35b1 100%);
|
||||
color: white;
|
||||
padding: 10px 24px;
|
||||
border-radius: 30px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
box-shadow: 0 4px 15px var(--accent-glow);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.btn-cta:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(124, 77, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
padding: 180px 0 100px 0;
|
||||
position: relative;
|
||||
background: radial-gradient(circle at 10% 20%, rgba(124, 77, 255, 0.06) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
/* Floating Background Glow Effect */
|
||||
.hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
right: 12%;
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
background: radial-gradient(circle, rgba(124, 77, 255, 0.1) 0%, rgba(186, 104, 200, 0.03) 50%, transparent 70%);
|
||||
z-index: -1;
|
||||
filter: blur(60px);
|
||||
pointer-events: none;
|
||||
animation: floatGlow 8s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes floatGlow {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
100% { transform: translate(40px, -40px) scale(1.15); }
|
||||
}
|
||||
|
||||
.hero-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 0.9fr;
|
||||
align-items: center;
|
||||
gap: 60px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 56px;
|
||||
font-weight: 700;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 24px;
|
||||
background: linear-gradient(135deg, #ffffff 30%, #d1c4e9 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 18px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 40px;
|
||||
font-weight: 300;
|
||||
max-width: 580px;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
color: var(--text-primary);
|
||||
padding: 12px 28px;
|
||||
border-radius: 30px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: background 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Mockup Mobile Phone styling */
|
||||
.hero-mockup-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-mockup {
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
height: 600px;
|
||||
background: var(--phone-bg);
|
||||
border: 12px solid #2d264d;
|
||||
border-radius: 40px;
|
||||
box-shadow: var(--card-shadow), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Phone Camera Notch */
|
||||
.hero-mockup::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 130px;
|
||||
height: 25px;
|
||||
background: #2d264d;
|
||||
border-bottom-left-radius: 18px;
|
||||
border-bottom-right-radius: 18px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Status Bar */
|
||||
.mockup-status {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 26px 20px 10px 24px;
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.2px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.mockup-status-icons {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mockup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
padding: 10px 20px 15px 20px;
|
||||
z-index: 5;
|
||||
background: rgba(19, 15, 38, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.mockup-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
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: 14px;
|
||||
}
|
||||
|
||||
.mockup-info h4 {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mockup-info span {
|
||||
font-size: 10px;
|
||||
color: #4cd964;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.mockup-info span::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background-color: #4cd964;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Chat Messages Container */
|
||||
.mockup-chat {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow-y: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.mockup-bubble {
|
||||
max-width: 80%;
|
||||
padding: 10px 14px;
|
||||
border-radius: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.mockup-bubble.partner {
|
||||
background: #231b40;
|
||||
align-self: flex-start;
|
||||
border-bottom-left-radius: 3px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.02);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.mockup-bubble.me {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
align-self: flex-end;
|
||||
border-bottom-right-radius: 3px;
|
||||
box-shadow: 0 4px 12px var(--accent-glow);
|
||||
}
|
||||
|
||||
.mockup-bubble .time {
|
||||
font-size: 9px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
text-align: right;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Typing Indicator CSS */
|
||||
.typing-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
align-self: flex-start;
|
||||
background: #231b40;
|
||||
border-radius: 16px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.typing-indicator span {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #a0aec0;
|
||||
border-radius: 50%;
|
||||
animation: typing-bounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
|
||||
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
|
||||
|
||||
@keyframes typing-bounce {
|
||||
0%, 80%, 100% { transform: scale(0.3); opacity: 0.4; }
|
||||
40% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
/* Chat Input Field Mockup */
|
||||
.mockup-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-top: 1px solid var(--glass-border);
|
||||
padding: 12px 15px 24px 15px;
|
||||
z-index: 5;
|
||||
background: rgba(19, 15, 38, 0.95);
|
||||
}
|
||||
|
||||
.mockup-input-field {
|
||||
flex: 1;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mockup-send {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(135deg, var(--accent-color) 0%, #6200ea 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
/* Phone Home Indicator Bar */
|
||||
.mockup-home-bar {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 120px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Features Section */
|
||||
.features {
|
||||
padding: 120px 0;
|
||||
position: relative;
|
||||
background: radial-gradient(circle at 90% 80%, rgba(124, 77, 255, 0.04) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(135deg, #ffffff 40%, #e2d9ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 24px;
|
||||
padding: 36px;
|
||||
transition: transform 0.4s cubic-bezier(0.165, 0.84, 0.44, 1), border-color 0.4s, box-shadow 0.4s;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.feature-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at top left, rgba(124, 77, 255, 0.15) 0%, transparent 60%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-6px);
|
||||
border-color: rgba(124, 77, 255, 0.25);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 0 20px rgba(124, 77, 255, 0.08);
|
||||
}
|
||||
|
||||
.feature-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background: rgba(124, 77, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--accent-light);
|
||||
margin-bottom: 24px;
|
||||
font-size: 24px;
|
||||
border: 1px solid rgba(124, 77, 255, 0.15);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* E2EE Call-to-Action Card */
|
||||
.e2ee-banner {
|
||||
padding: 60px 0 120px 0;
|
||||
}
|
||||
|
||||
.banner-card {
|
||||
background: linear-gradient(135deg, rgba(124, 77, 255, 0.12) 0%, rgba(20, 16, 35, 0.4) 100%);
|
||||
border: 1px solid rgba(124, 77, 255, 0.18);
|
||||
border-radius: 32px;
|
||||
padding: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 40px;
|
||||
box-shadow: var(--card-shadow);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -50px;
|
||||
right: -50px;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
max-width: 600px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.banner-tag {
|
||||
background: rgba(124, 77, 255, 0.15);
|
||||
color: #d1c4e9;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
display: inline-block;
|
||||
margin-bottom: 18px;
|
||||
border: 1px solid rgba(124, 77, 255, 0.2);
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(135deg, #ffffff 40%, #e2d9ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 15px;
|
||||
margin-bottom: 32px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.banner-visual {
|
||||
font-size: 120px;
|
||||
color: var(--accent-color);
|
||||
opacity: 0.85;
|
||||
filter: drop-shadow(0 0 20px var(--accent-glow));
|
||||
animation: pulse 3.5s infinite ease-in-out;
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); filter: drop-shadow(0 0 20px var(--accent-glow)); }
|
||||
50% { transform: scale(1.06); filter: drop-shadow(0 0 35px rgba(124, 77, 255, 0.6)); }
|
||||
100% { transform: scale(1); filter: drop-shadow(0 0 20px var(--accent-glow)); }
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
border-top: 1px solid var(--glass-border);
|
||||
padding: 50px 0;
|
||||
text-align: center;
|
||||
background: #06040e;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 28px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 900px) {
|
||||
.hero-layout {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
gap: 50px;
|
||||
}
|
||||
.hero-subtitle {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.hero-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
.hero-mockup {
|
||||
max-width: 360px;
|
||||
height: 570px;
|
||||
}
|
||||
.banner-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
.banner-visual {
|
||||
font-size: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header {
|
||||
padding: 12px 0;
|
||||
}
|
||||
nav {
|
||||
gap: 16px;
|
||||
}
|
||||
nav a {
|
||||
font-size: 13px;
|
||||
}
|
||||
.btn-cta {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.logo {
|
||||
font-size: 20px;
|
||||
}
|
||||
.hero-title {
|
||||
font-size: 40px;
|
||||
}
|
||||
.hero-subtitle {
|
||||
font-size: 15px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Header Navigation -->
|
||||
<header>
|
||||
<div class="container header-content">
|
||||
<a href="#" class="logo" id="logoLink">
|
||||
<span class="logo-dot"></span>Чепухаграм
|
||||
</a>
|
||||
<nav id="navMenu">
|
||||
<a href="#features">Функции</a>
|
||||
<a href="./about-secutity/index.html">Безопасность</a>
|
||||
<a href="./about-secutity/index.html" class="btn-cta" id="ctaHeader">О защите</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="hero">
|
||||
<div class="container hero-layout">
|
||||
<div class="hero-text">
|
||||
<h1 class="hero-title" id="mainTitle">Чепухаграм</h1>
|
||||
<p class="hero-subtitle" id="mainSub">Приватность следующего поколения. Полноценное сквозное шифрование сообщений, звонков и медиафайлов. Полный контроль над вашими личными данными.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="./about-secutity/index.html" class="btn-cta" id="heroCta">Изучить защиту</a>
|
||||
<a href="#features" class="btn-secondary" id="heroSecondary">Функции</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interactive Chat Mockup (Mobile phone styling) -->
|
||||
<div class="hero-mockup-wrapper">
|
||||
<div class="hero-mockup" id="chatMockup">
|
||||
<div class="mockup-status">
|
||||
<div class="mockup-time">09:41</div>
|
||||
<div class="mockup-status-icons">📶 🔋</div>
|
||||
</div>
|
||||
<div class="mockup-header">
|
||||
<div class="mockup-avatar">А</div>
|
||||
<div class="mockup-info">
|
||||
<h4>Алиса</h4>
|
||||
<span>в сети</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mockup-chat" id="mockupChat">
|
||||
<div class="mockup-bubble partner">
|
||||
Привет! Ты проверил систему шифрования Чепухаграм?
|
||||
<span class="time">22:04</span>
|
||||
</div>
|
||||
<div class="mockup-bubble me">
|
||||
Да! Используется X25519 для обмена ключами и AES-256-GCM для шифрования.
|
||||
<span class="time">22:05</span>
|
||||
</div>
|
||||
<div class="mockup-bubble partner">
|
||||
Отлично! А что с файлами и звонками?
|
||||
<span class="time">22:05</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mockup-input">
|
||||
<div class="mockup-input-field" id="mockupInputText">Напишите сообщение...</div>
|
||||
<div class="mockup-send" id="mockupSendBtn">⚡</div>
|
||||
</div>
|
||||
<div class="mockup-home-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section class="features" id="features">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title" id="featuresTitle">Возможности Чепухаграм</h2>
|
||||
<p class="section-subtitle" id="featuresSub">Сочетание передовых криптографических технологий с удобным современным интерфейсом мессенджера.</p>
|
||||
</div>
|
||||
|
||||
<div class="features-grid">
|
||||
<!-- Feature 1: E2EE Messages -->
|
||||
<div class="feature-card" id="featCard1">
|
||||
<div class="feature-icon">🔒</div>
|
||||
<h3>Сквозное шифрование</h3>
|
||||
<p>Текстовые сообщения и метаданные шифруются прямо на вашем устройстве и передаются в зашифрованном виде. Сервер не имеет ключей для расшифровки.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 2: Voice & Video notes -->
|
||||
<div class="feature-card" id="featCard2">
|
||||
<div class="feature-icon">🎙️</div>
|
||||
<h3>Голос и Видео-кружочки</h3>
|
||||
<p>Записывайте голосовые сообщения и видео-кружочки в одно касание. Нативное сжатие через FFmpeg уменьшает вес медиа без потери качества.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 3: File Streaming -->
|
||||
<div class="feature-card" id="featCard3">
|
||||
<div class="feature-icon">📤</div>
|
||||
<h3>Стриминг файлов</h3>
|
||||
<p>Безопасный поблочный стриминг файлов любого формата. Каждый файл шифруется своим уникальным AES-GCM ключом, который передается через E2EE канал.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 4: WebRTC Calls -->
|
||||
<div class="feature-card" id="featCard4">
|
||||
<div class="feature-icon">📞</div>
|
||||
<h3>Аудио и видеозвонки</h3>
|
||||
<p>Кристально чистые и конфиденциальные WebRTC звонки напрямую между вашими устройствами в обход промежуточных серверов.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Encryption Banner Section -->
|
||||
<section class="e2ee-banner">
|
||||
<div class="container">
|
||||
<div class="banner-card" id="bannerCard">
|
||||
<div class="banner-content">
|
||||
<span class="banner-tag">Архитектура безопасности</span>
|
||||
<h2 class="banner-title" id="bannerTitle">Zero-Knowledge Безопасность</h2>
|
||||
<p class="banner-text" id="bannerText">Мы придерживаемся принципа «нулевого разглашения». Ваши секретные ключи создаются на базе эллиптических кривых эллиптического протокола X25519 и никогда не покидают ваши устройства в незашифрованном виде.</p>
|
||||
<a href="./about-secutity/index.html" class="btn-cta" id="bannerCta">Подробнее о криптографии</a>
|
||||
</div>
|
||||
<div class="banner-visual" id="lockIcon">🔒</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<div class="container">
|
||||
<a href="#" class="footer-logo">
|
||||
<span class="logo-dot"></span>Чепухаграм
|
||||
</a>
|
||||
<div class="footer-links">
|
||||
<a href="#features">Функции</a>
|
||||
<a href="./about-secutity/index.html">Безопасность мессенджера</a>
|
||||
</div>
|
||||
<p class="footer-text">© 2026 Чепухаграм. Все права защищены. Разработано с фокусом на абсолютную приватность.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Interactive script for Chat Mockup Mock animations -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const chat = document.getElementById('mockupChat');
|
||||
const inputField = document.getElementById('mockupInputText');
|
||||
|
||||
const conversation = [
|
||||
{ isMe: true, text: "Файлы и кружочки шифруются поблочно ключом AES-GCM." },
|
||||
{ isMe: false, text: "Вау! А ключи файлов шифруются на общем X25519 ключе?" },
|
||||
{ isMe: true, text: "Да, именно так! Полная крипто-защита." },
|
||||
{ isMe: false, text: "Супер, теперь я спокоен за свои данные!" }
|
||||
];
|
||||
|
||||
let msgIdx = 0;
|
||||
|
||||
// Updates phone time in the mockup to match real time
|
||||
function updateMockupTime() {
|
||||
const timeEl = document.querySelector('.mockup-time');
|
||||
if (timeEl) {
|
||||
const now = new Date();
|
||||
timeEl.textContent = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
updateMockupTime();
|
||||
setInterval(updateMockupTime, 60000);
|
||||
|
||||
function simulateTypingAndSending() {
|
||||
if (msgIdx >= conversation.length) {
|
||||
msgIdx = 0;
|
||||
chat.innerHTML = `
|
||||
<div class="mockup-bubble partner">
|
||||
Привет! Ты проверил систему шифрования Чепухаграм?
|
||||
<span class="time">22:04</span>
|
||||
</div>
|
||||
<div class="mockup-bubble me">
|
||||
Да! Используется X25519 для обмена ключами и AES-256-GCM для шифрования.
|
||||
<span class="time">22:05</span>
|
||||
</div>
|
||||
<div class="mockup-bubble partner">
|
||||
Отлично! А что с файлами и звонками?
|
||||
<span class="time">22:05</span>
|
||||
</div>
|
||||
`;
|
||||
setTimeout(simulateTypingAndSending, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextMsg = conversation[msgIdx];
|
||||
|
||||
// Show typing indicator
|
||||
const typing = document.createElement('div');
|
||||
typing.className = 'typing-indicator';
|
||||
typing.innerHTML = '<span></span><span></span><span></span>';
|
||||
chat.appendChild(typing);
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
|
||||
inputField.textContent = "Печатает...";
|
||||
|
||||
setTimeout(() => {
|
||||
// Remove typing indicator
|
||||
typing.remove();
|
||||
|
||||
inputField.textContent = nextMsg.text;
|
||||
|
||||
setTimeout(() => {
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = `mockup-bubble ${nextMsg.isMe ? 'me' : 'partner'}`;
|
||||
|
||||
const time = new Date();
|
||||
const timeStr = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`;
|
||||
|
||||
bubble.innerHTML = `${nextMsg.text}<span class="time">${timeStr}</span>`;
|
||||
chat.appendChild(bubble);
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
|
||||
inputField.textContent = "Напишите сообщение...";
|
||||
msgIdx++;
|
||||
|
||||
setTimeout(simulateTypingAndSending, 3500);
|
||||
}, 800);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
setTimeout(simulateTypingAndSending, 3000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue