Реализация шифрования локальных данных в мобильном приложении
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:
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 важен: он даёт и конфиденциальность, и аутентификацию целостности. CBC без MAC — плохой выбор, уязвим к атакам на padding.
На 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 — экспортировать их нельзя даже рутом.
Типичные ошибки, которые ломают всю схему
Первая — неправильный класс защиты на iOS. FileProtectionType.complete означает: файл недоступен, пока устройство заблокировано. Но если приложение получает push-уведомление в фоне и пытается читать базу — крэш. Разработчики в панике меняют на completeUnlessOpen или вообще убирают защиту. Правильное решение — разделить данные: критичные под .complete, фоновые операции под .completeUnlessOpen.
Вторая — хранение ключа рядом с данными. Встречал кейс, где ключ шифрования базы лежал в той же директории, что и зашифрованная база, просто в файле key.bin. Это не шифрование, это переименование.
Третья — использование пароля пользователя напрямую как ключа. AES требует 128 или 256 бит. Пароль «qwerty123» — это не ключ. Нужен KDF: PBKDF2 с минимум 100 000 итераций или Argon2id. На iOS — CommonCrypto.CCKeyDerivationPBKDF, на Android — SecretKeyFactory с PBKDF2WithHmacSHA256.
Интеграция с биометрией
Продвинутый вариант — ключ шифрования базы защищён биометрией через Keystore/Keychain. На Android: KeyGenParameterSpec.Builder с .setUserAuthenticationRequired(true) и .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG). Ключ создаётся один раз при первом входе с биометрией, потом при каждом открытии приложения пользователь проходит аутентификацию, ключ разблокируется из Keystore, база открывается.
На iOS аналогично через kSecAttrAccessControl с SecAccessControlCreateWithFlags и флагом .biometryAny или .biometryCurrentSet. Разница: .biometryCurrentSet инвалидирует ключ при добавлении новых отпечатков — важно для банковских приложений.
Процесс
Начинаем с инвентаризации: что и где хранится, есть ли уже шифрование, какие данные попадают под требования (PCI DSS, GDPR, локальные нормативы). Дальше — выбор схемы ключей, реализация слоя шифрования, интеграция с существующим хранилищем. Отдельно — тестирование сценариев: обновление приложения, восстановление из бэкапа, смена биометрии пользователем.
Срок зависит от объёма данных и наличия существующей схемы хранения. Если база уже есть и нужна миграция на SQLCipher — 3–5 дней с учётом тестирования. Шифрование файлового кэша и Keychain-интеграция — дополнительно 1–2 дня. Полная схема с нуля для нового проекта — быстрее.







