Android как хранить токен

Безопасное хранение токена доступа в Android-приложении

Рубрика: Информационные технологии

Дата публикации: 02.09.2019 2019-09-02

Статья просмотрена: 711 раз

Библиографическое описание:

Лебёдкин, В. И. Безопасное хранение токена доступа в Android-приложении / В. И. Лебёдкин. — Текст : непосредственный // Молодой ученый. — 2019. — № 35 (273). — С. 11-14. — URL: https://moluch.ru/archive/273/62244/ (дата обращения: 06.12.2021).

Ключевые слова: аутентификация, протокол авторизации, токен доступа, уровень доступа, мобильное приложение, файл, файл настроек, база данных, менеджер учетных записей.

В наши дни различные онлайн-сервисы имеют разные способы обработки учетных записей и аутентификации. Многие из них построены на протоколе OAuth.

OAuth — открытый протокол авторизации, который позволяет предоставить третьей стороне ограниченный доступ к защищённым ресурсам пользователя без необходимости передавать ей логин и пароль [1]. Вместо передачи логина и пароля используется токен доступа.

Токен доступа — это уникальная последовательность символов, однозначно идентифицирующая пользователя. Зная эту последовательность, можно получить доступ к информации владельца токена.

Рис. 1. Получение токена доступа

Алгоритм взаимодействия Андроид приложения с веб-сервером (Рис. 1) для получения токена доступа выглядит следующим образом:

  1. Перенаправление пользователя на окно авторизации;
  2. Авторизация пользователя с мобильного приложения (заполнение формы);
  3. Запрос подтверждения прав на сервере;
  4. Перенаправление сервером на принимающую страницу;
  5. Мобильное приложение запрашивает токен доступа;
  6. Сервер выдает токен доступа приложению.

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

Операционная система Андроид предоставляет несколько инструментов для хранения данных:

  1. Shared preferences;
  2. Сохранение в файл;
  3. База данных;
  4. Account manager.

Shared preferences (файл общих настроек) представляет из себя хранилище данных, позволяющее хранить примитивные типы данных (целочисленные значения, строковые значения, символы, булевы переменные). Хранение производится по принципу «Ключ:Значение». Операционная система Андроид предоставляет инструменты, необходимые для чтения и записи данных в файл общих настроек.

Создавая/открывая файл общих настроек, необходимо указать уровень доступа:

  1. MODE_PRIVATE — файл созданный в данном режиме может использоваться только в создавшем его приложении (используется как режим по умолчанию);
  2. MODE_WORLD_READABLE — файл, созданный/открытый в этом режиме доступен для чтения для любого приложения;
  3. MODE_WORLD_WRITABLE — файл, созданный/открытый в этом режиме доступен для редактирования для любого приложения;
  4. MODE_MULTI_PROCESS — файл, созданный/открытый в этом режиме может использоваться одновременно несколькими потоками.

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

Режимы MODE_WORLD_READABLE и MODE_WORLD_WRITABLE не являются безопасными, так как дают доступ к файлу любому другому приложению.

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

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

Альтернативным способом хранения данных в файле является обычный файл. Его отличие от Shared Preferences состоит в том, что данные в нем хранятся в свободном виде, т. е. файл представляет из себя одну строку. Для хранения множества значений можно создать несколько файлов или продумать структуру, которая позволит хранить несколько значений.

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

Еще один способ хранения данных — база данных. База данных — хранилище, которое подходит для структурированных данных.

Чтобы записать токен доступа в базу данных необходимо проделать следующие шаги:

  1. Инициализировать базу данных;
  2. Инициализировать схему для базы данных;
  3. Создать таблицу;
  4. Сохранить запись.

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

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

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

Читайте также:  Csc what is this android

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

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

  1. E. Hammer-Lahav, Ed. The OAuth 1.0 Protocol // IETF. — 2010. — April. — ISSN 2070–1721, 1 p.

Источник

Токены в мире Android

Sep 10, 2018 · 4 min read

Дисклеймер: если вы знаете что такое токены и уже работали с ними, вам не сюда (либо в комментарии, буду рад замечаниям).

