вторник, 11 августа 2015 г.

Де заховати пароль в додатку на Android

Я хотів би пройти крізь декілька напівреалістичних прикладів та пояснити, що приховано за деякими з стратегій, та чому вони можуть бути не такими ефективними як ви могли б сподіватись.
Огляд не охоплює всі можливі варіанти, ми розгляними тільки декілька найбліьш популярних та доступних із способів збереження secret, і, що може піти не так:

  1. Вбудовувати в файл strings.xml
  2. Приховати у вихідному коді
  3. Приховати в BuildConfigs
  4. Використовувати Proguard
  5. Замасковані/Зашифровані файли String
  6. Приховати в Native Libraries



Загальні стратегії приховування секретів

Щоб допомогти зобразити деякі з цих концептів були створені приклади додатків на Android та розміщені на Github. Повний вихідний код доступний для перегляду, та все ж для впевненості перегляньте ще й декомпільваний вихідний код. Важливо щоб ви розуміли як виглядає не тільки код, який ви пишете, а й код, який бачить потенційний зловмисник при реверсінжинірингу вашого додатку.

0. Включення Secret в strings.xml

Як розробник під платформу Android, першим вашим інстинктом буде, імовірно, включити секрет, так як і ключ API, у ваш XML ресурси, як ви робили раніше з іншими активами. Ми зробили це, як і зазвичай, в res/values/strings.xml file:
<resources>
    <string name="app_name">HidingPasswords</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="server_password">My_S3cr3t_P@$$W0rD</string>
  </resources>
Поки що все гарно і охайно, це також, імовірно, найлегший спосіб збереження та використання ресурсів. Щоб побачити, як ми можемо це зробити почнемо з завантаженя файлу APK- це можна зробити вручну з github чи за допомогою wget з командного рядка:
$ wget https://github.com/pillfill/hiding-passwords-android/releases/download/1.0/app-x86-universal-debug.apk
Тепер давайте запустимо strings, і подивимось, що можна знайт цікавого:
$ strings app-x86-universal-debug.apk
  …(Lots of output)
Вам краще бачити всі ці цікаві речі тут. Придивившись детальніше ви навіть можете побачити наші key/password, що були включені:
$ strings app-x86-universal-debug.apk | grep My
    My_S3cr3t_P@$$W0rD
Отже команда strings розгромила все і знайшла API ключ з легкістю. Дана команда працює з усіма видами бінарників, а не лише з додатками Android.

1. Включення Secret у ваш вихідний код

