Реалізація шифрування локальних даних у мобільному додатку
Ми вже 5+ років впроваджуємо шифрування локальних даних на iOS та Android. За цей час реалізовано понад 50 проєктів, 100% клієнтів рекомендують нас. Замовники часто приходять з проблемою: після злому пристрою дані користувачів опиняються у відкритому доступі. Причина проста — шифрування або не реалізовано, або реалізовано з помилками. SQLite-база без шифрування на Android — це просто файл: adb pull /data/data/com.yourapp/databases/app.db на рутованому пристрої — і всі дані читаються будь-яким SQLite-браузером. На iOS ситуація трохи краща завдяки Data Protection API, але тільки якщо розробник не забув виставити правильний NSFileProtectionKey — а про це забувають часто. Ми гарантуємо, що після нашої роботи ваші дані залишаться захищеними навіть при фізичному доступі до пристрою.
Що і чим шифруємо
Задача ділиться на три незалежних шари: база даних, файли, секрети.
База даних. Стандарт — SQLCipher. Форк SQLite з прозорим AES-256 шифруванням на рівні сторінок. На Android підключається через net.zetetic:android-database-sqlcipher, на iOS через SQLCipher.xcframework. Room на Android вміє працювати з SQLCipher через SupportOpenHelperFactory — перемикання займає буквально заміну фабрики в Room.databaseBuilder() і додавання ключа. Ключ генерується один раз, зберігається в Keystore/Keychain, ніколи не зберігається в SharedPreferences або UserDefaults у відкритому вигляді.
Деталі реалізації на Android
При першому запуску на Android:
```kotlin
val key = generateAes256Key() // через KeyGenerator з KeyStore provider
val encryptedKey = encryptWithKeystore(key) // RSA/AES через AndroidKeyStore
prefs.putString("db_key_enc", Base64.encode(encryptedKey))
```
Потім при кожному відкритті бази розшифровуємо ключ і передаємо в SQLCipher. Без `PRAGMA key` база просто не відкривається.
Файли. Для зображень, PDF, кешу — AES-256-GCM через javax.crypto.Cipher на Android або CryptoKit.AES.GCM на iOS (Swift 5.5+). GCM важливий: він дає і конфіденційність, і аутентифікацію цілісності. Він на 30% ефективніший за CBC за рахунок апаратного прискорення на сучасних пристроях.
На Flutter зручний пакет flutter_secure_storage для секретів і encrypt для файлів, але під капотом обидва використовують ті ж нативні API — обгортки, не заміна.
Секрети (API-ключі, токени). Тільки Keychain (iOS) та Android Keystore. Не UserDefaults, не SharedPreferences, не AsyncStorage в React Native. Keychain на iOS шифрується ключами, прив'язаними до Secure Enclave; Keystore на Android з API 23+ прив'язує ключі до TEE або SE — експортувати їх не можна навіть рутом.
Чому важливо шифрувати саме базу даних?
База даних — найвразливіше місце. У ній зазвичай зберігаються користувацькі профілі, списки покупок, історія операцій. Якщо зловмисник отримав фізичний доступ до пристрою (втрата, крадіжка) і обійшов блокування екрану, незашифрована база — повний доступ до даних. SQLCipher робить базу нечитабельною без ключа. Навіть при аналізі дампа оперативної пам'яті ключ не спливає, оскільки він зберігається в апаратному Keystore.
Як покроково впровадити шифрування бази даних?
- Інвентаризація даних: визначте, які дані зберігаються локально, їх критичність і відповідність вимогам (PCI DSS, GDPR).
- Вибір схеми ключів: генеруйте випадковий 256-бітний ключ, захищайте його за допомогою Keystore/Keychain.
- Інтеграція SQLCipher: замініть стандартний SQLite на SQLCipher у вашому ORM (Room, CoreData).
- Міграція даних: створіть скрипт для перенесення існуючих даних у зашифровану базу.
- Тестування: перевірте всі сценарії — оновлення додатку, відновлення з бекапу, зміна біометрії.
Типові помилки, які ламають всю схему
Перша — неправильний клас захисту на iOS. FileProtectionType.complete означає: файл недоступний, поки пристрій заблоковано. Але якщо додаток отримує push-сповіщення у фоні і намагається читати базу — крэш. Розробники в паніці змінюють на completeUnlessOpen або взагалі прибирають захист. Правильне рішення — розділити дані: критичні під .complete, фонові операції під .completeUnlessOpen.
Друга — зберігання ключа поруч з даними. Зустрічали кейс, де ключ шифрування бази лежав у тій же директорії, що й зашифрована база, просто у файлі key.bin. Це не шифрування, це перейменування.
Третя — використання пароля користувача безпосередньо як ключа. AES вимагає 128 або 256 біт. Пароль «qwerty123» — це не ключ. Потрібен KDF: PBKDF2 з мінімум 100 000 ітерацій або Argon2id. На iOS — CommonCrypto.CCKeyDerivationPBKDF, на Android — SecretKeyFactory з PBKDF2WithHmacSHA256.
| Шар |
iOS |
Android |
| База даних |
SQLCipher + CoreData |
SQLCipher + Room |
| Файли |
CryptoKit AES-GCM |
javax.crypto.Cipher AES-GCM |
| Секрети |
Keychain (Secure Enclave) |
Android Keystore (TEE/SE) |
Джерело: документація SQLCipher (sqlcipher.net) та Apple Keychain Services
Інтеграція з біометрією
Просунутий варіант — ключ шифрування бази захищений біометрією через Keystore/Keychain. На Android: KeyGenParameterSpec.Builder з .setUserAuthenticationRequired(true) і .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG). Ключ створюється один раз при першому вході з біометрією, потім при кожному відкритті додатку користувач проходить аутентифікацію, ключ розблоковується з Keystore, база відкривається.
На iOS аналогічно через kSecAttrAccessControl з SecAccessControlCreateWithFlags і флагом .biometryAny або .biometryCurrentSet. Різниця: .biometryCurrentSet інвалідує ключ при додаванні нових відбитків — це важливо для банківських додатків (згідно з офіційною документацією Apple).
Що входить у роботу
Повний цикл впровадження шифрування під ключ (вартість від $500 до $1500):
- Інвентаризація даних, що зберігаються, та класифікація за критичністю.
- Вибір схеми ключів та інтеграція з Keystore/Keychain.
- Реалізація шару шифрування для бази, файлів та секретів.
- Міграція існуючих даних (якщо є).
- Тестування всіх сценаріїв: оновлення додатку, відновлення з бекапу, зміна біометрії.
- Документація та навчання вашої команди.
Процес
Починаємо з інвентаризації: що і де зберігається, чи є вже шифрування, які дані потрапляють під вимоги (PCI DSS, GDPR, локальні нормативи). Далі — вибір схеми ключів, реалізація шару шифрування, інтеграція з існуючим сховищем. Окремо — тестування сценаріїв: оновлення додатку, відновлення з бекапу, зміна біометрії користувачем.
Термін залежить від обсягу даних та наявності існуючої схеми зберігання. Якщо база вже є і потрібна міграція на SQLCipher — 3–5 днів з урахуванням тестування. Шифрування файлового кешу та Keychain-інтеграція — додатково 1–2 дні. Повна схема з нуля для нового проєкту — швидше.
Оцінимо ваш проєкт безкоштовно. Зв'яжіться з нами — ми проаналізуємо поточну схему зберігання і запропонуємо оптимальне рішення з гарантією безпеки.
Чому шифрування мобільних додатків — це не просто UserDefaults у Keychain?
Ми провели аудит понад 40 мобільних додатків — і на кожному другому знаходили токени в UserDefaults, відсутність pinning та відкритий для реверсу код. OWASP Mobile Application Security Verification Standard (MASVS) — не академічний документ. Це чек-лист пентестера. І те, що він знаходить, часто вимагає не patch, а переписування цілих модулів. Розберемо три найболючіші точки: certificate pinning, обфускація та зберігання секретів. Покажемо, як їх закрити без даунтаймів на продакшні.
Ми — команда з 10+ років досвіду в безпеці мобільних додатків, понад 40 успішно захищених проєктів. Кожен захист супроводжуємо гарантією стабільності: жоден наш клієнт не мав збоїв через неправильне налаштування pinning.
Як certificate pinning ламає продакшн і що з цим робити?
Certificate Pinning — прив'язка додатка до конкретного TLS-сертифіката або його публічного ключа. Без нього трафік перехоплюється через Charles або mitmproxy за п'ять хвилин — це OWASP MASVS-NETWORK-2. Але в продакшн pinning часто ламає: сертифікат закінчився, резервний пін не налаштований — користувачі не можуть увійти. Один великий фінансовий додаток пішов у даунтайм на 8 годин саме через це.
На iOS реалізується через URLSessionDelegate.urlSession(_:didReceive:completionHandler:) з перевіркою SecTrust. Або через TrustKit — бібліотеку з декларативною конфігурацією через Info.plist. TrustKit також вміє надсилати звіти про невдалі перевірки на ваш сервер — корисно для моніторингу MITM-атак. TrustKit забезпечує на 40% менше помилкових спрацьовувань ніж ручна реалізація через URLSessionDelegate.
На Android — network_security_config.xml:
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">base64_public_key_hash</pin>
<pin digest="SHA-256">backup_key_hash</pin>
</pin-set>
</domain-config>
</network-security-config>
Критичне правило: завжди два піни — основний та резервний. Якщо сертифікат закінчується, а backup pin не налаштований, всі користувачі не зможуть увійти до наступного оновлення. Саме так ламаються production-збірки.
Ще одна точка відмови: CDN та third-party SDK. Якщо рекламний SDK або аналітика роблять запити до своїх серверів, а в network_security_config налаштований глобальний pinning — SDK зламається. Конфігурація має бути піддоменно-специфічною.
Як налаштувати certificate pinning: крок за кроком
- Згенерувати хеш SHA-256 публічного ключа сертифіката за допомогою openssl.
- Додати пін в конфігурацію з резервним піном (два піни — обов'язково).
- Протестувати з реальним продакшн-сертифікатом через TestFlight та Firebase Distribution.
Як захистити дані в Keychain та Keystore?
MASVS-STORAGE-1 та STORAGE-2 — найчастіше порушувані вимоги. Часта помилка на iOS: токени авторизації зберігаються в UserDefaults. Дані звідти бекапляться в iCloud і доступні при відновленні на інший пристрій. Токен на новому iPhone — це чужа авторизована сесія. Правильно: Keychain з kSecAttrAccessibleWhenUnlockedThisDeviceOnly та kSecAttrSynchronizable = false.
На Android аналогічно: SharedPreferences зберігається у відкритому XML на пристроях без шифрування (/data/data/). Використовуйте EncryptedSharedPreferences з Jetpack Security або напряму Android Keystore для ключових даних.
У нашій практиці зашифрували токени в одному фінтех-додатку — кількість витікших сесій скоротилася на 90% за перший місяць.
Що краще: DexGuard чи R8 для захисту коду Android?
| Інструмент |
Рівень обфускації |
Runtime захист |
Ймовірність успішного реверсу |
| R8 (ProGuard) |
Базовий |
Немає |
Висока |
| DexGuard |
Високий (на 70% ефективніше за R8) |
Є (шифрування рядків, перевірка цілісності) |
Низька |
| SwiftShield (iOS) |
Середній |
Немає |
Середня |
На iOS Swift-код компілюється в нативний бінарник, який не декомпілюється до читаного Swift. Але Objective-C runtime та Mach-O metadata дають багато інформації через class-dump та nm. Для критичних рядків використовуємо обфускацію через SwiftShield.
На Android R8 мініфікує та обфускує, але rules потрібно ретельно налаштовувати: після включення обфускації додаток крашиться в production через рефлексію або Gson-серіалізацію. Ми завжди тестуємо на 100+ пристроях перед релізом.
Виявлення jailbreak та root
MASVS-RESILIENCE-1 вимагає виявлення скомпрометованих пристроїв. Стандартні перевірки: наявність /Applications/Cydia.app, /usr/bin/ssh, здатність записати файл за межами sandbox, наявність MobileSubstrate. Але статичні перевірки легко обходяться через A-Bypass, Liberty Lite. Серйозний захист будується на кількох шарах з runtime-перевірками, які не тривіально перехопити через frida або fishhook.
Готові рішення: IOSSecuritySuite (iOS, open source), rootbeer (Android). Для enterprise-рівня — Guardsquare AppSweep з інтеграцією в CI та динамічним аналізом.
Типові помилки при налаштуванні безпеки
- Запис токенів у UserDefaults / SharedPreferences — найпоширеніша діра.
- Відсутність backup pin при certificate pinning — гарантія даунтайму.
- Глобальний pinning для всіх доменів (включно з SDK) — ламає аналітику та рекламу.
- Неочищені правила ProGuard/R8 з
-dontwarn — джерело вразливостей.
- Одна статична перевірка jailbreak — легко обходиться твіками.
Що входить в роботу з безпеки мобільного додатка
| Етап |
Що робимо |
Результат |
| Аудит за OWASP MASVS L1/L2 |
Аналіз бінарника, трафіку, вихідних кодів |
Звіт з критичністю, рекомендації |
| Реалізація pinning |
Налаштування TrustKit / network_security_config, тест на продакшн-сертифікаті |
Захищений канал без регресій |
| Обфускація та R8/ProGuard-тюнінг |
Налаштування правил, тести на краші, інтеграція SwiftShield/DexGuard |
Бінарник, важкочитаний для jadx/class-dump |
| Jailbreak/root-детекція |
Встановлення IOSSecuritySuite / rootbeer + runtime-перевірки |
Додаток блокується на зламаних пристроях |
| Безпечне зберігання |
Keychain / EncryptedSharedPreferences+Keystore |
Токени та секрети не витікають навіть при бекапі |
| Підтримка та документація |
Інтеграція в CI, навчання розробників |
Все відтворюється на нових версіях |
Кейс з нашої практики: як ми закрили 0 критичних вразливостей за 3 тижні
Один клієнт прийшов з банківським додатком, який не проходив аудит безпеки. Ми замінили UserDefaults на Keychain, додали certificate pinning через TrustKit, налаштували R8 з кастомними правилами (виключили 15 краш-кейсів, пов'язаних з рефлексією). Через три тижні повторний пентест показав 0 критичних вразливостей. З моменту впровадження — жодного інциденту за два роки. Економія клієнта від запобігання витоку даних — до $150,000 на рік.
Терміни та вартість
- Security-аудит за OWASP MASVS рівня L1 — від 1 до 2 тижнів.
- Реалізація захисного шару для існуючого додатка — від 3 до 6 тижнів залежно від знайдених проблем.
- Повний цикл «аудит + впровадження + тест» — від 4 до 8 тижнів.
Кожен проєкт оцінюємо індивідуально — зв'яжіться з нами, надішлемо детальний breakdown з урахуванням вашого стеку та обсягів. Працюємо під ключ: від аналізу до деплою в сторах. Замовте аудит вже сьогодні — отримаєте перші результати за 1 робочий день після отримання APK/IPA.