Реалізація шифрування трафіку в мобільних додатках
Стандартний HTTPS закриває канал, але не розв'язує кілька проблем: що саме передається в тілі запиту, як захиститися від компрометованої ЦС, та як працює шифрування для даних, які йдуть не у REST API, а у WebSocket, MQTT або BLE-канал.
TLS як основа — і чому цього недостатньо
Будь-який продакшн-додаток використовує HTTPS, але стандартна конфігурація TLS на iOS та Android вразлива до downgrade-атак та приймає сертифікати від сотень системних ЦС. Network Security Configuration на Android та App Transport Security на iOS встановлюють мінімальні вимоги до TLS.
Android Network Security Configuration (res/xml/network_security_config.xml):
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">primaryPinBase64==</pin>
<pin digest="SHA-256">backupPinBase64==</pin>
</pin-set>
</domain-config>
<base-config cleartextTrafficPermitted="false"/>
</network-security-config>
cleartextTrafficPermitted="false" блокує HTTP на рівні ОС — жоден компонент додатку не може проштовхнути незашифрований запит. На iOS аналог — NSAllowsArbitraryLoads: false у Info.plist (стандартно з iOS 9).
Примусова TLS 1.2+ на Android через OkHttp:
val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3)
.cipherSuites(
CipherSuite.TLS_AES_128_GCM_SHA256,
CipherSuite.TLS_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
)
.build()
Шифрування тела запиту — коли TLS недостатньо
Якщо API доступне через кілька точок (CDN, API gateway, сторонні сервіси), дані можуть бути видні на промежуточних вузлах. End-to-end шифрування тела запиту розв'язує це: сервер отримує зашифрований blob, проміжні вузли бачать лише метаданні.
Схема з libsodium (через обгортку swift-sodium або lazysodium-android):
- Клієнт при першому запуску генерує X25519 keypair, реєструє публічний ключ на сервері
- Сервер публікує свій публічний ключ
- Для кожного запиту:
crypto_box_easy(message, nonce, server_public_key, client_private_key)— ECDH + XSalsa20-Poly1305 - Відповідь сервера зашифрована аналогічно
Nonce повинна бути унікальною для кожного повідомлення — 24 байти випадкових даних від SecRandomCopyBytes / SecureRandom. Ніколи не інкрементальний лічильник без додаткового захисту.
Шифрування WebSocket-трафіку
WSS (WebSocket Secure) — TLS поверх WebSocket. Проблема така ж, як з HTTPS: TLS закриває канал, але не тіло. Для чатів та фінтех-додатків, де сервер не повинен зберігати повідомлення у plaintext, потрібен додатковий рівень.
Типовий підхід — Signal Protocol (libsignal): Double Ratchet + X3DH. Реалізований в офіційних SDK для iOS та Android. Простіша альтернатива для додатків без синхронізації між пристроями — NaCl secretbox з pre-shared key, обміненим через TLS при ініціалізації сесії.
BLE та локальні з'єднання
Bluetooth Low Energy не використовує TLS. Якщо додаток обмінюється даними з IoT-пристроєм через BLE, шифрування повинно бути реалізовано на рівні додатку.
Мінімальна схема: ECDH обмін ключами під час pairing (Curve25519), потім AES-256-GCM для кожного пакета з інкрементним nonce (захист від replay через лічильник, переданий у associated data). Стек: CryptoKit на iOS (нативно), Bouncy Castle або Tink на Android.
Антидетект та обфускація трафіку
Для додатків у регіонах з глибокою інспекцією пакетів (DPI) — окреме завдання: обфускація TLS fingerprint через зміну порядку TLS extensions, використання QUIC (HTTP/3) або domain fronting. Виходить за рамки стандартного шифрування трафіку.
Процес
Аудит поточних мережевих викликів — HTTP або HTTPS, версії TLS, наявність валідації сертифіката. Далі: конфігурація ATS/NSC, примусова TLS 1.2+, закріплення сертифіката на критичних кінцевих точках, шифрування тела там, де це вимагає threat model. Для нестандартних каналів (BLE, MQTT) — окремаевежи схема.
Терміни — 2–5 днів. Базова конфігурація TLS + NSC/ATS — 1 день. Шифрування тела запитів з key exchange — ще 2–3 дні.







