Привет, Хабр!

По традиции, представлюсь, меня зовут Юрий Шабалин, и вместе с командой Стингрей мы разрабатываем платформу анализа защищенности мобильных приложений. Сегодня я хотел бы рассказать о хранении секретов в мобильных приложениях. А именно о том, что происходит с аутентификационными данными от сторонних сервисов, которые мы так любим использовать. И, конечно, о том, какими последствиями может обернуться для приложения и компании отсутствие должного внимания к их безопасности. Поехали!

Оглавление

Вступление

Как упоминалось в одной из наших статей, проблема хранения данных - одна из самых часто встречающихся в мобильных приложениях. Причем речь может идти не только об информации, непосредственно связанной с самим приложением и пользователями (включая пароли, пин-коды, персональные и конфиденциальные сведения), но и о данных от сторонних сервисов. Что имеется в виду? Сегодня мобильные приложения для реализации своего функционала часто применяют внешние сервисы, включая рассылку Push-уведомлений, хранение изображений на S3, интеграцию с различными картами, сервисами местоположения и многое-многое другое. И это полностью оправданно - не надо тратить свое время на то, что можно подключить и сразу использовать. Интегрировать готовые и удобные сервисы намного проще и выгоднее как в части разработки, так и в части обеспечения безопасности. Но очень важно правильно хранить токены, используемые для этих интеграций, - об этом мы и поговорим.

Проблема

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

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

Как обычно происходит процедура выпуска токена? Взаимодействие с системой выдачи прав и разрешений в сложном и большом сервисе выглядит примерно так: “Мне нужно, чтобы мое приложение умело получать вот эти данные. В документации написано, что мне нужна вот такая роль. Хорошо, поставлю ее. Так, не работает… Поставим еще и вот эту роль, она тоже делает что-то похожее... Опять не работает… А если и вот это еще… О, работает! Так и оставим!“

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

Еще один пример - миграция одного сервиса в другой, когда прежние ключи еще действуют для обеспечения обратной совместимости. Если процесс выполнен некорректно, ключи могут получить больше привилегий, чем было до этого. Такое, конечно, происходит крайне редко, но может случиться, например, при переходе GCM (Google Cloud Messaging) в FCM (Firebase Cloud Messaging). И иногда это может стать проблемой. Об этом подробно рассказано в статье, автор которой нашел десятки, если не сотни, ключей FCM/GCM, дающих возможность отправлять произвольные Push-сообщения от имени приложения.

Другой вариант - это банальная невнимательность или ошибка, когда в приложение попадает совсем не тот ключ, который должен. Вручную отличить их невозможно. Есть два ключа, есть две строки из 256 символов. Какой из них правильный, ни на одном из этапов разработки узнать не получится, только если специально это не проверить. Так что, если вы хотя бы однажды ошиблись с файлом или со значением ключа, оно может использоваться в приложении и до сих пор. И это не теория: так и было с одним из приложений, которые нам довелось анализировать.

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

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

Способы решения

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

Существует несколько способов это сделать. Нашей командой было найдено и проанализировано как минимум три репозитория для поиска секретов:

  1. Keyhacks - список различных регулярных выражений для самых разнообразных сервисов. Для некоторых из них есть описание того, как найденные ключи можно проверить. Также приводятся критерии определения успешности (например, что должно прийти в ответе, чтобы понять, валидный ключик или нет). Очень полезный репозиторий, который отлично подходит в качестве расширения проверок и базы для дальнейшей автоматизации.
    Список достаточно активно развивается и поддерживается, и последнее обновление было 18 дней назад (на момент написания статьи).

  2. RegHex - довольно большой и полный список регулярных выражений под многие сервисы. К большому сожалению, не содержит никаких сведений о том, как эти самые секреты проверять, являются ли они действующими или нет. Может использоваться, как отличное дополнение к двум другим репозиториям в части поиска определенных ключей.
    Последнее обновление - 8 месяцев назад (на момент написания статьи).

  3. Detect-secrets - репозиторий, который может стать отличным дополнительным шагом в процессе CI/CD или в процессе тестирования готового решения. В отличие от двух предыдущих, является не просто списком регулярных выражений, а полноценным инструментом для поиска секретов в коде и также имеет дополнительную функциональность в виде проверки некоторых секретов на валидность. Инструмент имеет модульную структуру и позволяет дописывать собственные плагины для поиска и валидации секретов. То есть, если обогатить этот репозиторий данными и способом проверки токеном из двух предыдущих, может получиться вполне хороший инструмент.

    В дополнение к конкретным плагинам этот репозиторий имеет интересный режим поиска, основанный на определении “мусора“ (Gibberish Detector). Он основан на простой ML-модельке, которая определяет, похожа строка на слово или нет. Инструмент обучается на английском тексте (в примере - отрывок из Шерлока Холмса) и определяет, с какой частотой буквы следуют друг за другом. Он умеет оценивать, насколько строка, которую подали на вход, соответствует ранее обученной модели и насколько она похожа на реальное слово. Так как секреты, в основном, это случайно сгенерированная строка, то можно найти еще несколько ключей, формат которых изменился или для которых нет правил.

