Перейти к содержанию

Хранение данных от сторонних сервисов в открытом виде

Критичность: СРЕДНИЙ

Описание

Проблема хранения данных — одна из самых часто встречающихся в мобильных приложениях. Причем речь может идти не только об информации, непосредственно связанной с самим приложением и пользователями (пароли, пин-коды, персональные и конфиденциальные сведения), но и о данных для доступа к сторонним сервисам. Сегодня мобильные приложения для реализации своего функционала часто используют внешние сервисы, включая рассылку Push-уведомлений, хранение изображений на S3, интеграцию с различными картами, сервисами местоположения и многое-многое другое. Очень важно правильно хранить токены, используемые для этих интеграций и контролировать права, которыми эти токены обладают. Платформа Стингрей умеет не только находить токены самых различных интеграций, но и определяет их валидность и права доступа.

Рекомендации

Если в приложении обнаружен валидный токен внешнего сервиса и он обладает избыточными правами, которые позволяют совершать действия, не предназначенные для клиентского приложения, необходимо:

  1. Отозвать существующий токен с избыточными правами.
  2. Выпустить новый токен, который будет обладать только необходимыми привилегиями.
  3. Правильно хранить подобную информацию на устройстве — можно воспользоваться стандартным функционалом Apple CryptoKit. В приведенном ниже примере библиотека Apple Encrypted Archive применяется для сжатия и шифрования строки при этом зашифрованное значение сохраняется в файл во временную директорию пользователя.

Для создания ключа можно воспользоваться алгоритмом PBKDF2, который сгенерирует ключ из данных пользователя «на лету», или воспользоваться кодом из примера, но в таком случае стоит учитывать, что ключ шифрования также нужно хранить безопасным способом.

let key = SymmetricKey(size: SymmetricKeySize.bits256)

Создадим объект, который содержит параметры, ключи и другие данные, необходимые библиотеке 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
}

После этого необходимо закодировать данные для записи.

guard let encodeStream = ArchiveStream.encodeStream(writingTo: encryptionStream) else {
    return
}

Для корректной записи необходимо определить заголовки при записи.

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)
}

Ссылки

  1. https://developer.apple.com/documentation/applearchive/encrypting_and_decrypting_a_string
  2. https://developer.apple.com/documentation/cryptokit/symmetrickey
  3. https://developer.apple.com/documentation/cryptokit
  4. https://developer.apple.com/documentation/applearchive/encrypting_and_decrypting_a_single_file
  5. https://developer.apple.com/documentation/applearchive/encrypting_and_decrypting_directories
К началу