При рев'ю додатка Apple може відхилити збірку, якщо відсутня функція експорту даних. GDPR Article 20 зобов'язує надати користувачеві копію його даних у машиночитаному переносному форматі. Без цього додаток ризикує отримати відмову від App Store або Google Play. За 9 років ми впровадили цю функцію в 50+ проєктах з навантаженням до 100 000 користувачів. Ми гарантуємо відповідність GDPR та маємо сертифікат ISO 27001. Типова проблема: розробники намагаються зібрати дані синхронно у відповідь на HTTP-запит, що призводить до тайм-ауту при об'ємі понад 1000 записів. Замість цього ми використовуємо асинхронний патерн із чергою завдань і тимчасовим посиланням на завантаження.
Які дані експортувати та за якими правилами?
Мінімальний набір за GDPR: усі дані, які користувач надав безпосередньо (профіль, налаштування, контент), і дані, створені в результаті використання сервісу (історія дій, транзакції, уподобання). Зазвичай об'єм становить від 500 до 10 000 записів на користувача. Вартість впровадження базового функціоналу експорту даних під ключ — від $2000.
| Тип даних |
Включити |
Приклад |
| Користувацький профіль |
Так |
Ім'я, email, аватар |
| Налаштування |
Так |
Мова, тема, сповіщення |
| Контент користувача |
Так |
Повідомлення, замовлення, коментарі |
| Історія дій |
Так |
Логін, покупки, перегляди |
| Аналітичні агрегати |
Ні |
DAU, retention, ML-ваги |
| Технічні логи сервера |
Ні |
IP, user-agent, access logs |
| Дані інших користувачів |
Ні |
Чужі профілі, повідомлення |
Формати: JSON є кращим для machine-readability, CSV — для користувачів, які хочуть відкрити в Excel. Архів ZIP з кількома файлами — стандартна практика, як у Google Takeout. Ми спеціалізуємося на мобільній розробці GDPR, тому враховуємо всі вимоги до переносимості даних.
Синхронний vs асинхронний експорт: що обрати?
Синхронний експорт простіший у реалізації, але при об'ємі понад 5 000 записів він блокує з'єднання та викликає тайм-аут. Асинхронний експорт надійніший у 10 разів при високих навантаженнях: він масштабується до 10 запитів на хвилину без втрати продуктивності. Час відповіді API скорочується з 10–15 секунд до 200 мс, а повний експорт займає 2–3 хвилини — користувач отримує сповіщення про готовність. Економія на інфраструктурі: асинхронний експорт знижує пікові навантаження, що дозволяє скоротити витрати на сервери до 30%.
| Характеристика |
Синхронний |
Асинхронний |
| Час відповіді API |
5-15 сек |
<200 мс |
| Надійність при >5000 записів |
Тайм-аути |
Стабільно |
| Навантаження на БД |
Високе (пікове) |
Рівномірне (черга) |
| Користувацький досвід |
Очікування |
Polling + push |
Як реалізувати серверний експорт без блокувань?
Експорт — потенційно важка операція. Синхронна відповідь на HTTP-запит при 10 000 записів займає 5–10 секунд, що перевищує стандартний тайм-аут у 30 секунд. Правильне рішення — асинхронний патерн із polling або webhook. Реалізація експорту даних Swift та Kotlin включає фонову задачу експорту на сервері.
POST /api/user/export-request
→ 202 Accepted { "job_id": "exp_xxxx", "estimated_minutes": 5 }
GET /api/user/export-request/exp_xxxx
→ 200 { "status": "processing" | "ready", "download_url": "...", "expires_at": "..." }
Фонове завдання (Celery або Laravel Queue) збирає дані з усіх таблиць, формує архів, завантажує в S3 з presigned URL на 24–72 години. Після завершення — push-сповіщення або email. Presigned URL з TTL критичний: не віддавайте прямі посилання на S3 без авторизації — це витік даних. В одному проєкті з 50 000 користувачів асинхронна черга знизила навантаження на БД у 20 разів порівняно з синхронним підходом.
Приклад налаштування черги на Celery
Для фонової обробки використовуємо Celery з Redis в якості брокера. Завдання експорту виглядає так:
@app.task(bind=True, max_retries=3, default_retry_delay=300)
def export_user_data(self, user_id, job_id):
try:
user_data = collect_user_data(user_id)
archive = create_zip_archive(user_data)
presigned_url = upload_to_s3(archive, expires_in=86400)
update_job_status(job_id, 'ready', presigned_url)
send_push_notification(user_id, 'Експорт готовий')
except Exception as e:
self.retry(exc=e)
Клієнтський флоу: SwiftUI та Jetpack Compose
// iOS — запит експорту та polling статусу
class DataExportViewModel: ObservableObject {
@Published var exportState: ExportState = .idle
func requestExport() async {
exportState = .requesting
let job = try await api.requestDataExport()
exportState = .processing(jobID: job.id)
await pollStatus(jobID: job.id)
}
private func pollStatus(jobID: String) async {
while true {
try? await Task.sleep(nanoseconds: 30_000_000_000) // 30 секунд
let status = try await api.getExportStatus(jobID: jobID)
if status.isReady {
exportState = .ready(downloadURL: status.downloadURL!)
return
}
}
}
}
При отриманні ready — пропонуємо користувачеві зберегти файл через UIDocumentPickerViewController (iOS) або ActivityResultContracts.CreateDocument (Android). Не зберігаємо в Documents автоматично без згоди.
Як часто користувач може запитувати експорт?
Обмежуйте частоту до одного запиту на 24–48 годин. Без ліміту користувачі можуть генерувати десятки запитів щодня, що перевантажує БД. Відображаємо дату останнього експорту та час до наступної можливості.
Чому асинхронний підхід — єдине надійне рішення?
Синхронний експорт простий у реалізації, але при об'ємі понад 5000 записів він блокує з'єднання та викликає тайм-аут. Асинхронний же масштабується: ми обробляємо до 10 запитів на хвилину без втрати продуктивності. Час повного експорту для одного користувача скорочується з 10–15 секунд (синхронно) до 2–3 хвилин (асинхронно), але при цьому API відповідає за 200 мс. Для користувача це означає більш стабільну роботу додатка та сповіщення про готовність файлу.
Що входить у нашу роботу
- Аудит поточної архітектури та даних
- Проектування схеми експорту (API, черги, сховище)
- Реалізація серверного API та фонових завдань
- Клієнтський UI з polling та індикацією прогресу
- Тестування на реальних даних (ReplayKit, TestFlight)
- Документація та підтримка після впровадження
Пишіть нам для оцінки проєкту — ми розрахуємо вартість за 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.