decryptFile static method

Future<File> decryptFile(
  1. File encryptedFile
)

Decrypts a file whose content was produced by encryptBytes.

Reads the IV from the first 16 bytes, then decrypts the remainder. If decryption fails (e.g., file was encrypted with the old hardcoded key), the caller (AppCacheManager.getFileStream) will delete and re-download it.

Implementation

static Future<io.File> decryptFile(File encryptedFile) async {
  _assertInitialized();

  final bytes = await encryptedFile.readAsBytes();
  if (bytes.isEmpty) throw Exception('File is empty');
  if (bytes.length <= _ivLength) {
    throw Exception(
      'File too small to contain IV + ciphertext. Likely from a previous format.',
    );
  }

  // Extract the per-file IV from the first 16 bytes.
  final iv = encrypt.IV(Uint8List.fromList(bytes.sublist(0, _ivLength)));
  final cipherBytes = Uint8List.fromList(bytes.sublist(_ivLength));

  final decryptedBytes = _encrypter!.decryptBytes(
    encrypt.Encrypted(cipherBytes),
    iv: iv,
  );

  // Safeguard path logic (unchanged from original)
  final String separator = io.Platform.pathSeparator;
  final String keyMatch = '$separator${AppCacheManager.key}$separator';
  final String decryptedMatch = '${keyMatch}decrypted$separator';

  String decryptedPath;
  if (encryptedFile.path.contains(keyMatch)) {
    decryptedPath = encryptedFile.path.replaceFirst(keyMatch, decryptedMatch);
  } else {
    decryptedPath =
        '${encryptedFile.parent.path}${separator}decrypted$separator${encryptedFile.basename}';
  }

  if (decryptedPath == encryptedFile.path) {
    throw Exception('Decryption path collision detected. Avoiding overwrite.');
  }

  final decryptedFile = io.File(decryptedPath);
  if (!await decryptedFile.parent.exists()) {
    await decryptedFile.parent.create(recursive: true);
  }
  return decryptedFile.writeAsBytes(decryptedBytes);
}