Offline data storage android

Хранение данных и файлов

В целом хранение файлов и данных можно условно разделить на две группы: во внутреннем или внешнем хранилище. Но разница между ними довольна тонка. В целом политика Гугла в отношение данных ужесточается с каждой версии системы.

Android поддерживает различные варианты хранения данных и файлов.

  • Специфичные для приложения файлы. Доступ к файлам имеет только приложение, их создавшее. Файлы могут находиться во внутреннем и внешнем хранилище. У других приложений нет доступа (кроме случаев, когда файлы хранятся на внешнем хранилище). Методы getFilesDir(), getCacheDir(), getExternalFilesDir(), getExternalCacheDir(). Разрешений на доступ не требуется. Файлы удаляются, когда приложение удаляется пользователем.
  • Разделяемое хранилище. Приложение может создавать файлы, которыми готово поделиться с другими приложениями — медиафайлы (картинки, видео, аудио), документы. Для медифайлов требуется разрешение READ_EXTERNAL_STORAGE или WRITE_EXTERNAL_STORAGE.
  • Настройки. Хранение простых данных по принципу ключ-значение. Доступно внутри приложения. Реализовано через Jetpack Preferences. Настройки удаляются, когда приложение удаляется пользователем.
  • Базы данных. Хранение данных в SQLite. На данный момент реализовано через библиотеку Room. Доступ только у родного приложения.

В зависимости от ваших потребностей, нужно выбрать нужный вариант хранения данных.

Следует быть осторожным при работе с внутренним и внешним хранилищем. Внутренне хранилище всегда есть в системе, но оно может быть не слишком большим по объёму. Вдобавок к внутреннему хранилищу, устройство может иметь внешнее хранилище. В старых моделях таким хранилищем выступала съёмная SD-карта. Сейчас чаще используют встроенную и недоступную для извлечения флеш-память. Если ваше приложение слишком большое, можно попросить систему устанавливать программу во внешнее хранилище, указав просьбу в манифесте.

В разных версиях Android требования к разрешению для работы с внешним хранилищем постоянно менялись. На данный момент (Android 10, API 29) требования выглядят следующим образом.

Приложение может иметь доступ к собственным файлам, которые находятся во внешнем хранилище. Также может получить доступ к определённым общим файлам на внешнем хранилище.

Доступ к общим файлам достигается через FileProvider API или контент-провайдеры.

Для просмотра файлов через студию используйте инструмент Device File Explorer.

Внешняя карта памяти

Когда появились первые устройства на Android, то практически у всех были внешние карточки памяти, которые вставлялись в телефон. Обычно там хранили фотки, видео и свои файлы. Всё было понятно — были различные методы для доступа к файловой системе. А потом началась чехарда. В телефонах также была и собственная «внешняя» память. Она вроде как и внешняя, но вставлена на заводе и вытащить её пользователь не мог, т.е. практически внутренняя. Затем пошла мода на телефоны, у которых была только такая внутреннее-внешняя карта. Пользователи поворчали, но привыкли. Сейчас встречаются оба варианта. Как правило, у телефонов с спрятанной картой больше памяти и выше степень водонепроницаемости.

Подобные фокусы с картой породили и другую проблему — Гугл озаботился безопасностью файлов и стала думать, как осложнить жизнь разработчику. С выходом каждой новой версии системы компания то давала добро на полный доступ к карточке, то ограничивала, то давала права с ограничениями, то откатывала свои решения назад. Короче, запутались сами и запутали всех.

Попробуем немного разобраться с этим зоопарком. Но помните, что процесс путаницы продолжается.

При подготовке материала я опирался на письма некоторых читателей сайта, которые присылали свои мысли по этому поводу. Спасибо им за структуризацию материала.

Вот что я (кажется) понял, попытавшись загрузить картинку с внешней SD карточки.

External это не External
«EXTERNAL_STORAGE» называется так не потому, что это внешняя память по отношению к устройству, а потому что она выглядит как внешняя память для компьютера, если устройство подключить кабелем к компьютеру. Причём именно выглядит, потому что обмен идёт по протоколу MTP – устройство только показывает компьютеру список папок и файлов, а при необходимости открыть или скопировать файл он специально загружается на компьютер, в отличие от настоящей флешки, файлы которой становятся файлами в файловой системе самого компьютера. Обмен по MTP позволяет устройству продолжать работать, когда оно подключено к компьютеру.

