Главная уязвимость Flutter-приложений при хранении данных заключается в избыточном доверии к стандартным плагинам SharedPreferences и sqflite, которые пишут данные в открытом виде. В случае root-доступа или физического перехвата устройства любой конфиденциальный токен или персональный профиль пользователя становится доступен за считанные секунды.
Многие разработчики используют пакет flutter_shared_preferences для хранения сессий, забывая, что на Android данные пишутся в XML-файл, а на iOS — в Plist. Оба формата хранятся в открытом виде. Если злоумышленник получит доступ к файловой системе, он извлечет JWT-токен или API-ключ без необходимости взламывать код приложения.
Мини-кейс: при разработке корпоративного клиента для внутреннего пользования мы обнаружили, что даже после выхода из аккаунта часть кэшированных данных оставалась в SharedPrefs, что создавало риск утечки при передаче устройства другому сотруднику. Решение — полный сброс ключей при logout и переход на зашифрованные хранилища.
Микро-вывод: SharedPreferences допустимы только для некритичных настроек интерфейса (тема, язык), но категорически запрещены для хранения секретов.
Flutter Secure Storage и аппаратные анклавы
Золотым стандартом для хранения малых объемов данных является flutter_secure_storage. Он не просто шифрует данные, а делегирует управление ключами системным механизмам: Keychain на iOS и Keystore на Android. Это означает, что ключ шифрования хранится в защищенном аппаратном модуле (TEE или Secure Enclave), и извлечь его программно практически невозможно.
Важный нюанс: на Android при использовании Keystore может возникнуть конфликт с бэкапом данных (Auto Backup), когда зашифрованные данные переносятся на новое устройство, но ключ остается на старом, что приводит к крашу приложения при попытке чтения. Для этого необходимо явно отключать бэкап для файлов хранилища в AndroidManifest.xml.
Микро-вывод: Используйте Secure Storage для токенов и паролей, но помните о настройках бэкапа в Android, чтобы избежать потери доступа к данным.
Шифрование больших массивов данных в SQLite
Когда объем данных превышает возможности Keychain/Keystore, разработчики переходят на локальные БД. Стандартный sqflite не обеспечивает шифрования. Для защиты данных на уровне страниц диска следует использовать SQLCipher. Он реализует AES-256 шифрование всей базы данных, превращая её в нечитаемый бинарный файл.
Условный пример: представьте приложение для учета медицинских показателей. Хранение истории измерений в обычном SQLite позволяет любому приложению с root-правами прочитать историю болезни. SQLCipher закрывает эту дыру, требуя ключ для инициализации соединения с БД.
Микро-вывод: Для защиты структурированных данных используйте sqlcipher или Hive с поддержкой шифрования, передавая ключ из Secure Storage.
Риски утечки данных через оперативную память
Безопасность на диске бессмысленна, если данные «текут» в памяти. В Dart объекты строк (String) неизменяемы, и при манипуляциях с паролями в памяти создаются временные копии, которые остаются там до срабатывания Garbage Collector. Это открывает возможность для Memory Dump атак.
Практика показывает, что для максимально чувствительных операций стоит минимизировать время жизни секрета в памяти: обнулять ссылки и использовать TypedData вместо строк там, где это возможно. Также стоит учитывать, что разработка мобильных приложений на Flutter в аспекте интеграции с нативным кодом позволяет вынести критические криптографические операции на сторону Swift/Kotlin, используя более низкоуровневые средства управления памятью.
Микро-вывод: Чем меньше времени секрет находится в памяти в виде String, тем ниже риск его перехвата через дамп памяти.
Защита от реверс-инжиниринга и обфускация
Шифрование бесполезно, если алгоритм извлечения ключа зашит в коде в открытом виде. Flutter компилирует код в машинный код (AOT), что сложнее для анализа, чем Java/Kotlin, но не невозможно. Использование флага --obfuscate при сборке скрывает имена классов и методов, затрудняя поиск функций, отвечающих за безопасность.
Кейс из практики: при аудите приложения было замечено, что статический ключ шифрования был жестко прописан в коде (hardcoded). С помощью простого декомпилятора и поиска по строкам ключ был найден за 10 минут. Правильный подход — генерация уникального ключа при первом запуске и его хранение в Keystore/Keychain.
Микро-вывод: Никогда не храните ключи шифрования в коде. Используйте обфускацию как базовый слой защиты, но не полагайтесь на неё как на основной метод безопасности.
Вывод
Безопасность данных во Flutter строится на многослойности: SharedPreferences — для настроек, Flutter Secure Storage — для токенов, SQLCipher — для БД. Моя экспертная рекомендация: начните с полного аудита всех точек записи данных. Избегайте хранения любых секретов в открытом виде и жестко зашитых ключей в коде. Оптимальный стек для защищенного приложения: flutter_secure_storage + sqlcipher + обязательная обфускация при сборке. Это обеспечит стандарт безопасности, достаточный для большинства финтех и корпоративных продуктов.