Примеры

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

  • Firebase Cloud Messaging / Google Cloud Messaging

  • Google Services

  • Twitter

  • Facebook

  • Instagram

  • AWS

  • GitHub

  • PayPal

  • slack

  • Cloudant

  • IBM Cloud Object Storage

  • Stripe

  • Twillo

Про некоторые из них я бы хотел рассказать немного подробнее.

Firebase

Firebase — одно из широко используемых хранилищ данных для мобильных приложений. В 2018 году команда Appthority Mobile Threat Team (MTT) обнаружила уязвимость, связанную с неправильной конфигурацией Firebase. Ее назвали “HospitalGown”. Вот краткая выдержка из отчета об этой уязвимости:

  • Исследование было проведено на 2 705 987 приложениях. Было обнаружено, что 27 227 приложений для Android и 1 275 приложений для iOS используют базу данных Firebase;

  • 9% приложений Android и почти половина приложений iOS (47%), применяющих БД Firebase, были уязвимы;

  • Более 3 000 приложений хранили данные на примерно 2 300 незащищенных серверов;

  • Каждая десятая БД Firebase (10,34%) была уязвима;

  • Одни только уязвимые приложения для Android были загружены пользователями более 620 миллионов раз;

  • Было раскрыто более 100 миллионов записей (113 гигабайт) данных.

Все это делает такой сервис лакомым кусочком для злоумышленника. А ведь автоматическая проверка на корректность настройки самой базы достаточно проста.

Сервисы Google

На данный момент, мы поддерживаем поиск и проверку токенов для 17 самых популярных сервисов Google (в скобках указана цена за количество запросов):

  • Staticmap ($2/1.000)

  • Streetview ($7/1.000)

  • Directions ($5/1.000)

  • Geocode ($5/1.000)

  • Find Place From Text ($17/1.000)

  • Autocomplete ($2.83/1.000)

  • Elevation ($5/1.000)

  • Timezone ($5/1.000)

  • Nearest Roads ($10/1.000)

  • Geolocation ($5/1.000)

  • Route to Traveled ($10/1.000)

  • Place Details ($17/1.000)

  • Speed Limit-Roads ($20/1.000)

  • Nearby Search-Places ($32/1.000)

  • Text Search-Places ($2.83/1.000)

  • Playable Locations ($18/1.000)

  • Google Custom Search ($5/1.000)

Что может произойти, если мы, к примеру, храним в коде токен от сервиса “Nearby Search-Places“, каждая тысяча запросов к которому стоит для компании 32$? Злоумышленники, оценив ситуацию, могут написать простого бота, который в несколько потоков будет отправлять запросы с вашим токеном на этот сервис. В итоге, в конце месяца счет за услуги будет огромным. У Google оплата проводится постфактум, так что придется выложить очень много денег. Как вариант, злоумышленник может просто позаимствовать такой ключик для своего приложения, раз уж его оставили на видном месте. Конечно, многие компании заключают договор на безлимитное использование необходимых сервисов. Но это относится, скорее, к большим финансовым организациям, которые могут себе это позволить. Ну а более экономным может и не повезти.

Найденные ключи с автоматической валидацией
Найденные ключи с автоматической валидацией

Как показывает наша статистика, в достаточно большом количестве Android-приложений используются ключи такого типа от различных сервисов. Конечно, у Google есть процедура восстановления денежных средств при злоупотреблении ключами (то есть если счёт слишком большой, то можно попросить вернуть средства). Но наверняка все не так просто с возвратом (если кто-то может рассказать о реальных случаях, буду очень признателен).

FCM / GCM

