126 lines
4.2 KiB
Dart
Executable File
126 lines
4.2 KiB
Dart
Executable File
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<void> 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:<base64_iv>:<base64_ciphertext>"
|
|
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:<iv_b64>:<cipher_b64>"
|
|
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<int>.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();
|
|
}
|