- Ключи, учетные данные и хранилище на Android
- Диспетчер учётных записей
- Keychain — связка ключей
- KeyStore
- Генерирование нового случайного ключа
- Шифрование данных
- Расшифровка в массив байтов
- Смотрим на примере
- Использование асимметричных ключей RSA для старых устройств
- Заключение
- Лучшие практики для хранения и защиты закрытых ключей API в приложениях [закрыто]
- Старый необеспеченный путь:
- Обновить :
Ключи, учетные данные и хранилище на Android
Russian (Pусский) translation by Ellen Nelson (you can also view the original English article)
В предыдущем материале о безопасности пользовательских данных Android мы рассмотрели шифрование данных с помощью предоставленного пользователем кода. В этом уроке перейдём к хранению учётных данных и ключей. Я начну с ознакомления с учётными данными и закончу примером защиты данных с помощью хранилища ключей KeyStore.
Часто, при работе со сторонней службой, требуется какая-то форма авторизации. Она может быть настолько простой, как /login на стороне пользователя, которая принимает имя пользователя и пароль.
Сначала может показаться, что простое решение — это собрать UI, который будет предлагать пользователю войти в систему, а затем записать и сохранить их данные для входа. Однако, это не лучший метод, так как нашему приложению не нужно знать данные для входа в сторонний аккаунт. Вместо этого, мы можем использовать Диспетчер учётных записей (Account Manager), который уполномочен отрабатывать эту конфиденциальную информацию для нас.
Диспетчер учётных записей
Диспетчер учётных записей (Account Manager) — это централизованный помощник для работы с учётными данными пользователя, поэтому вашему приложению, иметь дело с паролями напрямую не нужно. Часто он предоставляет токен вместо реального имени пользователя и пароля, который можно использовать для выполнения аутентифицированных запросов к службе. Например, при запросе токена OAuth2.
Иногда, вся необходимая информация уже хранится на устройстве, а иногда Account Manager придётся обращаться к серверу за новым токеном. Вы, наверное, видели раздел Учётные записи в Настройках вашего устройства для разных приложений. И вот как, мы можем получить список доступных учётных записей:
Этому коду потребуется разрешение android.permission.GET_ACCOUNTS . Если вы ищете определённую учётную запись, вы можете найти её вот так:
Как только найдёте учётную запись, её токен можно получить вызвав метод getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler) . Затем, таки можно использовать как для авторизированных запросов API к сервису. Это может быть RESTful API, в котором вы передаёте параметр токена во время HTTPS-запроса без необходимости знать детали личной учётной записи пользователя.
Так как, у каждой службы будет свой способ проверки подлинности и хранения личных учётных данных, Диспетчер учётных записей предоставляет модули проверки подлинности для реализации сторонней службой. Хотя Android может выполнять вход на многие популярные сервисы, для вашего приложения, вы можете написать свой собственный обработчик авторизации учётной записи и хранилища учётных данных. Это позволит убедиться, что учётные данные зашифрованы. Имейте также в виду, что учётные данные в Диспетчере учётных записей, которые используются другими службами, могут храниться в виде открытого текста, что делает их видимыми для любого, кто имеет рут-права на своё устройство.
Временами вам нужно будет иметь дело с ключами или сертификатами для отдельного лица или сущности, вместо просто данных для входа. Например, когда стороннее приложение отправляет вам файл сертификата, который вам нужно сохранить. Самый распространённый сценарий — это когда приложению нужно авторизироваться на сервере частной организации.
В следующем уроке, мы рассмотрим использование сертификатов для аутентификации и безопасной связи, ну а пока, я всё же хочу рассмотреть, как хранить эти элементы. Изначально Keychain API был создан для очень конкретного использования — установка закрытого ключа или пары сертификатов из файла PKCS#12.
Keychain — связка ключей
Представленный в Android 4.0 (API Level 14), Keychain API управлял ключами. В частности, это работает с объектами PrivateKey и X509Certificate и обеспечивает более безопасный контейнер, чем использование хранилища данных вашего приложения. Связано это с тем, что разрешения закрытых ключей открывают доступ к ключам только вашему приложению и только после авторизации пользователя. Это означает, что, прежде чем, вы сможете использовать хранилище учётных данных, на устройстве должен быть настроен экран блокировки. Кроме того, объекты в связке ключей можно объединить с защитой от оборудования, если доступно.
Код установки сертификата выглядит следующим образом:
Пользователю будет предложено ввести пароль для доступа к закрытому ключу и указать имя сертификата. Для получения ключа, в следующем коде представлен пользовательский интерфейс, который позволяет пользователю выбирать ключ из списка установленных ключей.
Как только выбор сделан, возвращается строка с названием псевдонима alias(final String alias) , где вы можете напрямую получить доступ к закрытому ключу или цепочке сертификатов.
Вооружившись этими знаниями, теперь давайте посмотрим, как мы можем использовать хранилище учётных данных для сохранения конфиденциальных данных.
KeyStore
В предыдущем уроке, мы рассмотрели защиту данных с помощью предоставляемого пользователем пароля. Такой вариант хорош, но требования к приложениям часто уводят от того, чтобы пользователи каждый раз входили в систему и запоминали дополнительный пароль.
Вот где можно использовать KeyStore API. Начиная с API 1, KeyStore используется системой для хранения учётных данных WiFi и VPN. Начиная с 4.3 (API 18), вы можете работать с асимметричными ключами конкретного приложения, а в Android M (API 23) можно хранить симметричный ключ AES. Таким образом, хотя API не позволяет хранить конфиденциальные строки напрямую, эти ключи можно сохранить, а затем использовать для шифрования строк.
Преимущество хранения ключа в хранилище ключей заключается в том, что он позволяет работать с ключами без раскрытия секретного содержимого этого ключа; данным ключа не место в приложении. Помните, что ключи защищаются разрешениями, так что только ваше приложение может получить к ним доступ, и они могут быть дополнительно защищены аппаратным обеспечением, если устройство поддерживает это. Создаётся контейнер, который усложняет извлечение ключей с устройства.
Генерирование нового случайного ключа
В этом примере вместо генерации ключа AES из предоставленного пользователем пароля мы можем автоматически сгенерировать случайный ключ, который будет защищён в хранилище ключей KeyStore. Мы можем сделать это, создав экземпляр KeyGenerator , настроенного на поставщика «AndroidKeyStore» .
Здесь важно обратить внимание на спецификации .setUserAuthenticationRequired(true) и .setUserAuthenticationValidityDurationSeconds(120) . Для этого, обязательно должна быть включена блокировка экрана и ключ должен быть заблокирован, до тех пор, пока пользователь не аутентифицируется.
Изучив документацию .setUserAuthenticationValidityDurationSeconds() , вы увидите, что это означает, что ключ доступен только через определённое количество секунд после аутентификации по паролю, и что для передачи -1 требуется идентификация по отпечатку пальца каждый раз, когда вы хотите получить доступ к ключу. Включение требования для аутентификации также приводит к отзыву ключа, когда пользователь удаляет или изменяет экран блокировки.
Поскольку хранение незащищённого ключа вместе с зашифрованными данными, это как прятать ключ от дома под половик, эти параметры пытаются защитить ключ в состоянии покоя в случае взлома устройства. Примером может служить автономный дамп данных устройства. Если пароль устройства не известен, эти данные, по сути, бесполезны.
Опция .setRandomizedEncryptionRequired(true) включает требование о наличии достаточного количества случайных чисел (каждый раз новый случайный ВИ [вектор инициализации]), чтобы при повторном шифровании одних и тех же данных, зашифрованный результат всё равно не повторялся. Это не позволяет злоумышленнику получить информацию о зашифрованном тексте на основе передачи тех же данных.
Ещё стоит отметить — setUserAuthenticationValidWhileOnBody(boolean remainsValid) , что блокирует ключ, как только устройство обнаружит, что он больше не принадлежит человеку.
Шифрование данных
Теперь, когда ключ хранится в хранилище KeyStore, мы можем создать метод, который зашифрует данные с использованием объекта Cipher , учитывая SecretKey . Это вернёт HashMap , содержащий зашифрованные данные и случайный ВИ, который понадобится для расшифровки данных. Зашифрованные данные вместе с ВИ могут быть сохранены в файл или в открытых настройках.
Расшифровка в массив байтов
Для расшифровки применяется обратный ход. Объект Cipher инициализируется с использованием константы DECRYPT_MODE , и возвращается расшифрованный массив byte[] .
Смотрим на примере
Теперь мы можем посмотреть на пример!
Использование асимметричных ключей RSA для старых устройств
Это хорошее решение для хранения данных в версии M и выше, но что, если ваше приложение поддерживает более ранние версии? Хотя симметричные ключи AES не поддерживаются в M, поддерживаются асимметричные ключи RSA. Это означает, что для достижения того же результата, мы можем использовать RSA ключи и шифрование.
Основное отличие заключается в том, что асимметричная пара ключей содержит два ключа: закрытый и открытый ключ, где открытый ключ шифрует данные, а закрытый ключ расшифровывает их. KeyPairGeneratorSpec передаётся в KeyPairGenerator , который инициализируется с помощью KEY_ALGORITHM_RSA и поставщика AndroidKeyStore .
Для шифрования, из пары ключей мы получаем RSAPublicKey и используем его с объектом Cipher .
Расшифровка выполняется с использованием объекта RSAPrivateKey .
Кое-что об RSA — шифрование медленнее, чем в AES. Для небольших объёмов информации, например, когда вы защищаете строки общих настроек, это не страшно. Если вы обнаружите проблему с производительностью при шифровании больших объёмов данных, то вместо этого вы можете использовать данный пример для шифрования и хранения только ключа AES. И тогда, для остальной части ваших данных, используйте более быстрое шифрование AES, которое обсуждалось в предыдущем уроке. Вы можете сгенерировать новый AES ключ и преобразовать его в массив byte[] , который совместим с этим примером.
Чтобы получить ключ, сделайте вот так:
Довольно много кода! Для простоты примеров, я пропустил обработку исключений. Но помните, что для итогового кода не рекомендуется просто перехватывать все случаи Throwable в одном операторе catch.
Заключение
На этом урок по работе с учётными данными и ключами завершён. Большая часть неразберихи вокруг ключей и хранилища связана с эволюцией ОС Android, но вы можете выбрать, какое решение использовать, учитывая уровень API, поддерживаемый вашим приложением.
Теперь, когда мы рассмотрели лучшие примеры защиты данных в состоянии покоя, следующий урок будет сосредоточен на защите данных при передаче.
Источник
Лучшие практики для хранения и защиты закрытых ключей API в приложениях [закрыто]
Хотите улучшить этот вопрос? Обновите вопрос, чтобы ответить на него фактами и цитатами, отредактировав этот пост .
Закрыт 10 месяцев назад .
Большинство разработчиков приложений интегрируют в свои приложения сторонние библиотеки. Если это для доступа к службе, например Dropbox или YouTube, или для регистрации сбоев. Количество сторонних библиотек и сервисов просто поражает. Большинство этих библиотек и сервисов интегрированы путем какой-либо аутентификации с сервисом, в большинстве случаев это происходит через ключ API. В целях безопасности сервисы обычно генерируют открытый и закрытый, часто называемый секретным ключом. К сожалению, для подключения к сервисам этот закрытый ключ должен использоваться для аутентификации и, следовательно, вероятно, быть частью приложения. Само собой разумеется, что это сталкивается с огромной проблемой безопасности. Открытые и закрытые ключи API могут быть извлечены из APK за считанные минуты и могут быть легко автоматизированы.
Предполагая, что у меня есть что-то похожее на это, как я могу защитить секретный ключ:
Какой, по вашему мнению, лучший и самый безопасный способ хранения закрытого ключа? Запутывание, шифрование, как вы думаете?
В действительности ваше скомпилированное приложение содержит ключевые строки, а также имена констант APP_KEY и APP_SECRET. Извлечение ключей из такого самодокументируемого кода тривиально, например, с помощью стандартного инструмента Android dx.
Вы можете применить ProGuard. Это оставит ключевые строки без изменений, но удалит имена констант. Он также будет переименовывать классы и методы с короткими бессмысленными именами, где это возможно. Затем для извлечения ключей требуется больше времени, чтобы выяснить, какая строка служит какой цели.
Обратите внимание, что настройка ProGuard не должна быть такой сложной, как вы боитесь. Для начала вам нужно только включить ProGuard, как описано в project.properties. Если есть какие-либо проблемы со сторонними библиотеками, вам может потребоваться скрыть некоторые предупреждения и / или предотвратить их запутывание в proguard-project.txt. Например:
Это подход грубой силы; Вы можете уточнить такую конфигурацию, как только обработанное приложение заработает.
Вы можете запутать строки вручную в своем коде, например, с помощью кодировки Base64 или, предпочтительно, с более сложным; возможно даже родной код. Затем хакеру придется статически пересмотреть кодировку или динамически перехватить декодирование в нужном месте.
Вы можете применить коммерческий обфускатор, как специализированный брат ProGuard DexGuard . Он может дополнительно зашифровать / запутать строки и классы для вас. Извлечение ключей требует еще больше времени и опыта.
Возможно, вы сможете запустить части своего приложения на своем собственном сервере. Если вы можете хранить ключи там, они в безопасности.
В конце концов, вы должны сделать экономический компромисс: насколько важны ключи, сколько времени или программного обеспечения вы можете себе позволить, насколько изощренны хакеры, которые заинтересованы в ключах, сколько времени они захотят получить. потратить, сколько стоит задержка перед взломом ключей, в каком масштабе успешные хакеры будут распространять ключи и т. д. Небольшие фрагменты информации, такие как ключи, защитить труднее, чем целые приложения. По сути, ничто на стороне клиента не может быть сломано, но вы, безусловно, можете поднять планку.
(Я разработчик ProGuard и DexGuard)
Мало идей, на мой взгляд, только первая дает некоторую гарантию:
Храните свои секреты на каком-то сервере в Интернете, а при необходимости просто берите их и используйте. Если пользователь собирается использовать Dropbox, ничто не мешает вам сделать запрос на ваш сайт и получить ваш секретный ключ.
Поместите свои секреты в код jni, добавьте переменный код, чтобы сделать ваши библиотеки больше и сложнее декомпилировать. Вы также можете разбить строку ключа на несколько частей и хранить их в разных местах.
используйте obfuscator, также поместите в хешированный код секретный код, а затем при необходимости используйте его.
Поместите свой секретный ключ как последние пиксели одного из ваших изображений в ресурсы. Затем при необходимости прочитайте это в своем коде. Запутывание вашего кода должно помочь скрыть код, который будет его читать.
Если вы хотите быстро взглянуть на то, как легко читать ваш apk-код, тогда скачайте APKAnalyser:
Другой подход заключается в том, чтобы вообще не иметь секрета на устройстве! См. Техника безопасности мобильного API (особенно часть 3).
Используя проверенную временем традицию косвенного обращения, поделитесь секретом между конечной точкой API и службой аутентификации приложения.
Когда ваш клиент хочет выполнить вызов API , он запрашивает у службы аутентификации приложения его аутентификацию (используя строгие методы удаленной аттестации), и он получает ограниченный по времени (обычно JWT ) токен, подписанный секретом.
Маркер отправляется при каждом вызове API, где конечная точка может проверить свою подпись, прежде чем действовать по запросу.
Фактический секрет никогда не присутствует на устройстве; на самом деле, приложение никогда не имеет представления о том, является ли оно действительным или нет, оно просто выполняет аутентификацию запросов и передает полученный токен. Хорошая выгода от косвенного обращения, если вы когда-нибудь захотите изменить секрет, вы можете сделать это, не требуя от пользователей обновления своих установленных приложений.
Так что, если вы хотите защитить свой секрет, то, прежде всего, не иметь его в своем приложении.
Старый необеспеченный путь:
Выполните 3 простых шага для защиты API / секретного ключа ( старый ответ )
Мы можем использовать Gradle для защиты ключа API или секретного ключа.
1. gradle.properties (Свойства проекта): создать переменную с ключом.
2. build.gradle (Module: app): Установите переменную в build.gradle, чтобы получить доступ к ней в действии или фрагменте. Добавьте приведенный ниже код для buildTypes <>.
3. Получите доступ к нему в Activity / Fragment с помощью BuildConfig приложения:
Обновить :
Приведенное выше решение полезно в проекте с открытым исходным кодом для фиксации над Git. (Спасибо Дэвиду Роусону и Рияз-Али за ваш комментарий).
Согласно комментариям Мэтью и Пабло Чегарры, вышеуказанный способ небезопасен, и Декомпилятор позволит кому-то просматривать BuildConfig с нашими секретными ключами.
Решение :
Мы можем использовать NDK для защиты ключей API. Мы можем хранить ключи в собственном классе C / C ++ и получать к ним доступ в наших классах Java.
Пожалуйста, следуйте этому блогу, чтобы защитить ключи API с помощью NDK.
Секретный ключ приложения должен быть закрытым, но при выпуске приложения некоторые парни могут обратить его вспять.
для тех парней это не скроет, заблокируйте либо ProGuard код. Это рефакторинг, и некоторые платные обфускаторы вставляют несколько побитовых операторов, чтобы вернуть jk433g34hg3 строку. Вы можете увеличить время взлома на 5-15 минут, если вы работаете 3 дня 🙂
Лучший способ — оставить все как есть, имхо.
Даже если вы храните на стороне сервера (ваш компьютер) ключ может быть взломан и распечатан. Может быть, это займет больше всего времени? Во всяком случае, в лучшем случае это всего лишь несколько минут или несколько часов.
Обычный пользователь не будет декомпилировать ваш код.
Одним из возможных решений является кодирование данных в вашем приложении и использование декодирования во время выполнения (когда вы хотите использовать эти данные). Я также рекомендую использовать progaurd, чтобы затруднить чтение и понимание декомпилированного исходного кода вашего приложения. например, я вставил закодированный ключ в приложение, а затем использовал метод декодирования в своем приложении для декодирования моих секретных ключей во время выполнения:
Декомпилированный исходный код защищенного приложения выглядит так:
По крайней мере, это достаточно сложно для меня. это то, что я делаю, когда у меня нет выбора, кроме как сохранить значение в моем приложении. Конечно, мы все знаем, что это не лучший способ, но он работает для меня.
и вы можете найти так много классов шифрования с небольшим поиском в Google.
В дополнение к решению @Manohar Reddy можно использовать базу данных firebase или firebase RemoteConfig (со значением по умолчанию Null):
- Зашифруй свои ключи
- Сохраните его в базе данных Firebase
- Получите его во время запуска приложения или при необходимости
- расшифровать ключи и использовать его
Чем отличается это решение?
- нет кредитов для пожарной базы
- Доступ к Firebase защищен, поэтому только приложение с подписанным сертификатом имеет право совершать вызовы API.
- шифрование / дешифрование для предотвращения перехвата среднего человека. Однако звонки уже https на firebase
Этот пример имеет ряд различных аспектов. Я упомяну пару моментов, которые, я не думаю, были явно рассмотрены в других местах.
Защита секрета в пути
Первое, на что следует обратить внимание, это то, что для доступа к API Dropbox с использованием механизма аутентификации приложения вам необходимо передать свой ключ и секрет. Соединение — HTTPS, что означает, что вы не можете перехватить трафик, не зная сертификата TLS. Это сделано для предотвращения перехвата и считывания пакетов на пути от мобильного устройства к серверу. Для обычных пользователей это действительно хороший способ обеспечить конфиденциальность своего трафика.
В чем он не хорош, так это в предотвращении загрузки приложения злоумышленником и проверки трафика. Очень просто использовать прокси-сервер «человек посередине» для всего трафика, поступающего на мобильное устройство и выходящего из него. В этом случае для извлечения ключа и секрета приложения не требуется разборка или обратный инжиниринг кода из-за особенностей API Dropbox.
Вы могли бы сделать закрепление, которое проверяет, что сертификат TLS, который вы получаете от сервера, является тем, который вы ожидаете. Это добавляет проверку клиенту и усложняет перехват трафика. Это затруднило бы проверку трафика в полете, но проверка закрепления происходит в клиенте, поэтому, вероятно, все еще можно будет отключить проверку закрепления. Это делает все труднее, хотя.
Защищать секрет в покое
В качестве первого шага использование чего-то вроде proguard поможет сделать менее очевидным, где хранятся какие-либо секреты. Вы также можете использовать NDK для хранения ключа и секрета и отправки запросов напрямую, что значительно сократит количество людей, обладающих соответствующими навыками для извлечения информации. Дальнейшее запутывание может быть достигнуто, не сохраняя значения непосредственно в памяти в течение какого-либо промежутка времени, вы можете зашифровать их и расшифровать их непосредственно перед использованием, как предложено в другом ответе.
Более продвинутые варианты
Если вы сейчас не хотите раскрывать секрет где-либо в своем приложении и у вас есть время и деньги, чтобы инвестировать в более комплексные решения, тогда вы можете подумать о сохранении учетных данных на своих серверах (если они у вас есть). Это увеличит задержку любых вызовов к API, так как ему придется обмениваться данными через ваш сервер, и может увеличить затраты на обслуживание вашего сервиса из-за увеличения пропускной способности.
Затем вам нужно решить, как лучше всего общаться с вашими серверами, чтобы обеспечить их защиту. Это важно для предотвращения повторения всех тех же проблем с вашим внутренним API. Лучшее эмпирическое правило, которое я могу дать, — не передавать какой-либо секрет напрямую из-за угрозы «человек посередине». Вместо этого вы можете подписать трафик, используя свой секрет, и проверить целостность любых запросов, поступающих на ваш сервер. Один из стандартных способов сделать это — вычислить HMAC сообщения с секретным ключом. Я работаю в компании, у которой есть продукт для обеспечения безопасности, который также работает в этой области, поэтому такого рода вещи меня интересуют. На самом деле, вот статья в блоге одного из моих коллег, которая посвящена большей части этого.
Сколько я должен сделать?
С любым подобным советом по безопасности вам необходимо принять решение о соотношении затрат и выгод относительно того, насколько сложно вы хотите, чтобы кто-то взломал его. Если вы являетесь банком, защищающим миллионы клиентов, ваш бюджет полностью отличается от того, кто поддерживает приложение в их Свободное время. Практически невозможно помешать кому-либо нарушить вашу безопасность, но на практике немногим людям нужны все навороты, и с некоторыми основными мерами предосторожности вы можете пройти долгий путь.
Источник