Конечно, нельзя обойти вниманием возможность отправки Push-сообщений всем пользователям, у которых было установлено приложение. Представьте: вдруг ваше приложение рассылает всем “молнию“ с предложением перейти к конкуренту. Не очень приятно, да? А ведь чтобы провернуть это, нужно всего лишь найти правильный ключ в исходном коде приложения и прочитать в документации, на какие API и в какой последовательности необходимо отправить запросы.

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

Найденный ключ FCM с возможностью отправки PUSH сообщений
Найденный ключ FCM с возможностью отправки PUSH сообщений

Добавление собственных проверок

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

Один из вариантов проверки исходного кода предполагает использование инструмента Detect-secrets с реализацией собственного плагина. Для того, чтобы автоматически находить и валидировать внутренние токены в уже собранных приложениях и в данных, которые они используют при работе, мы сделали механизм handle для правил поиска чувствительных данных в приложении. После добавления проверки в выявленных уязвимостях можно будет увидеть значение токена, место, где он располагается, и результат проверки.

Пример добавления

Пример handle для проверки токена и передачи в него минимального набора полей:

{
  "name": "My custom token",
  "url": "https://stingray-mobile.ru/authorize_zone/?key={key}",
  "method": "get",
  "unauthorized": "status_code>=400",
  "status": "reason",
  "price": "Unbelievable"
}

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

Варианты хранения

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

Так, к примеру, одна из организаций делала промежуточный “прокси“ API для различных сервисов аналитики для своего Web-сайта, чтобы не было гневных постов в соцсетях о том, что компания сливает данные пользователей, поскольку обращения идут не только на её домен. Да, был и такой случай, - пришлось долго и нудно объяснять, что это такое и зачем это нужно.

По первому способу с использованием шифрования возникает логичный вопрос: “Как ключи передавать и как хранить их на устройстве?” Основной нюанс заключается в необходимости максимально сократить время их нахождения в открытом виде на устройстве. Для этого (как один из вариантов) можно рассмотреть передачу ключей только при первом запуске приложения, а лучше при аутентификации (обязательно первой!) передавать их на клиента, где уже сразу шифровать, и потом правильно хранить. Таким образом, мы максимально сократим пребывание токенов в открытом виде. Они будут видны только при первой аутентификации и только в трафике мобильного приложения. Это, конечно, не идеальный вариант, но это точно лучше, чем хранить их в открытом виде.

Хранение ключей в iOS

С хранением ключей в iOS все достаточно просто. Можно быть уверенным, что при запуске на устройстве с определенной версией iOS будет предоставлена возможность хранения в безопасном аппаратном хранилище ключей. Достаточно обратиться к механизму Security Enclave, сгенерировать там ключи, и зашифрованное значение положить положить в Keychain. В прошлых статьях мы уже говорили про использование Security Enclave в “сыром“ виде, а сегодня посмотрим, как можно применять интересную библиотеку EllipticCurveKeyPair (это небольшое изложение описанного в репозитории с некоторыми комментариями от меня):

Первое, что необходимо сделать это добавление зависимости в Сocoapods

pod EllipticCurveKeyPair

Или в Carthage

github "agens-no/EllipticCurveKeyPair"

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

struct KeyPair {
    static let manager: EllipticCurveKeyPair.Manager = {
        let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
        let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.userPresence, .privateKeyUsage])
        let config = EllipticCurveKeyPair.Config(
            publicLabel: "payment.sign.public",
            privateLabel: "payment.sign.private",
            operationPrompt: "Confirm payment",
            publicKeyAccessControl: publicAccessControl,
            privateKeyAccessControl: privateAccessControl,
            token: .secureEnclave)
        return EllipticCurveKeyPair.Manager(config: config)
    }()
}

Также можно использовать KeyChain, если по какой-то причине Secure Enclave недоступен:

let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: {
    return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
}())

Собственно, любые операции теперь можно совершать, используя KeyPair.manager.

Шифрование

do {
    let digest = "some text to encrypt".data(using: .utf8)!
    let encrypted = try KeyPair.manager.encrypt(digest, hash: .sha256)
} catch {
    // handle error
}

Что интересно, можно шифровать данные на другом устройстве/ОС/платформе с помощью открытого ключа. Если вы хотите узнать все подробности о том, как шифровать в формате, который понимает Secure Enclave, то рекомендую ознакомиться с этой статьей. Как вариант - можно дополнительно шифровать важные данные перед их отправкой на устройство.

