Chepuhagram/lib/domain/services/crypto_service.dart

373 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

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

import 'package:cryptography/cryptography.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:chepuhagram/data/models/contact_model.dart';
class CryptoService {
final _storage = const FlutterSecureStorage();
final algorithm = X25519();
final aesGcm = AesGcm.with256bits();
Future<Map<String, String>> 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<String> 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<String> 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<SecretKey> _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<SecretKey> 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<String> 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<String> 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<List<Contact>> bulkDecryptContacts(
Map<String, dynamic> data,
) async {
final List<Contact> contacts = data['contacts'];
final String privKey = data['privKey'];
final Map<int, SecretKey> cache = data['cache'];
final x25519 = X25519();
final aesGcm = AesGcm.with256bits();
final List<Contact> 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<Map<int, List<int>>> computeSharedKeysTask(
Map<String, dynamic> params,
) async {
final Map<int, String> isolateKeysMap = params['keysMap'];
final String isolatePrivKey = params['privKey'];
final x25519 = X25519();
final Map<int, List<int>> 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<(List<int>, String)?> encryptImage(
List<int> fileBytes,
SecretKey sharedKey,
) async {
try {
final SecretKey fileSecretKey = await aesGcm.newSecretKey();
final List<int> fileSecretKeyBytes = await fileSecretKey.extractBytes();
final SecretBox secretBox = await aesGcm.encrypt(
fileBytes,
secretKey: fileSecretKey,
);
final List<int> dataToUpload = secretBox.concatenation();
final encryptedKeyBox = await aesGcm.encrypt(
fileSecretKeyBytes,
secretKey: sharedKey,
);
final String encryptedKeyForServer = base64Encode(
encryptedKeyBox.concatenation(),
);
return (dataToUpload, encryptedKeyForServer);
} catch (e) {
print("Ошибка шифрования медиа: $e");
return null;
}
}
Future<Uint8List?> 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<String?> encryptAesKey(List<int> 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<Uint8List?> decryptImage(
List<int> 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<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);
}
Future<String?> getPrivateKey() async {
return await _storage.read(key: 'private_key');
}
Future<bool> hasPrivateKey() async {
final key = await _storage.read(key: 'private_key');
return key != null;
}
Future<void> savePrivateKey(String privateKey) async {
await _storage.write(key: 'private_key', value: privateKey);
}
Future<void> deletePrivateKey() async {
await _storage.delete(key: 'private_key');
}
}