В современном мире, вопрос о хранении и защиты данных стоит наиболее остро. Http протокол, открытые wi-fi сети, DPI на стороне провайдера — все это дает возможность людям смотреть на данные, которые Вы можете слать из своего приложения и важно чтобы эти данные были понятны не всем.

Наше приложение обращается к стор о ннему сервису для обмена данными и защищенность этих данных обычно колеблется от plain-text до base64 в лучшем случае, просто потому что вопрос о необходимости шифрования не является приоритетным (надо быстрее фичи закрывать потому что).

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

Предположим, что мы все таки беспокоимся о данных, которые передаем и храним. Здесь надо понимать одну вещь:

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

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

Токены

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

“Совершать запросы от моего лица, хм, не думаю, что позволю это кому-то.” — это меньшее из зол и на самом деле не так страшно, как звучит. Если бы на каждый ваш запрос на получение новых мемасиков с пикабу от вас требовали бы пароль, вы скорее всего предпочли бы например 9gag (проплаченная реклама), хотя бы потому что там удобнее, не говоря уже о том, что с первого сервиса ваш пароль многократное количество раз будет улетать на сервер, вместо одного. С каждой последующей отправкой пароля, вы увеличиваете вероятность его компрометации злоумышленником, т.к. он часто мелькает в сети и его становится легко заметить. То же самое происходит и с токеном, но это не так страшно, ведь токен обезличен, представлен из набора букв и цифр и не несет никакой важной информации. Более того, токены живут не долго (30 минут, час, день, как решит сервер) и даже если злоумышленник узнает ваш токен и сможет совершать действия от вашего лица на этом ресурсе, это продлится не дольше 30 минут (времени жизни токена), а после доступ будет ограничен и токен надо будет обновить.