Расшифровка

do {
    let encrypted = ...
    let decrypted = try KeyPair.manager.decrypt(encrypted, hash: .sha256)
    let decryptedString = String(data: decrypted, encoding: .utf8)
} catch {
    // handle error
}

Подпись

do {
    let digest = "some text to sign".data(using: .utf8)!
    let signature = try KeyPair.manager.sign(digest, hash: .sha256)
} catch {
    // handle error
}

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

  1. Пользователь запрашивает соглашение, покупку или какую-либо операцию, которая требует его явного согласия;

  2. Сервер отправляет push-уведомление с токеном, который должен быть подписан;

  3. На устройстве подписываем токен при помощи закрытого ключа (что обязательно требует от пользователя подтверждения через FaceId/TouchId);

  4. Подписанный токен отправляется на сервер;

  5. Сервер при помощи открытого ключа проверяет подпись;

  6. Если все прошло успешно, можно быть уверенным, что пользователь действительно подтвердил покупку/соглашение с использованием биометрии.

Достаточно удобная библиотека, которая большинство функций берет на себя и предоставляет неплохие возможности. Если планируете использовать Security Enclave, рекомендую более подробно с ней ознакомиться.

Хранение ключей в Android

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

Есть несколько основных способов хранения ключей в зависимости от версии операционной системы:

  • На Android API<18 ключи шифрования должны храниться внутри директории приложения в BKS

  • На Android API>=18 RSA ключи должны храниться в AndroidKeyStore, AES ключи в BKS

  • На Android API>=23 RSA и AES ключи должны храниться в AndroidKeyStore

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

  • Содержать не меньше 20 символов

  • Содержать хотя бы одну прописную букву

  • Содержать хотя бы одну строчную букву

  • Содержать хотя бы одну цифру

  • Содержать хотя бы один специальный символ

Пример генерации защищенного хранилища BKS с паролем и ключом, также защищенным паролем

$ keytool -importcert -v -trustcacerts -file "C:\Users\Indra\Documents\myapp.com.cer" -alias IntermediateCA -keystore "C:\Users\Indra\Documents\appKeyStore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "C:\Users\Indra\Downloads\bcprov-jdk15on-154.jar" -storetype BKS -storepass StorePass123


$ keytool -list -keystore "C:\Users\Indra\Documents\appKeyStore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "C:\Users\Indra\Downloads\bcprov-jdk15on-154.jar" -storetype BKS -storepass "StorePass123"

--------------------------------------------------------------------------------------------------------------------------------------------------------

$ openssl pkcs12 -export -in "/home/myapp/myapp_cert_2016/ssl_certificate.crt" -inkey "/home/myapp/myapp_cert_2016/domainname.key" -certfile "/home/myapp/myapp_cert_2016/ssl_certificate.crt" -out testkeystore.p12
Export password : exportpass123

$ keytool -importkeystore -srckeystore "C:\Users\Indra\myapp\testkeystore.p12" -srcstoretype pkcs12 -destkeystore ""C:\Users\Indra\myapp\wso2carbon.jks" -deststoretype JKS

Destination keystore password : exportpass123



----------------------------------------------------- Final JKS Keystore generation ------------------------------------------------------

$ openssl pkcs12 -export -in "/home/myapp/myapp_cert_2016/ssl_certificate.crt" -inkey "/home/myapp/myapp_cert_2016/domainname.key" -certfile "/home/myapp/myapp_cert_2016/ssl_certificate.crt" -out myapp_cert.p12

Export Password : StorePass123

$ keytool -importkeystore -srckeystore "C:\Users\Indra\myapp\myapp_cert.p12" -srcstoretype pkcs12 -destkeystore "C:\Users\Indra\myapp\myapp_keystore.jks" -deststoretype JKS


Import Password : StorePass123


----------------------------------------------------- Final BKS Keystore generation ------------------------------------------------------

$ keytool -importkeystore -srckeystore "C:\Users\Indra\myapp\myapp_keystore.jks -deststoretype JKS" -destkeystore "C:\Users\Indra\myapp\myapp_keystore.bks" -srcstoretype JKS -deststoretype BKS -srcstorepass StorePass123 -deststorepass StorePass123 -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "C:\Users\Indra\Downloads\bcprov-jdk15on-154.jar"


On error or exception steps to be taken

