Хранение данных от сторонних сервисов в открытом виде
Критичность: СРЕДНИЙ | |
Описание
Проблема хранения данных — одна из самых часто встречающихся в мобильных приложениях. Причем речь может идти не только об информации, непосредственно связанной с самим приложением и пользователями (пароли, пин-коды, персональные и конфиденциальные сведения), но и о данных для доступа к сторонним сервисам. Сегодня мобильные приложения для реализации своего функционала часто используют внешние сервисы, включая рассылку Push-уведомлений, хранение изображений на S3, интеграцию с различными картами, сервисами местоположения и многое-многое другое. Очень важно правильно хранить токены, используемые для этих интеграций и контролировать права, которыми эти токены обладают. Платформа Стингрей умеет не только находить токены самых различных интеграций, но и определяет их валидность и права доступа.
Рекомендации
Если в приложении обнаружен валидный токен внешнего сервиса и он обладает избыточными правами, которые позволяют совершать действия, не предназначенные для клиентского приложения, необходимо:
- Отозвать существующий токен с избыточными правами.
- Выпустить новый токен, который будет обладать только необходимыми привилегиями.
- Правильно хранить подобную информацию на устройстве — можно воспользоваться стандартным функционалом Apple CryptoKit. В приведенном ниже примере библиотека Apple Encrypted Archive применяется для сжатия и шифрования строки при этом зашифрованное значение сохраняется в файл во временную директорию пользователя.
Для создания ключа можно воспользоваться алгоритмом PBKDF2, который сгенерирует ключ из данных пользователя «на лету», или воспользоваться кодом из примера, но в таком случае стоит учитывать, что ключ шифрования также нужно хранить безопасным способом.
Создадим объект, который содержит параметры, ключи и другие данные, необходимые библиотеке Apple Encrypted Archive. Пример инициализирует контекст с помощью профиля и алгоритма сжатия, а также набора симметричных ключей для шифрования.
let context = ArchiveEncryptionContext(profile: .hkdf_sha256_aesctr_hmac__symmetric__none,
compressionAlgorithm: .lzfse)
try context.setSymmetricKey(key)
Создадим fileStream
, который будет записывать зашифрованный файл в файловую систему, при этом, режим файлового потока — writeOnly
. Важно, что используемые параметры указывают, что поток создает файл, если он не существует, а если файл существует, он должен быть усечен до нуля байтов, прежде чем поток выполнит какие-либо операции.
guard let destinationFileStream = ArchiveByteStream.fileStream(
path: destinationFilePath,
mode: .writeOnly,
options: [ .create, .truncate ],
permissions: FilePermissions(rawValue: 0o644)) else {
return
}
Далее необходимо создать поток шифрования, который будет использовать созданные ранее контекст шифрования и файловый поток для записи зашифрованной строки в файловую систему.
guard let encryptionStream = ArchiveByteStream.encryptionStream(
writingTo: destinationFileStream,
encryptionContext: context) else {
throw Error.unableToCreateEncryptionStream
}
После этого необходимо закодировать данные для записи.
Для корректной записи необходимо определить заголовки при записи.
let header = ArchiveHeader()
// The PAT field contains the file path. Specify the unarchived file name
// for the PAT field.
header.append(.string(key: ArchiveHeader.FieldKey("PAT"),
value: unarchivedFileName))
// The TYP field contains the compressed file type. Specify `regularFile`
// for the TYP field.
header.append(.uint(key: ArchiveHeader.FieldKey("TYP"),
value: UInt64(ArchiveHeader.EntryType.regularFile.rawValue)))
// The DAT field contains the compressed file payload. Specify the size
// of the uncompressed data, in bytes, for the DAT field.
header.append(.blob(key: ArchiveHeader.FieldKey("DAT"),
size: UInt64(string.utf8.count)))
// Write the header to the encode stream.
try encodeStream.writeHeader(header)
И наконец, запишем необходимую строку в зашифрованном виде в файл.
var mutableString = string
try mutableString.withUTF8 { textPtr in
let rawBufferPointer = UnsafeRawBufferPointer(textPtr)
try encodeStream.writeBlob(key: ArchiveHeader.FieldKey("DAT"),
from: rawBufferPointer)
}
Ссылки
- https://developer.apple.com/documentation/applearchive/encrypting_and_decrypting_a_string
- https://developer.apple.com/documentation/cryptokit/symmetrickey
- https://developer.apple.com/documentation/cryptokit
- https://developer.apple.com/documentation/applearchive/encrypting_and_decrypting_a_single_file
- https://developer.apple.com/documentation/applearchive/encrypting_and_decrypting_directories