import 'dart:math'; import 'dart:typed_data'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:flutter/foundation.dart'; import 'package:secure_string_operations/secure_string_operations.dart'; import '../../constant/char_map.dart'; import '../../env/env.dart'; import '../../main.dart'; import '../../print.dart'; class EncryptionHelper { static EncryptionHelper? _instance; late final encrypt.Key key; late final encrypt.IV iv; // Legacy CBC IV (kept for backward-compat) /// Prefix to distinguish new GCM ciphertext from old CBC ciphertext static const String _gcmPrefix = 'GCM:'; EncryptionHelper._(this.key, this.iv); static EncryptionHelper get instance { if (_instance == null) { throw Exception( "EncryptionHelper is not initialized. Call `await EncryptionHelper.initialize()` in main."); } return _instance!; } /// Initializes and stores the instance globally static Future initialize() async { if (_instance != null) { debugPrint("EncryptionHelper is already initialized."); return; } debugPrint("Initializing EncryptionHelper..."); var keyOfApp = r(Env.keyOfApp).toString().split(Env.addd)[0]; var initializationVector = r(Env.initializationVector).toString().split(Env.addd)[0]; _instance = EncryptionHelper._( encrypt.Key.fromUtf8(keyOfApp!), encrypt.IV.fromUtf8(initializationVector!), ); debugPrint("EncryptionHelper initialized successfully."); } /// ✅ FIX H-04: Encrypts a string using AES-256-GCM with a random IV /// Format: "GCM::" String encryptData(String plainText) { try { final randomIv = _generateRandomIv(12); // 12-byte nonce recommended for GCM final gcmEncrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm)); final encrypted = gcmEncrypter.encrypt(plainText, iv: randomIv); // Prepend GCM prefix + IV so the server/decoder knows the format return '$_gcmPrefix${randomIv.base64}:${encrypted.base64}'; } catch (e) { debugPrint('GCM Encryption failed, falling back to CBC: $e'); // Fallback to CBC for environments that don't support GCM try { final cbcEncrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); final encrypted = cbcEncrypter.encrypt(plainText, iv: iv); return encrypted.base64; } catch (e2) { debugPrint('CBC Encryption Error: $e2'); return ''; } } } /// ✅ FIX H-04: Decrypts a string — supports both GCM (new) and CBC (legacy) String decryptData(String encryptedText) { try { if (encryptedText.startsWith(_gcmPrefix)) { // New GCM format: "GCM::" final parts = encryptedText.substring(_gcmPrefix.length).split(':'); if (parts.length != 2) { debugPrint('Invalid GCM format, falling back to CBC'); return _decryptLegacyCbc(encryptedText); } final gcmIv = encrypt.IV.fromBase64(parts[0]); final gcmEncrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm)); return gcmEncrypter.decrypt( encrypt.Encrypted.fromBase64(parts[1]), iv: gcmIv); } // Legacy CBC format (no prefix) return _decryptLegacyCbc(encryptedText); } catch (e) { debugPrint('Decryption Error: $e'); try { return _decryptLegacyCbc(encryptedText); } catch (_) { return ''; } } } /// Legacy CBC decryption (backward compatibility with existing data) String _decryptLegacyCbc(String encryptedText) { final cbcEncrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); final encrypted = encrypt.Encrypted.fromBase64(encryptedText); return cbcEncrypter.decrypt(encrypted, iv: iv); } /// Generates a cryptographically secure random IV encrypt.IV _generateRandomIv(int length) { final random = Random.secure(); final bytes = List.generate(length, (_) => random.nextInt(256)); return encrypt.IV(Uint8List.fromList(bytes)); } } r(String string) { return X.r(X.r(X.r(string, cn), cC), cs).toString(); } c(String string) { return X.c(X.c(X.c(string, cn), cC), cs).toString(); }