- Comment above line and add the new line in java.security file in jre/lib/security

	#security.provider.7=com.sun.security.sasl.Provider
	security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider

- You need to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy  

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

Добавление нового ключа в KeyStore

public void createNewKeys(View view) {
    String alias = aliasText.getText().toString();
    try {
        // Create new key if needed
        if (!keyStore.containsAlias(alias)) {
            Calendar start = Calendar.getInstance();
            Calendar end = Calendar.getInstance();
            end.add(Calendar.YEAR, 1);
            KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
                    .setAlias(alias)
                    .setSubject(new X500Principal("CN=Sample Name, O=Android Authority"))
                    .setSerialNumber(BigInteger.ONE)
                    .setStartDate(start.getTime())
                    .setEndDate(end.getTime())
                    .build();
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
            generator.initialize(spec);

            KeyPair keyPair = generator.generateKeyPair();
        }
    } catch (Exception e) {
        Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
        Log.e(TAG, Log.getStackTraceString(e));
    }
    refreshKeys();
}

Удаление ключа из KeyStore

public void deleteKey(final String alias) {
    AlertDialog alertDialog =new AlertDialog.Builder(this)
            .setTitle("Delete Key")
            .setMessage("Do you want to delete the key \"" + alias + "\" from the keystore?")
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    try {
                        keyStore.deleteEntry(alias);
                        refreshKeys();
                    } catch (KeyStoreException e) {
                        Toast.makeText(MainActivity.this,
                                "Exception " + e.getMessage() + " occured",
                                Toast.LENGTH_LONG).show();
                        Log.e(TAG, Log.getStackTraceString(e));
                    }
                    dialog.dismiss();
                }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            })
            .create();
    alertDialog.show();
}

Применение ключа для шифрования

public void encryptString(String alias) {
    try {
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
        RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();

        // Encrypt the text
        String initialText = startText.getText().toString();
        if(initialText.isEmpty()) {
            Toast.makeText(this, "Enter text in the 'Initial Text' widget", Toast.LENGTH_LONG).show();
            return;
        }

        Cipher input = Cipher.getInstance("RSA/CBC/PKCS7Padding", "AndroidOpenSSL");
        input.init(Cipher.ENCRYPT_MODE, publicKey);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        CipherOutputStream cipherOutputStream = new CipherOutputStream(
                outputStream, input);
        cipherOutputStream.write(initialText.getBytes("UTF-8"));
        cipherOutputStream.close();

        byte [] vals = outputStream.toByteArray();
        encryptedText.setText(Base64.encodeToString(vals, Base64.DEFAULT));
    } catch (Exception e) {
        Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
        Log.e(TAG, Log.getStackTraceString(e));
    }
}

Применение ключа для расшифровки

public void decryptString(String alias) {
    try {
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
        RSAPrivateKey privateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey();

        Cipher output = Cipher.getInstance("RSA/CBC/PKCS7Padding", "AndroidOpenSSL");
        output.init(Cipher.DECRYPT_MODE, privateKey);

        String cipherText = encryptedText.getText().toString();
        CipherInputStream cipherInputStream = new CipherInputStream(
                new ByteArrayInputStream(Base64.decode(cipherText, Base64.DEFAULT)), output);
        ArrayList<Byte> values = new ArrayList<>();
        int nextByte;
        while ((nextByte = cipherInputStream.read()) != -1) {
            values.add((byte)nextByte);
        }

        byte[] bytes = new byte[values.size()];
        for(int i = 0; i < bytes.length; i++) {
            bytes[i] = values.get(i).byteValue();
        }

        String finalText = new String(bytes, 0, bytes.length, "UTF-8");
        decryptedText.setText(finalText);

    } catch (Exception e) {
        Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
        Log.e(TAG, Log.getStackTraceString(e));
    }
}

Заключение

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

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

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

Ссылки

  1. Unsecured Firebase Databases

  2. Android KeyStore - Kotlin

  3. How to use the Android Keystore to store passwords

  4. Android Developer - Cryptography

  5. Android Developer - EncryptedFile

  6. Does the use of a smartphone's Secure Element really offer security benefits

  7. Qlassified Android Library

  8. detect-secrets

  9. detect-secrets - Verified-Secrets

  10. keyhacks

  11. RegHex

Комментарии (1)


  1. Knutov_V
    05.05.2022 11:43
    +1

    Спасибо! Как всегда по полочкам