Emulated это не Emulated
Сначала я пытался прочесть файл с карточки на эмуляторе (из этого так ничего и не вышло). Функция getExternalStorageDirectory() давала мне /storage/emulated/0, и я думал, что «emulated» – это потому что на эмуляторе. Но когда я подцепил реальный планшет, слово «emulated» никуда не исчезло. Я стал рыться в интернете и обнаружил, что «Emulated storage is provided by exposing a portion of internal storage through an emulation layer and has been available since Android 3.0.» – то есть это просто кусок внутренней памяти, которая путём какой-то эмуляции делается доступной для пользователя, в отличие от собственно внутренней памяти.

Читайте также:  Определитель номера для андроида от яндекса

При этом с точки зрения системы доступная для пользователя папка называется /storage/emulated/0, а при подключении к компьютеру по USB это просто одна из двух главных папок устройства – у меня в Windows Explorer она называется Tablet. Вторая папка у меня называется Card, и это и есть настоящая внешняя карточка.

Нет стандартных средств добраться из приложения до файлов на внешней карточке. Все попытки добраться до настоящей внешней карточки делаются с помощью неких трюков. Самое интересное, что я нашел, это статья на http://futurewithdreams.blogspot.com/2014/01/get-external-sdcard-location-in-android.html — парень читает таблицу смонтированных устройств /proc/mounts, таблицу volume daemons /system/etc/vold.fstab, сравнивает их и выбирает те тома, которые оказываются съёмными (с помощью Environment.isExternalStorageRemovable()).

Оказалось, что несистемным приложениям в принципе запрещено напрямую обращаться к съёмной карточке! Похоже, что это было так всегда, но вот начиная с версии Android 6 Marshmallow написано: внешняя карточка может быть определена как Portable либо Adoptable. Adoptable – это как бы «усыновляемая» память которая может быть «adopted», то есть взята в систему (примерно как кот с улицы в дом – это тоже называется to adopt) и использована как внутренняя. Для этого ее надо особым образом отформатировать и не вынимать, иначе не факт, что система продолжит нормально работать.

Portable – это нормальная съёмная карточка, но несистемным приложениям запрещено обращаться из программ к файлам на ней! Вот что написано в https://source.android.com/devices/storage/traditional.html:

Android 6.0 supports portable storage devices which are only connected to the device for a short period of time, like USB flash drives. When a user inserts a new portable device, the platform shows a notification to let them copy or manage the contents of that device. In Android 6.0, any device that is not adopted is considered portable. Because portable storage is connected for only a short time, the platform avoids heavy operations such as media scanning. Third-party apps must go through the Storage Access Framework to interact with files on portable storage; direct access is explicitly blocked for privacy and security reasons.

Если я правильно понял, этот самый Storage Access Framework позволяет работать с документом на карточке через диалог (открыть файл/сохранить файл), а вот прочитать или записать файл на карточке непосредственно из программы невозможно.

Общий вывод – реально из программы можно работать только с файлами на предоставляемой пользователю части встроенной памяти устройства, а на съёмной карточке – нет.

Это напоминает войну Microsoft с пользователями и разработчиками по поводу диска C:, компания уговаривала не устраивать беспорядок в корне этого диска, а ещё лучше — перенести свои файлы на другой диск. Но явных запретов не было.

Состояние на текущий момент

Гугл утверждает, что с версии Android 10 Q стандартный доступ к файлам будет прекращён. Ещё в Android 4.4 появился Storage Access Framework, который и должен стать заменой для работы с файлами.

Методы Environment.getExternalStorageDirectory() и Environment.getExternalStoragePublicDirectory() признаны устаревшими и будут недоступны. Даже если они будут возвращать корректные значения, ими вы не сможете воспользоваться.

В Android 7.0 добавили исключение FileUriExposedException, чтобы разработчики перестали использовать схему file://Uri.

Можно создавать файлы в корневой папке карточки при помощи Environment.getExternalStorageDirectory(), а также папки с вложенными файлами. Если папка уже существует, то у вас не будет доступа на запись (если это не ваша папка).

Если вы что-то записали, то сможете и прочитать. Чужое читать нельзя.

