Лучшая практика хранения и защиты частных ключей API в приложениях

Большинство разработчиков приложений будут интегрировать некоторые сторонние библиотеки в свои приложения. Если это доступ к службе, например Dropbox или YouTube, или к сбоям в регистрации. Количество сторонних библиотек и служб колеблется. Большинство из этих библиотек и сервисов интегрированы путем аутентификации с помощью службы, большую часть времени это происходит с помощью ключа API. В целях безопасности службы обычно генерируют открытый и закрытый, часто также называемый секретным ключом. К сожалению, для подключения к службам этот закрытый ключ должен использоваться для аутентификации и, следовательно, вероятно, быть частью приложения. Излишне говорить, что это сталкивается с огромной проблемой безопасности. Публичные и частные ключи API могут быть извлечены из APK за считанные минуты и могут быть легко автоматизированы.

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

public class DropboxService  {

    private final static String APP_KEY = "jk433g34hg3";
    private final static String APP_SECRET = "987dwdqwdqw90";
    private final static AccessType ACCESS_TYPE = AccessType.DROPBOX;

    // SOME MORE CODE HERE

}

Что, на ваш взгляд, лучший и самый безопасный способ хранения закрытого ключа? Обфускация, шифрование, что вы думаете?

Ответ 1

  • Как это, ваше скомпилированное приложение содержит ключевые строки, но также имена констант APP_KEY и APP_SECRET. Извлечение ключей из такого самодокументирующего кода тривиально, например, со стандартным инструментом Android dx.

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

    Обратите внимание, что настройка ProGuard не должна быть такой сложной, как вы боитесь. Для начала вам нужно включить ProGuard, как описано в project.properties. Если есть проблемы с сторонними библиотеками, вам может потребоваться подавить некоторые предупреждения и/или предотвратить их запутывание в proguard-project.txt. Например:

    -dontwarn com.dropbox.**
    -keep class com.dropbox.** { *; }
    

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

  • Вы можете запутать строки вручную в своем коде, например, с кодировкой Base64 или предпочтительно с чем-то более сложным; возможно, даже собственный код. Затем хакеру придется статически реконструировать вашу кодировку или динамически перехватить декодирование в нужном месте.

  • Вы можете применить коммерческий обфускатор, например специализированный род занятий Proguard DexGuard. Он может дополнительно шифровать/обфускации строк и классов для вас. Извлечение ключей требует еще больше времени и знаний.

  • Возможно, вы сможете запускать части своего приложения на своем собственном сервере. Если вы можете держать ключи там, они безопасны.

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

(Я разработчик ProGuard и DexGuard)

Ответ 2

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

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

  • Поместите свои секреты в jni-код, добавьте некоторый переменный код, чтобы ваши библиотеки больше и сложнее декомпилировать. Вы можете также разделить ключевую строку на несколько частей и сохранить их в разных местах.

  • использовать obfuscator, а также помещать в код хэшированный секрет, а затем разоблачать его при необходимости использовать.

  • Поместите свой секретный ключ в качестве последних пикселей одного из ваших изображений в активах. Затем, когда это необходимо, прочитайте его в своем коде. Obfuscating ваш код должен помочь скрыть код, который будет читать его.

Если вы хотите быстро взглянуть на то, как легко читать вам код apk, возьмите APKAnalyser:

http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/

Ответ 3

Следуйте 3 простым шагам, чтобы защитить API/секретный ключ

Мы можем использовать Gradle для защиты ключа API или секретного ключа.

1. gradle.properties (Свойства проекта): Создать переменную с ключом.

GoolgeAPIKey = "Your API/Secret Key"

2. build.gradle(Module: app): установите переменную в build.gradle для доступа к ней в активности или фрагменте. Добавьте ниже код в buildTypes {}.

buildTypes.each {
    it.buildConfigField 'String', 'GoogleSecAPIKEY', GoolgeAPIKey
}

3. Доступ к нему в Activity/Fragment с помощью приложения BuildConfig:

BuildConfig.GoogleSecAPIKEY

Обновить :

Вышеупомянутое решение полезно в проекте с открытым исходным кодом для передачи через Git. (Спасибо Дэвиду Роусону и Риязи-Али за ваш комментарий).

Согласно комментариям Matthew и Pablo Cegarra, вышеуказанный способ небезопасен, и Decompiler позволит кому-то просматривать BuildConfig с помощью наших секретных ключей.

