Реалізація захисту API-ключів у мобільному додатку
strings app.apk | grep -i "key\\|secret\\|token" — ця команда в більшості додатків без захисту виводить кілька секретів. Google Maps API Key в AndroidManifest.xml, Firebase API Key в google-services.json, Stripe Publishable Key у вихідному коді — все це витягується з APK без реверс-інжинірингу. Ми стикалися з проєктами, де ключі лежали прямо в коді, і їх бачив кожен, хто завантажив додаток.
Чому не можна зберігати ключі в коді?
Будь-який ключ, вбудований у ресурси або константи, є публічним. APK та IPA декомпілюються; обфускація лише ускладнює пошук, але не запобігає йому. Наприклад, ключ у local.properties все одно потрапляє в білд і читається з маніфесту. Google Maps API Key можна обмежити за package name та SHA-1, але для справжніх секретів (ключа платіжного шлюзу) це не вихід.
Часто чують: «Firebase API Key публічний, його можна засвітити». Технічно вірно для apiKey — він ідентифікує проєкт, доступ контролюється Firebase Rules. Але Maps Key, Stripe Secret Key, ключі до бекенду — інше. Витік Maps Key загрожує чужими запитами за ваш рахунок. Ми гарантуємо, що після нашої роботи ключі ніколи не покинуть пристрій без потреби, а серверні секрети залишаться на сервері.
Як безпечно зберігати ключі на пристрої?
Якщо ключ повинен бути на пристрої (наприклад, токен після аутентифікації) — використовуйте Android Keystore або Keychain Services. Нативне зберігання в Keystore в 100 разів надійніше за Shared Preferences.
Android:
val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") keyGen.init( KeyGenParameterSpec.Builder("my_key_alias", KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build() ) // шифруємо токен, зберігаємо зашифрований blob в EncryptedSharedPreferences EncryptedSharedPreferences з androidx.security:security-crypto — зручна обгортка, що автоматизує цей процес.
iOS: Keychain Services через SecItemAdd/SecItemCopyMatching. У Swift використовуйте KeychainAccess або SwiftKeychainWrapper. Вкажіть атрибут kSecAttrAccessible = kSecAttrAccessibleWhenUnlockedThisDeviceOnly, щоб дані не мігрували з резервною копією iCloud.
Чому серверне зберігання — єдиний безпечний варіант?
API-ключі до зовнішніх сервісів (платіжні шлюзи, SMS-провайдери, AI API) повинні зберігатися на сервері. Клієнт робить запит до свого бекенду, бекенд — до Stripe/Twilio/OpenAI зі своїм ключем. Клієнт ніколи не отримує цей ключ. Це в десятки разів безпечніше для критичних даних.
Паттерн для ключів з обмеженим доступом: клієнт аутентифікується, сервер видає короткоживучий токен (JWT або HMAC-підписаний nonce) з конкретними permissions. Наприклад, для завантаження файлів напряму в S3 використовуйте presigned URL — основний ключ не покидає сервер.
Коли виправдані NDK та обфускація?
Якщо рядок повинен бути в додатку і не можна запитувати його з сервера — застосовуйте нативний код. JNI функція повертає ключ, зібраний з кількох частин:
JNIEXPORT jstring JNICALL Java_com_example_NativeKeys_getApiKey(JNIEnv *env, jobject obj) { const char part1[] = {0x41, 0x42, 0x43, 0x00}; const char part2[] = {0x44, 0x45, 0x46, 0x00}; // збірка + XOR розшифровка } Це security through obscurity, але поріг атаки зростає: нативний код складніше хукати автоматичними інструментами.
Build-time захист: як не допустити витоку в репозиторії
Використовуйте local.properties (ігнорується git) для змінних збірки. Приклад:
MAPS_API_KEY=AIzaSy... У build.gradle:
manifestPlaceholders = [mapsApiKey: properties["MAPS_API_KEY"] ?: ""] У AndroidManifest:
<meta-data android:name="com.google.android.geo.API_KEY" android:value="${mapsApiKey}"/> Ключ не потрапляє в репозиторій, але потрапляє в APK — все одно читається з маніфесту. Для Maps Key це прийнятно з обмеженнями за package name та SHA-1, але не для справжніх секретів.
Порівняння методів захисту
| Метод | Рівень безпеки | Складність реалізації | Рекомендація |
|---|---|---|---|
| Зберігання в коді (рядок/ресурс) | Низький | Низька | Ніколи не використовувати |
| Обфускація (ProGuard/R8) | Низький-Середній | Середня | Недостатньо для секретів |
| NDK + шифрування | Середній | Висока | Для ключів, які неможливо винести |
| Keychain/Keystore | Високий | Середня | Для токенів та даних після аутентифікації |
| Серверний проксі | Дуже високий | Середня | Для всіх зовнішніх API-ключів |
Типові помилки при захисті ключів
- Зберігання ключів у
BuildConfigабо ресурсах — перша ознака витоку. - Використання однакових ключів для dev та production — ризикуєте на етапі розробки.
- Відсутність обмежень у консолях провайдерів — Maps Key без рестрикції вкраде будь-хто.
- Нехтування ротацією ключів — періодично міняйте ключі, особливо при підозрі на витік.
Що входить у нашу роботу
- Аудит усіх API-ключів та секретів у коді, конфігураціях та білд-скриптах.
- Міграція критичних ключів на сервер з налаштуванням проксі-сервісу.
- Впровадження Keychain/Keystore для токенів та клієнтських секретів.
- Налаштування обмежень у Google Cloud Console, Stripe, Firebase та інших сервісах.
- Документація з описом нової схеми зберігання.
- Post-launch підтримка — місяць безкоштовних консультацій.
Процес роботи: від аудиту до деплою
- Аналіз — шукаємо всі місця, де використовуються ключі.
- Проектування — вирішуємо, які ключі виносимо на сервер, які залишаємо на пристрої.
- Реалізація — пишемо код для Keychain/Keystore, проксі-сервера, оновлюємо білд-процес.
- Тестування — перевіряємо, що ключі не витікають навіть при аналізі трафіку або декомпіляції.
- Деплой та моніторинг — публікуємо оновлення, налаштовуємо алерти на незвичну активність.
Повна реалізація схеми захисту займає від 2 до 5 днів залежно від кількості ключів та архітектури. Вартість розраховується індивідуально після аудиту. Ми захистили понад 20 мобільних проєктів. Зв'яжіться з нами для безкоштовної оцінки вашого проєкту. Отримайте консультацію — напишіть нам.