Кстати, разрешения на чтение и запись файлов не требуются, а READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE объявлены устаревшими.

Другие приложения не могут получить доступ к файлам вашего приложения. Файлы, которые вы создали через getExternalFilesDir(), доступны через Storage Access Framework, кроме файлов, созданных в корне карточки (что-то я совсем запутался). Ещё можно дать доступ через FileProvider.

При подключении USB-кабеля через getExternalFilesDir(), вы можете увидеть свои файлы и папки, а также файлы и папки пользователя. При этом файлы и папки пользователя на корневой папке вы не увидите. Вам не поможет даже adb или Device File Explorer студии.

Что делать?

Пользуйтесь методами класса Context, типа getExternalFilesDir(), getExternalCacheDir(), getExternalMediaDirs(), getObbDir() и им подобными, чтобы найти место для записи.

Используйте Storage Access Framework.

Используйте MediaStore для мультимедийных файлов.

Используйте FileProvider, чтобы файлы были видимы другим приложениям через ACTION_VIEW/ACTION_SEND.

Android 10: Появился новый флаг android:allowExternalStorageSandbox=»false» и метод Environment.isExternalStorageSandboxed() для работы с песочницей. Флаг android:requestLegacyExternalStorage=»true» для приложений, которые ещё используют старую модель доступа к файлам.

Как временное решение можно добавить в блок манифеста application атрибут android:requestLegacyExternalStorage=»true», чтобы доступ к файлам был как раньше в Android 4.4-9.0.

Android 11

Если вы создаёте файловый менеджер, то ему нужны возможности для просмотра файлов. Для этого следует установить разрешение MANAGE_EXTERNAL_STORAGE или использовать атрибут android:requestLegacyExternalStorage=»true» (см. выше).

Источник

Безопасное хранение данных на Android

Russian (Pусский) translation by Ellen Nelson (you can also view the original English article)

Читайте также:  Сканер призраков для андроид

Сегодня надежность приложения зависит от того, как относятся к личным данным пользователя. В стеке Android есть много мощных API-интерфейсов, связанных с учетными данными и хранилищем ключей, причем определенные функции доступны только в определенных версиях. Эта короткая серия начнётся с простого подхода к тому, как работает система хранения, и как шифровать и хранить конфиденциальные данные через предоставленный пользователем код доступа. На втором уроке мы рассмотрим более сложные способы ключей защиты и учетных данных.

Основы

Первый вопрос, о котором стоит подумать — это то, о скольких данных вам придётся позаботиться. Хорошим вариантом будет — не хранить личные данные, если это действительно не нужно.

С данными, которые вам необходимо сохранить, архитектура Android готова помочь. Начиная с 6.0 Marshmellow, шифрование всего диска включено по умолчанию, для устройств с этой возможностью. Для файлов и SharedPreferences , которые сохранены приложением, автоматически устанавливается постоянная MODE_PRIVATE . Это означает, что доступ к данным может получить только ваше приложение.

Неплохо бы придерживаться того, что есть по-умолчанию. Вы можете явно указать это, при сохранении общих настроек.

Или при сохранении файла.

Избегайте хранения данных на внешних хранилищах, так как в этом случае данные смогут просматривать другие приложения и пользователи. По факту, чтобы усложнить копирование исполнительных (двоичных) файлов и данных вашего приложения, вы можете не позволять пользователям устанавливать приложение на внешнее хранилище. Чтобы следать это, добавьте android:installLocation со значением internalOnly в manifest файл.

Также, вы можете предотвратить создание резервных копий вашего приложения и его данных. Это также предотвратит скачивание содержимого каталога приложения с приватными данными, через adb backup . Чтобы сделать это, установите атрибут android:allowBackup в состояние false в файле manifest. По молчанию этот атрибут установлен на true .

Это лучшие методы, но они не будут работать для взломанного или рутованного устройства, а шифрование диска полезно только тогда, когда устройство защищено экраном блокировки. Вот где полезно использовать пароль на приложение, который защищает его данные с помощью шифрования.

Защита данных пользователя паролем

Conceal — отличный выбор в качестве библиотеки шифрования, потому что это позволит вам быстро начать и работать, не беспокоясь о попутных деталях. Тем не менее, эксплойт, нацеленный на популярный фреймворк, одновременно повлияет на все приложения, работающие на нём.

