import 'package:cryptography/cryptography.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:chepuhagram/data/models/contact_model.dart'; import 'dart:async'; import 'package:pointycastle/export.dart' as pc; import 'dart:io'; class CryptoService { final _storage = const FlutterSecureStorage(); final algorithm = X25519(); final aesGcm = AesGcm.with256bits(); Future> initAccountSecurity(String masterPassword) async { // Генерируем пару X25519 ключей final keyPair = await algorithm.newKeyPair(); final publicKey = await keyPair.extractPublicKey(); final privateKeyBytes = await keyPair.extractPrivateKeyBytes(); // Сохраняем приватный ключ в Secure Storage await _storage.write( key: 'private_key', value: base64Encode(privateKeyBytes), ); // Шифруем приватный ключ с мастер-паролем (AES-GCM) final masterKey = await _deriveKeyFromPassword(masterPassword); final nonce = aesGcm.newNonce(); final encrypted = await aesGcm.encrypt( privateKeyBytes, secretKey: masterKey, nonce: nonce, ); // Комбинируем nonce и зашифрованные данные final encryptedData = nonce + encrypted.mac.bytes + encrypted.cipherText; final encryptedPrivateKey = base64Encode(encryptedData); final publicKeyBase64 = base64Encode(publicKey.bytes); return { 'public_key': publicKeyBase64, 'encrypted_private_key': encryptedPrivateKey, }; } Future decryptPrivateKey( String encryptedPrivateKey, String masterPassword, ) async { 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 decrypted = await aesGcm.decrypt( SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)), secretKey: masterKey, ); return base64Encode(decrypted); } catch (e) { throw Exception('Неверный мастер-пароль или поврежденные данные'); } } Future encryptPrivateKeyWithPassword( String privateKeyBase64, String masterPassword, ) async { final privateKeyBytes = base64Decode(privateKeyBase64); final masterKey = await _deriveKeyFromPassword(masterPassword); final nonce = aesGcm.newNonce(); final encrypted = await aesGcm.encrypt( privateKeyBytes, secretKey: masterKey, nonce: nonce, ); final encryptedData = nonce + encrypted.mac.bytes + encrypted.cipherText; return base64Encode(encryptedData); } Future _deriveKeyFromPassword(String password) async { final pbkdf2 = Pbkdf2( macAlgorithm: Hmac.sha256(), iterations: 10000, bits: 256, ); final salt = utf8.encode('chepuhagram_salt'); return await pbkdf2.deriveKeyFromPassword(password: password, nonce: salt); } Future deriveSharedSecret( String myPrivateKeyBase64, String theirPublicKeyBase64, ) async { final myKeyPair = await algorithm.newKeyPairFromSeed( base64Decode(myPrivateKeyBase64), ); final theirPublicKey = SimplePublicKey( base64Decode(theirPublicKeyBase64), type: KeyPairType.x25519, ); return await algorithm.sharedSecretKey( keyPair: myKeyPair, remotePublicKey: theirPublicKey, ); } Future encryptMessage(String text, SecretKey sharedKey) async { final nonce = aesGcm.newNonce(); final encrypted = await aesGcm.encrypt( utf8.encode(text), secretKey: sharedKey, nonce: nonce, ); // Сохраняем Nonce + MAC + CipherText для передачи return base64Encode(nonce + encrypted.mac.bytes + encrypted.cipherText); } static Future decryptInIsolate( String base64Data, SecretKey sharedKey, ) async { final data = base64Decode(base64Data); final aesGcm = AesGcm.with256bits(); 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); } static Future> bulkDecryptContacts( Map data, ) async { final List contacts = data['contacts']; final String privKey = data['privKey']; final Map cache = data['cache']; final x25519 = X25519(); final aesGcm = AesGcm.with256bits(); final List result = []; // Вычисляем свою пару один раз final myKeyPair = await x25519.newKeyPairFromSeed(base64Decode(privKey)); for (var contact in contacts) { if (contact.lastMessage == null || contact.publicKey == null) { result.add(contact); continue; } try { SecretKey sharedKey; if (cache.containsKey(contact.id)) { sharedKey = cache[contact.id]!; } else { final theirPubKey = SimplePublicKey( base64Decode(contact.publicKey!), type: KeyPairType.x25519, ); sharedKey = await x25519.sharedSecretKey( keyPair: myKeyPair, remotePublicKey: theirPubKey, ); } // Дешифровка AES-GCM final msgData = base64Decode(contact.lastMessage!); final decrypted = await aesGcm.decrypt( SecretBox( msgData.sublist(28), nonce: msgData.sublist(0, 12), mac: Mac(msgData.sublist(12, 28)), ), secretKey: sharedKey, ); result.add( contact.copyWith( lastMessage: utf8.decode(decrypted), isLastMsgDecrypted: true, avatarFileId: contact.avatarFileId, avatarUrl: contact.avatarUrl, ), ); } catch (e) { result.add( contact.copyWith( lastMessage: '[не удалось расшифровать: $e]', isLastMsgDecrypted: true, avatarFileId: contact.avatarFileId, avatarUrl: contact.avatarUrl, ), ); } } return result; } static Future>> computeSharedKeysTask( Map params, ) async { final Map isolateKeysMap = params['keysMap']; final String isolatePrivKey = params['privKey']; final x25519 = X25519(); final Map> result = {}; final myKeyPair = await x25519.newKeyPairFromSeed( base64Decode(isolatePrivKey), ); for (var entry in isolateKeysMap.entries) { try { final theirPubKey = SimplePublicKey( base64Decode(entry.value), type: KeyPairType.x25519, ); final sharedKey = await x25519.sharedSecretKey( keyPair: myKeyPair, remotePublicKey: theirPubKey, ); result[entry.key] = await sharedKey.extractBytes(); } catch (_) { continue; } } return result; } Future<(Stream>, String)> encryptFileStream( Stream> fileStream, SecretKey sharedKey, { void Function(int processed, int total)? onProgress, int? totalSize, }) async { // 1. Генерируем уникальный ключ для конкретного файла final SecretKey fileKey = await aesGcm.newSecretKey(); final List fileKeyBytes = await fileKey.extractBytes(); // 2. Шифруем этот ключ файла на общем ключе чата (sharedKey) final keyNonce = aesGcm.newNonce(); final encryptedKeyBox = await aesGcm.encrypt( fileKeyBytes, secretKey: sharedKey, nonce: keyNonce, ); // Кодируем зашифрованный ключ в Base64 для сервера final String encryptedKeyForServer = base64Encode( encryptedKeyBox.concatenation(), ); int processedBytes = 0; final int total = totalSize ?? 0; // 3. Создаем асинхронный генератор для поблочного шифрования самого файла Stream> processEncryption() async* { final List buffer = []; const int chunkSize = 64 * 1024; // 64 KB await for (final chunk in fileStream) { buffer.addAll(chunk); while (buffer.length >= chunkSize) { final plainBlock = Uint8List.fromList(buffer.sublist(0, chunkSize)); buffer.removeRange(0, chunkSize); final blockNonce = aesGcm.newNonce(); final secretBox = await aesGcm.encrypt( plainBlock, secretKey: fileKey, nonce: blockNonce, ); final payload = secretBox.concatenation(); final header = ByteData(4)..setUint32(0, payload.length); yield header.buffer.asUint8List(); yield payload; processedBytes += chunkSize; if (onProgress != null) { onProgress(processedBytes, total); } } } // Дозаписываем остаток файла (хвост), если он есть if (buffer.isNotEmpty) { final plainBlock = Uint8List.fromList(buffer); final blockNonce = aesGcm.newNonce(); final secretBox = await aesGcm.encrypt( plainBlock, secretKey: fileKey, nonce: blockNonce, ); final payload = secretBox.concatenation(); final header = ByteData(4)..setUint32(0, payload.length); yield header.buffer.asUint8List(); yield payload; } } // Возвращаем кортеж (Record): очищенный зашифрованный поток данных и ключ для сервера return (processEncryption(), encryptedKeyForServer); } Stream> decryptFileStream( Stream> encryptedStream, SecretKey sharedKey, String encryptedFileKey, { int? totalBytes, void Function(int processed, int total)? onProgress, }) async* { try { // 1. Дешифруем ключ файла с помощью общего ключа чата final encryptedKeyBytes = base64Decode(encryptedFileKey); final keySecretBox = SecretBox.fromConcatenation( encryptedKeyBytes, nonceLength: 12, macLength: 16, ); final fileKeyBytes = await aesGcm.decrypt( keySecretBox, secretKey: sharedKey, ); final fileKey = SecretKey(fileKeyBytes); final List buffer = []; int blocksDecrypted = 0; int totalProcessedBytes = 0; // 2. Потоковая дешифровка блоков файла await for (final chunk in encryptedStream) { buffer.addAll(chunk); while (true) { if (buffer.length < 4) break; final headerBytes = Uint8List.fromList(buffer.sublist(0, 4)); final int payloadLength = ByteData.sublistView( headerBytes, ).getUint32(0); // Проверяем: если длина чанка подозрительно огромная (из-за неверного формата файла) if (payloadLength > 500 * 1024 || payloadLength <= 0) { print( "ОШИБКА: Неверный заголовок длины чанка: $payloadLength байт. Возможно, файл зашифрован старым методом!", ); throw Exception("Неверный формат зашифрованного блока"); } if (buffer.length < 4 + payloadLength) break; final encryptedBlockBytes = Uint8List.fromList( buffer.sublist(4, 4 + payloadLength), ); buffer.removeRange(0, 4 + payloadLength); final blockSecretBox = SecretBox.fromConcatenation( encryptedBlockBytes, nonceLength: 12, macLength: 16, ); final decryptedBlock = await aesGcm.decrypt( blockSecretBox, secretKey: fileKey, ); blocksDecrypted++; if (blocksDecrypted % 10 == 0 || payloadLength < 64 * 1024) { print( "Дешифровано блоков: $blocksDecrypted. Текущий размер: ${decryptedBlock.length} байт. Всего обработано $totalProcessedBytes. Всего $totalBytes", ); } // Увеличиваем счетчик обработанных зашифрованных байт totalProcessedBytes += 4 + payloadLength; // Вызываем колбэк прогресса if (onProgress != null) { // Передаем, сколько байт обработано, и общий размер (если totalBytes null, передаем -1) onProgress(totalProcessedBytes, totalBytes ?? -1); } yield decryptedBlock; } } print( "ПОТОК ДЕШИФРАЦИИ ЗАВЕРШЕН ПОЛНОСТЬЮ. Всего блоков: $blocksDecrypted", ); } catch (e, stack) { print("КРИТИЧЕСКАЯ ОШИБКА ВНУТРИ КРИПТОСТРИМА: $e"); print(stack); rethrow; } } Future decryptAesKey( String encryptedKey, SecretKey sharedKey, ) async { try { final keyBytes = base64Decode(encryptedKey); final nonce = keyBytes.sublist(0, 12); final cipherText = keyBytes.sublist(12, keyBytes.length - 16); final mac = keyBytes.sublist(keyBytes.length - 16); final decrypted = await aesGcm.decrypt( SecretBox(cipherText, nonce: nonce, mac: Mac(mac)), secretKey: sharedKey, ); return Uint8List.fromList(decrypted); } catch (e) { print('Ошибка дешифровки AES ключа: $e'); return null; } } Future encryptAesKey(List keyBytes, SecretKey sharedKey) async { try { final encrypted = await aesGcm.encrypt(keyBytes, secretKey: sharedKey); return base64Encode(encrypted.concatenation()); } catch (e) { print('Ошибка шифрования AES ключа: $e'); return null; } } Future decryptMedia( List encryptedData, String encryptedKey, SecretKey sharedKey, ) async { try { final keyBytes = base64Decode(encryptedKey); final keyNonce = keyBytes.sublist(0, 12); final keyCipher = keyBytes.sublist(12, keyBytes.length - 16); final keyMac = keyBytes.sublist(keyBytes.length - 16); final decryptedFileKey = await aesGcm.decrypt( SecretBox(keyCipher, nonce: keyNonce, mac: Mac(keyMac)), secretKey: sharedKey, ); final fileSecretKey = SecretKey(decryptedFileKey); final nonce = encryptedData.sublist(0, 12); final cipherText = encryptedData.sublist(12, encryptedData.length - 16); final mac = encryptedData.sublist(encryptedData.length - 16); final decryptedBytes = await aesGcm.decrypt( SecretBox(cipherText, nonce: nonce, mac: Mac(mac)), secretKey: fileSecretKey, ); return Uint8List.fromList(decryptedBytes); } catch (e) { print('Ошибка дешифровки медиа: $e'); return null; } } Future _decryptFileKey( String encryptedFileKey, SecretKey sharedKey, ) async { try { final keyBytes = base64Decode(encryptedFileKey); final nonce = keyBytes.sublist(0, 12); final macBytes = keyBytes.sublist(keyBytes.length - 16); final cipherText = keyBytes.sublist(12, keyBytes.length - 16); final decrypted = await aesGcm.decrypt( SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)), secretKey: sharedKey, ); return SecretKey(decrypted); } catch (e) { print('Error decrypting file key: $e'); return null; } } Future 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); } Future getPrivateKey() async { return await _storage.read(key: 'private_key'); } Future hasPrivateKey() async { final key = await _storage.read(key: 'private_key'); return key != null; } Future savePrivateKey(String privateKey) async { await _storage.write(key: 'private_key', value: privateKey); } Future deletePrivateKey() async { await _storage.delete(key: 'private_key'); } SecretKey? _currentSharedKey; // Метод для установки ключа (вызывается при входе в чат) void setCurrentSharedKey(SecretKey key) { _currentSharedKey = key; } // Тот самый метод, который ищет ChatScreen Future getSharedKey(String? chatId) async { if (_currentSharedKey == null) { // Если ключа нет, его нужно либо вычислить заново, // либо выбросить ошибку. Для теста можно вернуть ошибку: throw Exception("Shared key not initialized for chat $chatId"); } return _currentSharedKey!; } }