Ще один популярний спосіб збереження розробниками ключів API. Для демонстрації, ми включимо public static final String поле та навіть byte[] масив з нашими захардкодженими ключами всередині MainActivity додатку-прикладу:
public class MainActivity extends AppCompatActivity {
  //A simple static field to store sensitive keys
  private static final String myNaivePasswordHidingKey = "My_S3cr3t_P@$$W0rD";
  //A marginally better effort to store a key in a byte array (to avoid string analysis)
  private static final byte[] mySlightlyCleverHidingKey = new byte[]{
    'M','y','_','S','3','c','r','3','t','_','P','@','$','$','W','0','r','D','_','2'
Поки утиліта strings не може знайти так само легко, як і при роботі з файлами в ресурсах XML, але все ще залишається корисною, якщо копнути глибше. Поки APK залишаєтсья стиснутим/заархівованим файли від нас приховані, ми можемо розархівувати APK і, знову-таки, знайти пароль:
$ unzip app-x86-universal-debug.apk
$ strings classes.dex | grep My
   My_S3cr3t_P@$$W0rD_2
   My_S3cr3t_P@$$W0rD
Утиліта strings змогла знайти обидва значення (наш password в string та в навіть в масиві byte array!) з легкістю. Ми говорили перевірити classes.dex file- файл, що повністю містить повний зкомпільований java код.

2. Включення Secret у ваш Build Config

Ще один варіант з минуло-тижневої дискусії на Reddit, було управління ключами в BuildConfig з плагуну Android Gradle. Безумовно, даним підходом ви зводите до мінімуму засвітити свій пароль в одній зсистем контрол версій (особливо важливо, коли ви використовуєте публічні DVCS як GitHub):
buildTypes {
  debug {
    minifyEnabled true
    buildConfigField "String", "hiddenPassword", "\"${hiddenPassword}\""
  }
}
Ви може вставити це значення в файл .gitignore local.properties чи відмітити gradle.properties як показано тут:
hiddenPassword=My_S3cr3t_P@$$W0rD
Нажаль, це не допоможе приховати secret, як в прикладі, зображеному вище,  так як ці значення виділяються у вигляді BuildConfig. Це може бути досліджено і розпаковано точно таким же чином.

3. Шифрування Secret за допомогою Proguard

Битва зі strings програна. Ми можемо запустити proguard над нашим додатком, обфускувати нащ код, і це вирішить нашу проблемку з безпекою файлів string. Так?
Не зовсім. Поглянемо на proguard-rules.pro в нашому проекті:
# Just change our classes (to make things easier)
  -keep class !com.apothesource.** { *; }
Ми вже наказали proguard весь код в нашому пакеті (com.apothesource.**). Я можу також сказати з впевненістю що Proguard працює як представлено. Отже чому ми досі можемо бачити паролі?
Proguard явно не робить нічого з нашим проектом чи незашифрованими string файлами. Причиною, яка б надала всьому цьому сенс є те, що він просто не може змінити значення  string від якого залежить ваш додаток, зміна може призвести до непередбачуваних результатів. Ви можете бачити, що саме proguard робить, переглянувши mapping.txt файл в нашому білді:
com.apothesource.hidingpasswords.HidingUtil -> com.apothesource.hidingpasswords.a:
    java.lang.String hide(java.lang.String) -> a
    java.lang.String unhide(java.lang.String) -> b
    void doHiding(byte[],byte[],boolean) -> a
com.apothesource.hidingpasswords.MainActivity -> .hidingpasswords.MainActivity:
    byte[] mySlightlyCleverHidingKey -> a
    java.lang.String[] myCompositeKey -> b
Тепер видно, що перейменування наших класів, методів, та імен змінних, як і очікувалось, нічим не  допомагає. Погляньте на результат роботи компілятора, що б оцінити роботу proguard. Для порівняння зображена робота з та без утиліти proguard в класі MainActivity, дивіться приклад:
Normal Output:
#static fields
.field private static final TAG:Ljava/lang/String; = "HidingActivity"
.field private static final myCompositeKey:[Ljava/lang/String;
.field private static final myNaivePasswordHidingKey:Ljava/lang/String; = "My_S3cr3t_P@$$W0rD"
.field private static final mySlightlyCleverHidingKey:[B
Proguard Output:
#static fields
.field private static final n:[B
.field private static final o:[Ljava/lang/String;
Proguard робить свою роботу змінюючи імена та навіть значення password, перетворивши його на локальну змінну. Коли ви досліджуєте згенеровану реалізацію методів, ви можете бачити, що наш password досі в "сирому" виді:
.method public b(Ljava/lang/String;)V 
  
  move-result-object v0
  const-string v1, "My_S3cr3t_P@$$W0rD"
Досі не срібна куля, але Proguard залишається важливою утилітою, якщо ви маєте намір запобігти реверсінжинірингу. Він дуже ефективний в заплутуванні коду та приховуванні логіки, що, звісно ж, підвищує стійкість коду. Ви можете переглянути версії з Proguard та без  обидві включені в наш проект на Github.

4. Приховати секрети в стрінгах

Заголовок звучить трохи непристойно, але, звісно ж, мова йде про код "secret" та файл strings.xml. З того часу як proguard не приховує ваші файли string, чому б не зробити це самостійно?
Ви можете приховати ваш secret в stringsза допомогою шифрування, base64 будучи достаньо поширеною, може підійти. В нашому додатку, ми зроби це за допомогоюю операцій XOR:
//A more complicated effort to store the XOR'ed halves of a key (instead of the key itself)
private static final String[] myCompositeKey = new String[]{
  "oNQavjbaNNSgEqoCkT9Em4imeQQ=","3o8eFOX4ri/F8fgHgiy/BS47"
};
Це все ще наш My_S3cr3t_P@$$W0rD пароль. Ми просто приховали за допомогою XOR значення з випадково згенерованим значенням. Ви можете дослідити це за допомогою HidingUtil implementation якщо ви хочете побачити як значення генерується. Зверніть увагу, що доки цей нативнй метод генерує XOR ключ для кожного виклику, немає причин чому б ви не могли використовувати той же ключ для всіх значень у вашому додатку, які б ви хотіли захистити.
Коли ви будете готові використати "приховани" ключ, ви просто запускаєте зворотній процес:
public void useXorStringHiding(String myHiddenMessage) {
  byte[] xorParts0 = Base64.decode(myCompositeKey[0],0);
  byte[] xorParts1 = Base64.decode(myCompositeKey[1], 0);

  byte[] xorKey = new byte[xorParts0.length];
  for(int i = 0; i < xorParts1.length; i++){
    xorKey[i] = (byte) (xorParts0[i] ^ xorParts1[i]);
  }
  HidingUtil.doHiding(myHiddenMessage.getBytes(), xorKey, false);
}
Поки не дуже розумно (чи оптимізовано), це ще один крок в правильному напрямку, ускладнюючи декомпіляцію додатку зловмисником.

Висновок

Найкращий спосіб зберегти secret це ніколи його не показувати. Розділення на секції конфіденційної інформації та операцій вашої власної серверної частини має бути вашим першим вибором. Якщо ви все-таки вирішили приховувати схеми, ви мусите зробити процес реверсінжиніринга зловмисника настільки складним, наскільки це можливо.

source

Комментариев нет:

Отправить комментарий