Для этого существует refresh-token , который живет значительно дольше access-token`а (1 неделю, месяц, год, как решит сервер) и используется для его обновления, вместо ввода пароля. В сети он мелькает один раз в неделю (время жизни refresh-token ), что существенно снижает вероятность его компрометации.

Если вдруг ваш access-token протух, приложение отправляет на сервис запрос на его обновление, прикрепляя к запросу refresh-token . Если сервер отвечает отказом, к примеру refresh-token тоже протух, стал невалиден или по другим причинам, требуется повторная авторизация с помощью email и пароля. Примерно так работает OAuth 2.0 которым мы пользуемся каждый день в современных приложениях (в том числе и пикабу, который я ради примера несправедливо ущемил. Хотя все же стоит помнить, что их базы данных не раз похищались, но это о другом)

Читайте также:  Построение маршрута по нескольким точкам для андроид

Типичный день OAuth2 сервера

  1. Получить пару логин и пароль
  2. Сгенерировать в ответ access-token и refresh-token
  3. Принимать ответы от авторизированной стороны

Достаточно просто и безопасно, что может пойти не так?

  • канал передачи данных не достаточно хорошо защищен (к примеру, используется http вместо https и ssl), и какой-нибудь Вася при вашей авторизации, сможет проснифать ваш трафик и увидеть там знакомое поле password=trololo.
  • email или пароль не зашифрован (передается как plain-text или base64), что позволяет его увидеть без особых трудностей любым первокурсником.
  • второкурсники могут проснифать ваше https соединение и увидеть там access-token, чтобы делать дела от вашего имени.
  • место где хранятся ваши access-token и refresh-token может быть уязвимым, и третьекурсник разобравшийся c shared-preferences знает как их достать, но вряд ли сможет (нужно сойтись большому количеству условий для этого: у shared-preferences должен быть выставлен не приватный доступ или ваш телефон должен быть рутованый для получения доступа к файловой-системе, должно быть установлено левое-приложение третьекурсника, которое будет делать злые дела, либо устройство должно попасть к нему в руки и т.д.)
  • ваше приложение не обфусцированно и провести его анализ при декомпиляции задачка тривиальная. Ищем где хранятся приватные ключи, изучаем критически важную информацию, в том числе работу алгоритмов шифрования или скрытый функционал (фича-тогглы, привет!)
  • ваше приложение обфусцировано, но включено логирование на релизной сборке, что позволит любой программе с правами READ_LOGS их собирать и анализировать, на наличие ключевых слов password, token, mamku-eval и т.д.

Что мы можем с этим сделать?

  • Например не хранить критически важную информацию на клиенте, делегировав ответственность третьей стороне (например, бекенду :3).
  • В том числе, не хранить на клиенте ключи шифрования, об этом написано в исчерпывающей статьей о хранении ключей в Android.
  • Отключать логирование на релизных сборках (в том числе и логирование на вашем OkHttp клиенте, вы это делаете?)
  • Настроить ProGuard, единожды прописав правила для каждой из зависимостей. Это не сложно, если делать это каждый раз при подключении новой зависимости. Если же вспомнить о необходимости обфусцировать код под конец разработки, можно очень неприятно потратить время на поиск правил и отладку кода, чтобы проверить что на каком-нибудь экране 4 уровня ничего не крашнется.

В целом и все. Пишите что не понятно, либо где я ошибся, буду рад обсуждениям.

Источник

Храним токены авторизации безопасно

Привет %username%. Меня, независимо от темы доклада, на конференциях постоянно спрашивают один и тот же вопрос — «как безопасно хранить токены на устройстве пользователя?». Обычно я стараюсь ответить, но время не позволяет полностью раскрыть тему. Этой статьей я хочу полностью закрыть этот вопрос.

Я проанализировал с десяток приложений, чтобы посмотреть как они работают с токенами. Все проанализированные мной приложения обрабатывали критичные данные и позволяли установить пин-код на вход в качестве дополнительной защиты. Давайте посмотрим на самые частые ошибки:

  • Отправка в API пин-кода вместе с RefreshToken, для подтверждения аутентификации и получения новых токенов. — Плохо, RefreshToken лежит незащищенный в локальном хранилище, при физическом доступе к устройству или бэкапу можно его извлечь, так же это может сделать малварь.
  • Сохранение пин-кода в сторэдж вместе с RefreshToken, далее локальная проверка пин-кода и отправка RefreshToken в API. — Кошмар, RefreshToken лежит незащищенный вместе с пином, что позволяет их извлечь, кроме того появляется еще один вектор предполагающий bypass локальной аутентификации.
  • Неудачное шифрование RefreshToken пин-кодом, которое позволяет восстановить из шифротекста пин-код и RefreshToken. — Частный случай предыдущей ошибки, эксплуатирующийся немного сложней. Но отметим, что это правильный путь.

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

Credentials — (логин + пароль) — используются для аутентификации пользователя в системе.
+ пароль никогда не хранится на устройстве и должен быть немедленно очищен из оперативной памяти после отправки в API
+ не передаются методом GET в query параметрах HTTP запроса, вместо этого используются POST запросы
+ кэш клавиатуры отключен для текстовых полей обрабатывающих пароль
+ буфер обмена деактивирован у текстовых полей, которые содержат пароль
+ пароль не разглашаются через пользовательский интерфейс (те используются звездочки), так же пароль не попадает в скриншоты

AccessToken — используется для подтверждения авторизации пользователя.
+ никогда не сохраняется в долговременную память и хранится только в оперативной памяти
+ не передаются методом GET в query параметрах HTTP запроса, вместо этого используются POST запросы

RefreshToken — используется для получения новой связки AccessToken+RefreshToken.
+ ни в каком виде не хранится в оперативной памяти и должен быть немедленно удален из нее после получения от API и сохранения в долговременную память или после получения из долговременной памяти и использования
+ хранится только в зашифрованном виде в долговременной памяти
+ шифруется пином с помощью магии и определенных правил (правила будут описаны ниже), те если пин не был установлен, то не сохраняем вообще
+ не передаются методом GET в query параметрах HTTP запроса, вместо этого используются POST запросы

Читайте также:  Android 5 поддержка моделей

PIN — (обычно 4 или 6 значное число) — используется для шифрования/дешифрования RefreshToken.
+ никогда и нигде не хранится на устройстве и должен быть немедленно очищен из оперативной памяти после использования
+ никогда не покидает пределы приложения, те никуда не передается
+ используется только для шифрования/дешифрования RefreshToken

OTP — одноразовый код для 2FA.
+ OTP никогда не хранится на устройстве и должен быть немедленно очищен из оперативной памяти после отправки в API
+ не передаются методом GET в query параметрах HTTP запроса, вместо этого используются POST запросы
+ кэш клавиатуры отключен для текстовых полей обрабатывающих OTP
+ буфер обмена деактивирован у текстовых полей, которые содержат OTP
+ OTP не попадает в скриншоты
+ приложение удаляет OTP с экрана, когда уходит в бэкграунд

Теперь перейдем к магии криптографии. Основное требование — ни при каких обстоятельствах нельзя допустить реализацию такого механизма шифрования RefreshToken, при котором можно провалидировать результат расшифровки локально. То есть, если злоумышленник завладел шифротекстом он не должен иметь возможности подобрать ключ. Единственным валидатором должно быть API. Это единственный способ ограничить попытки подбора ключа и заинвалидировать токены в случае Brute-Force атаки.

Я приведу наглядный пример, допустим мы хотим зашифровать UUID

таким набором AES/CBC/PKCS5Padding, в качестве ключа используя PIN. Вроде алгоритм хороший, все по гайдлайнам, но тут есть ключевой момент — ключ содержит крайне мало энтропии. Давайте посмотрим к чему это ведет:

    Padding — поскольку наш токен занимает 36 байт, а AES блочный режим шифрования с блоком 128 бит, то алгориму нужно добить токен до 48 байт (что кратно 128 битам). В нашем варианте хвост будет добавлен по стандарту PKCS5Padding, т.е. значение каждого добавляемого байта равняется количеству добавляемых байт

01
02 02
03 03 03
04 04 04 04
05 05 05 05 05
06 06 06 06 06 06
etc.

… | 61 62 66 65 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C |

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

  • Предикабельный формат токена — даже если сделаем наш токен кратным 128 битам (например убрав дефисы), чтобы избежать добавления паддинга, то мы наткнемся на следующую проблему. Проблема заключается в том, что все из той же сбрученой кучи мы можем собрать строки и определить какая из них попадает под формат UUID. UUID в своем каноническом текстовом виде представляет собой 32 цифры в шестнадцатеричном формате разделенных дефисом на 5 групп 8-4-4-4-12
    xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
    где M — это версия, а N — вариант. Всего этого вполне достаточно, чтобы отсеить расшифрованные неправильным ключом токены, оставив подходящий под формат UUID RefreshToken.
  • С учетом всего перечисленного выше можно переходить к реализации, я выбрал простой вариант сгенерировать 64 рандомных байта и завернуть их в base64:

    Вот пример такого токена:

    Теперь посмотрим как это выглядит алгоритмически (на Android и iOS алгоритм будет одинаковый):

    На какие строки стоит обратить внимание:

    Никакого padding, ну вы помните.

    Нельзя просто так взять и зашифровать токен в base64 представлении, тк это представление имеет определенный формат (ну вы помните).

    На выходе получим ключ размером AES_KEY_SIZE, пригодный для алгоритма AES. В качестве kdf можно использовать любую key derivation function, рекомендуемые Argon2, SHA-3, Scrypt в случае плохой жизни pbkdf2(очень хорошо параллелится на FPGA).

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

    Еще немного рекомендаций:

    • Исключите токены из бэкапов.
    • На iOS храните токен в keychain с атрибутом kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly.
    • Не раскидывайте ассеты, расмотренные в этой статье (key, pin, password и тд.) по всему приложению.
    • Затирайте ассеты сразу как они становятся не нужны, не держите их в памяти дольше чем необходимо.
    • Используйте SecureRandom в Android и SecRandomCopyBytes в iOS для генерации рандомных байт в криптографическом контексте.

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

    Источник

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