Также важно быть осведомлённым о том, как работают системы шифрования, чтобы иметь возможность сказать, надёжно ли вы используете конкретную инфраструктуру (фреймворк). Итак, в этом уроке мы запачкаем руки, конкретно рассмотрев поставщика криптографии.

AES и ключ на основе пароля

Мы будем использовать рекомендованный стандарт AES, который шифрует данные с помощью ключа. Один и тот же ключ, используемый и для шифрования данных, и для их дешифрования, это то, что называется симметричным шифрованием. Существуют разные размеры ключей и AES256 (256 бит) является предпочтительной длиной для использования с конфиденциальными данными.

Хотя пользовательский интерфейс вашего приложения должен заставить пользователя использовать надёжный пароль, есть вероятность, что один и тот же пароль будет выбран другим пользователем. Оставлять безопасность наших зашифрованных данных на совесть пользователя — небезопасно. Вместо этого наши данные должны быть защищены ключом, который будет случайным и достаточно большим (т.е. иметь достаточную энтропию (непредсказуемость)), чтобы считаться сильным. Вот почему никогда не рекомендуется использовать пароль напрямую для шифрования данных, вот где вступает в игру функция, называемая Password-Based Key Derivation Function (PBKDF2).

PDKDF2 извлекает ключ из пароля, хешируя (путая, мешая) его много раз с «солью». Это называется растягивание ключа. «Соль» — это просто случайная последовательность данных и делает извлечённый ключ уникальным, даже если тот же пароль был использован кем-то другим. Давайте начнём с создания этой «соли».

Класс SecureRandom гарантирует, что сгенерированный вывод будет трудно предсказать — это «криптографически сильный генератор случайных чисел». Теперь мы можем поместить «соль» и пароль в защищённый паролем объект шифрования: PBEKeySpec . Конструктор объекта также принимает форму счётчика повторений (итераций), делая ключ сильнее. Это связано с тем, что увеличение количества повторов увеличивает время, затрачиваемое на работу с набором ключей во время атаки по типу «brute force» (метод «грубой силы» или полный перебор значений). Затем PBEKeySpec передаётся в SecretKeyFactory , который, наконец, генерирует ключ как массив byte[] . Мы перенесём этот массив byte[] в объект SecretKeySpec , как есть.

Обратите внимание, что пароль передаётся как массив char[] , и класс PBEKeySpec сохраняет его так же, как массив char[] . Массив char[] обычно используются для функций шифрования, потому что класс String неизменяем, а массив char[] , содержащий конфиденциальную информацию, может быть перезаписан, что позволяет полностью удалить конфиденциальные данные из физической памяти устройства (RAM).

Векторы инициализации

Теперь мы готовы зашифровать данные, но есть ещё одна вещь. Существуют различные способы шифрования AES, но мы будем использовать рекомендуемый вариант: последовательность блочного шифра — cipher block chaining (CBC). Это работает по нашим данным по одному блоку за раз. Самое замечательное в этом методе заключается в том, что каждый следующий незашифрованный блок данных сцепляется по методу XOR с предыдущим зашифрованным блоком, чтобы сделать шифрование более сильным. Однако это означает, что первый блок никогда не бывает таким уникальным, как все остальные!

Читайте также:  Ошибка спулера печати андроид

Если сообщение, которое нужно зашифровать, должно начинаться с того же самого, что и другое зашифрованное сообщение, начальный зашифрованный вывод будет таким же, и это даст злоумышленнику ключ к выяснению того, какое же это сообщение. Решением является использование вектора инициализации (IV).

IV — это всего лишь блок случайных байтов, который будет смешиваться по XOR с первым блоком пользовательских данных. Поскольку каждый блок зависит от всех блоков, обработанных до этой точки, всё сообщение будет зашифровано идентично, а идентичные сообщения, зашифрованные одним и тем же ключом, не дадут одинаковых результатов. Давайте теперь создадим IV.

Примечание о SecureRandom . В версиях 4.3 и ниже, архитектура криптографии Java имела уязвимость из-за неправильной инициализации базового генератора псевдослучайных чисел (PRNG). Если вы нацелены на версии 4.3 и ниже, доступно исправление.

Шифрование данных

Вооружившись IvParameterSpec , мы можем теперь делать действительно шифрование.

