Рекомендации по хранению и защите закрытых ключей API в приложениях

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

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

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

}

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

14 ответов


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


выполните 3 простых шага для защиты API / секретного ключа

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

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

GoolgeAPIKey = "Your API/Secret Key"

2. строить.gradle (модуль: приложение): установить переменную в сборке.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


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

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

лучший способ-сохранить все как есть, имхо.

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

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


одним из возможных решений является кодирование данных в вашем приложении и использовать декодирование во время выполнения (когда вы хотите использовать эти данные). Я также рекомендую использовать 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.


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


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

защита секрета в пути

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

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

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

защита тайны в покое

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

Дополнительные параметры

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

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

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

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


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


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

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

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

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


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

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


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


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


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

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

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

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

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

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

$ strings libsecretlib.so | grep My
  My_S3cr3t_P@$$W0rD