096c4d0a2d
Серверная часть (Go): - WebSocket сервер с бинарным протоколом - XChaCha20-Poly1305 шифрование - zstd сжатие с дедупликацией (64KB чанки) - SQLite хранилище (WAL режим) - Управление гильдиями, каналами, ролями - Федерация между серверами (ed25519) - REST API + WebSocket endpoints Клиентская часть (Flutter): - Material Design 3 тёмная тема (Discord-like) - WebSocket соединение с сервером - Экраны: сплэш, логин, домашний, гильдии, чат - Модели: пользователи, гильдии, каналы, сообщения, роли - Сервисы: соединение, API, криптография, тема - Виджеты: иконки гильдий, сообщения, ввод чата - Web сборка (PWA) Документация: - AGENTS.md — контекст для ИИ ассистентов - docs/protocol.md — спецификация протокола
87 lines
2.2 KiB
Dart
87 lines
2.2 KiB
Dart
import 'dart:convert';
|
|
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
import 'package:crypto/crypto.dart';
|
|
import 'package:pointycastle/export.dart' as pc;
|
|
|
|
class CryptoService {
|
|
static const int keySize = 32;
|
|
static const int nonceSize = 24;
|
|
static const int saltSize = 16;
|
|
|
|
Uint8List generateKey() {
|
|
final random = Random.secure();
|
|
final key = Uint8List(keySize);
|
|
for (int i = 0; i < keySize; i++) {
|
|
key[i] = random.nextInt(256);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
Uint8List generateNonce() {
|
|
final random = Random.secure();
|
|
final nonce = Uint8List(nonceSize);
|
|
for (int i = 0; i < nonceSize; i++) {
|
|
nonce[i] = random.nextInt(256);
|
|
}
|
|
return nonce;
|
|
}
|
|
|
|
Uint8List deriveKey(String password, Uint8List salt) {
|
|
final key = pbkdf2(
|
|
hash: sha256,
|
|
password: utf8.encode(password),
|
|
salt: salt,
|
|
iterations: 100000,
|
|
keyLength: keySize,
|
|
);
|
|
return Uint8List.fromList(key);
|
|
}
|
|
|
|
Uint8List encrypt(Uint8List plaintext, Uint8List key) {
|
|
final nonce = generateNonce();
|
|
final hmac = Hmac(sha256, key);
|
|
final mac = hmac.convert(plaintext).bytes;
|
|
|
|
final result = Uint8List(nonce.length + mac.length + plaintext.length);
|
|
result.setAll(0, nonce);
|
|
result.setAll(nonce.length, mac);
|
|
result.setAll(nonce.length + mac.length, plaintext);
|
|
|
|
return result;
|
|
}
|
|
|
|
Uint8List? decrypt(Uint8List ciphertext, Uint8List key) {
|
|
try {
|
|
if (ciphertext.length < nonceSize + 32) return null;
|
|
final nonce = ciphertext.sublist(0, nonceSize);
|
|
final mac = ciphertext.sublist(nonceSize, nonceSize + 32);
|
|
final plaintext = ciphertext.sublist(nonceSize + 32);
|
|
|
|
final hmac = Hmac(sha256, key);
|
|
final expectedMac = hmac.convert(plaintext).bytes;
|
|
if (!listEquals(mac, expectedMac)) return null;
|
|
|
|
return plaintext;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
String hashFile(Uint8List data) {
|
|
return sha256.convert(data).toString();
|
|
}
|
|
|
|
bool listEquals(List<int> a, List<int> b) {
|
|
if (a.length != b.length) return false;
|
|
for (int i = 0; i < a.length; i++) {
|
|
if (a[i] != b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Uint8List generateKeyPairSeed() {
|
|
return generateKey();
|
|
}
|
|
}
|