Здесь мы передаём строку «AES/CBC/PKCS7Padding» . Это указывает на шифрование AES с последовательностью блочного шифра. Последняя часть этой строки указывает на PKCS7, что является стандартом для заполнения данных, которые не вписываются в размер блока. (Блоки 128 бит, а заполнение выполняется до шифрования).

Чтобы завершить наш пример, мы поместим этот код в метод шифрования, который упакует результат в HashMap , содержащий зашифрованные данные, вместе с солью и вектором инициализации, необходимым для дешифрования.

Метод дешифрования

С вашими данными нам нужно хранить только IV и «соль». Хотя «соль» и IV считаются общедоступными, убедитесь, что они не последовательно инкрементируются или используются повторно. Чтобы расшифровать данные, всё, что нам нужно сделать, это изменить режим в конструкторе Cipher с ENCRYPT_MODE на DECRYPT_MODE . Метод дешифрования возьмёт HashMap , который содержит ту же требуемую информацию (зашифрованные данные, «соль» и IV) и вернёт дешифрованный массив byte[] , передающий правильный пароль. Метод дешифрования восстановит ключ шифрования из пароля. Ключ никогда не должен сохраняться!

Тестирование шифрования и дешифрования

Чтобы упростить пример, мы исключаем проверку ошибок на то, что HashMap содержит необходимые пары ключей и значений. Теперь мы можем протестировать наши методы, чтобы гарантировать, что данные будут правильно дешифрованы после шифрования.

Эти методы используют массив byte[] , чтобы вы могли шифровать произвольные данные, а не только объекты String .

Сохранение зашифрованных данных

Теперь, когда у нас есть зашифрованный массив byte[] , мы можем сохранить его в хранилище.

Если вы не хотите сохранять IV и «соль» отдельно, то HashMap сериализуется с классами ObjectInputStream и ObjectOutputStream .

Сохранение безопасных данных в SharedPreferences

Вы также можете сохранить защищенные данные в SharedPreferences вашего приложения.

Поскольку SharedPreferences представляет собой XML-структуру, которая принимает только определённые примитивы (встроенный тип данных) и объекты в качестве значений, нам необходимо преобразовать наши данные в совместимый формат, такой как объект String . Base64 позволяет преобразовать необработанные данные в представление String , которое содержит только символы, разрешённые XML-форматом. Зашифруйте ключ и значение так, чтобы злоумышленник не смог понять, для чего может быть это значение. В приведённом выше примере, оба, и encryptedKey и encryptedValue являются зашифрованными массивами byte[] , возвращаемыми из нашего метода encryptBytes() . IV и «соль» можно сохранить в файле настроек или в виде отдельного файла. Чтобы вернуть зашифрованные байты из SharedPreferences , мы можем применить декодер Base64 над сохранённой String .

Очистка небезопасных данных для старых версий

Теперь, когда данные в надежно сохранены, может случиться так, что в ранней версии приложения остались данные в небезопасном хранилище. С обновлением, эти данные можно уничтожить и перешифровать. Следующий код сбрасывает файл, использующий случайные данные.

Теоретически вы можете просто удалить общие настройки, удалив файлы /data/data/com.your.package.name/shared_prefs/your_prefs_name.xml и your_prefs_name.bak, затем очистить настройки из памяти следующим кодом:

Однако, вместо того, чтобы пытаться стереть старые данные и надеяться, что это сработает, лучше всего, в первую очередь, их зашифровать! Это особенно актуально для твердотельных накопителей, которые часто распространяют записи данных по разным регионам для предотвращения износа. Это означает, что даже если вы перезапишете файл в файловой системе, физическая твердотельная память может сохранить ваши данные в исходном местоположении на диске.

Выводы

На этом закончим наш урок по хранению зашифрованных данных. Из него вы узнали, как безопасно шифровать и дешифровать конфиденциальные данные с помощью пароля, предоставленного пользователем. Это легко сделать, когда вы знаете, как это сделать, но важно следовать лучшим методам, для обеспечения надёжной защиты данных ваших пользователей.

В следующем уроке мы рассмотрим, как использовать KeyStore и другие API-интерфейсы, связанные с учётными данными, для безопасного хранения элементов. В то же время, ознакомьтесь с некоторыми из наших замечательных материалов о разработке приложений для Android.

Источник

Оцените статью