Решение :

Мы можем использовать NDK для защиты ключей API. Мы можем хранить ключи в собственном классе C/C++ и обращаться к ним в наших классах Java.

Пожалуйста, следуйте этому блогу, чтобы защитить ключи API с помощью NDK.

Последующее наблюдение за тем, как безопасно хранить маркеры в Android

Ответ 4

Ключ App-Secret должен оставаться закрытым - но при выпуске приложения они могут быть отменены некоторыми парнями.

для тех парней он не скрывает, блокирует либо код ProGuard. Это рефактор, и некоторые платные обфускаторы вставляют несколько побитовых операторов, чтобы вернуть jk433g34hg3 Строка. Вы можете сделать 5-15 минут дольше взлома, если вы работаете 3 дня:)

Лучший способ сохранить это как есть, imho.

Даже если вы храните на стороне сервера (ваш ПК), ключ может быть взломан и распечатан. Может быть, это занимает больше всего времени? Во всяком случае, это вопрос нескольких минут или нескольких часов в лучшем случае.

Обычный пользователь не будет декомпилировать ваш код.

Ответ 5

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

// "the real string is: "mypassword" "; 
//encoded 2 times with an algorithm or you can encode with other algorithms too
public String getClientSecret() {
    return Utils.decode(Utils
            .decode("Ylhsd1lYTnpkMjl5WkE9PQ=="));
}

Декомпилированный исходный код защищенного приложения:

 public String c()
 {
    return com.myrpoject.mypackage.g.h.a(com.myrpoject.mypackage.g.h.a("Ylhsd1lYTnpkMjl5WkE9PQ=="));
  }

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

/**
 * @param input
 * @return decoded string
 */
public static String decode(String input) {
    // Receiving side
    String text = "";
    try {
        byte[] data = Decoder.decode(input);
        text = new String(data, "UTF-8");
        return text;
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return "Error";
}

Декомпилированная версия:

 public static String a(String paramString)
  {
    try
    {
      str = new String(a.a(paramString), "UTF-8");
      return str;
    }
    catch (UnsupportedEncodingException localUnsupportedEncodingException)
    {
      while (true)
      {
        localUnsupportedEncodingException.printStackTrace();
        String str = "Error";
      }
    }
  }

и вы можете найти так много классов шифрования с небольшим поиском в google.

Ответ 6

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

Ответ 7

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

Защита секретности в пути

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

То, что это не хорошо, предотвращает загрузку приложения злоумышленником и проверку трафика. Очень просто использовать прокси-сервер man-in-the-middle для всего трафика на мобильном устройстве и из него. В этом случае для извлечения ключа приложения и секретности в этом случае не требуется разборки или обратного проектирования кода из-за характера API Dropbox.

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

Защита секретности в покое

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

Сколько я должен делать?

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

Ответ 8

Старый пост, но все же достаточно хорош. Я думаю, что скрывать его в библиотеке .so было бы здорово, конечно, используя NDK и С++..so файлы можно просмотреть в шестнадцатеричном редакторе, но удача декомпиляции: P

Ответ 9

Другой подход заключается в том, чтобы не иметь секрет на устройстве в первую очередь! См. Методы безопасности Mobile API (особенно часть 3).

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

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

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

Ответ 10

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

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

Ответ 11

Храните секрет в firebase database и получайте от него при запуске приложения. Это намного лучше, чем вызов веб-службы.

Ответ 12

Единственный верный способ сохранить эти личные данные - сохранить их на своем сервере и отправить приложение на сервер, а сервер взаимодействует с Dropbox. Таким образом, вы НИКОГДА не распространяете свой секретный ключ в любом формате.

Ответ 13

Добавляя к решению @Redman, можно использовать базу данных firebase или firebase RemoteConfig (с нулевым значением по умолчанию):

  1. Зашифруйте свои ключи
  2. Хранить его в базе данных firebase
  3. Получите его во время запуска приложения или в случае необходимости
  4. расшифровывать ключи и использовать их

Что в этом решении отличается?

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

Ответ 14

Основываясь на горьком опыте, и после консультации с экспертом IDA-Pro лучшим решением является переместить ключевую часть кода в DLL/SharedObject, а затем извлечь его с сервера и загрузить во время выполнения.

Чувствительные данные должны быть закодированы, так как очень легко сделать что-то вроде этого:

$ strings libsecretlib.so | grep My
  [email protected]$$W0rD