Skip to main content

Шифрование файлов при поставке готового XCFramework

XCFramework — это формат архива, который позволяет собрать библиотеку или фреймворк для различных платформ и архитектур. Как правило, разработчики используют XCFramework для упрощения процесса сборки и развертывания своих приложений на различных устройствах.

Однако, при поставке готового XCFramework возникает вопрос о безопасности и конфиденциальности данных, хранящихся в этом архиве. Ведь библиотеки и фреймворки могут содержать конфиденциальную информацию, такую как ключи API, пароли и другие данные, которые не должны быть доступны посторонним.

Шифрование

Для обеспечения безопасности и конфиденциальности данных при поставке готового XCFramework разработчики могут использовать шифрование файлов. Шифрование позволяет защитить данные от несанкционированного доступа и использования.

Существует несколько способов шифрования файлов при поставке готового XCFramework. Для шифрования обычно используются такие шифры как DES или AES, в некоторых случая можно использовать а ассиметричные шифры, такие как RSA, DSA, Схему Эль-Гамаля или шифрование на элиптических кривых.

AES и DES являются симметричными алгоритмами шифрования, который использует один и тот же ключ для шифрования и дешифрования данных. Ключ может быть сгенерирован случайным образом и передан отдельно от XCFramework или зашит в него

Ассиметричные шифры, такие как AES, подразумевают использование публичного и приватного ключа. В данном случае закрытый ключ может быть передан отдельно от XCFramework, а открытый ключ может быть встроен в XCFramework.

Пример DES шифрования файлов

Для того чтобы зашифровать файл при помощи алгоритма DES можно воспользоваться инструментом opessl.

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

Размер блока для DES равен 64 битам. В основе алгоритма лежит сеть Фейстеля с 16 циклами (раундами) и ключом, имеющим длину 56 бит. Ключ обычно представляется 64-битовым числом, но каждый восьмой бит используется для проверки четности и игнорируется. Алгоритм использует комбинацию нелинейных (S-блоки) и линейных (перестановки E, IP, IP-1) преобразований.

С помощью инструмента openssl можно создавать ключи определенной длины в бинарном, HEX или Base64 формате. Чтобы создать ключ длиной 64 бита в формате HEX, можно выполнить следующую команду:

openssl rand -hex 8

Результатом такой работы может служить следующая строка

3fd6786e458fda1f

Это строка представляет собой 64-битный ключ.

Шифрование

Полученный на предыдущем этапе ключ теперь нужно правильно применить для шифрования.

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

Для шифрование такой модели openssl предоставляет необходимый функционал:

openssl enc -des-ecb -in "model.mlmodel" -out "model.enc" -K 3fd6786e458fda1f

Полный список поддерживаемых openssl шифров можно посмотреть используя следующую команду:

openssl ciphers -v

Для простоты в рамках данной статьи мы будем использовать des-ecb.

Шифрование сразу нескольких файлов

Для шифрования сразу нескольких файлов с раширением .mlmodel можно воспользоваться следующим скриптом:

#!/bin/bash

# for in all files

for file in *

do

# check file extension .mlmodel

if [[ "$file" == *.mlmodel ]]; then

# set output file name

out_file="${file%.*}.enc"

# encoding with openssl

openssl enc -des-ecb -in "$file" -out "$out_file" -K <your-key> # setup your 64 bit key

echo "File $file encrypted and saved as $out_file"

fi

done

После запуска данного скрипта, он зашифрует все файлы с расширением .mlmodel в той директории, в которой находится предложенный sh скрипт.

Дешифровка файла со стороны iOS

После того как файлы зашифрованы и сложены в проект, нужно разобраться как с ними работать.

CryptoUtils

Для удобства работы с функциями дешифровки хорошо было бы создать специальный класс:

class CryptoUtils {

struct BundledFile {
// ...
}

private static func decrypt(fileName: String, key: String) -> Data? {
// ...
}



static func decryptAndGetBundledFile(filename: String) -> BundledFile? {
// ...
}
}

Для начала разберемся, в каком формате должны возвращаться данные из функции decryptAndGetBundledFile.

BundledFile

BundledFile должен содержать информацию о бандле декодированного файла, имени файла и его расширении и выглядит так:

struct BundledFile {
let bundle: Bundle
let fileName: String
let ext: String
}

Decrypt

Метод decrypt принимает имя файла и ключ для расшифровки данных, а затем должен загружать файл из ресурсов XCFramework'а и производить его дешифровку.

private static func decrypt(fileName: String, password: String) -> Data? {

guard let filePath = Bundle(for: Self.self).path(forResource: "mldata/\(fileName).enc", ofType: nil) else {
return nil
}

let fileURL = URL(fileURLWithPath: filePath)

let key = password.hexStringBytes

// docoding
}

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

extension String {
var hexStringBytes: [UInt8]? {
var hex = self
var bytes = [UInt8]()
while !hex.isEmpty {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
if let byte = UInt8(hex[..<subIndex], radix: 16) {
bytes.append(byte)
hex = String(hex[subIndex...])
} else {
return nil
}
}
return bytes
}
}

Он позволяет преобразовывать строку, содержащую шестнадцатеричное представление байтов, в массив байтов [UInt8].

Он выполняет это путем разбиения строки шестнадцатеричного кода на отдельные пары символов и преобразования каждой из этих пар в соответствующий байт типа UInt8 с использованием метода UInt8(_:radix:). Затем он добавляет байты в массив и продолжает разбиение и преобразование, пока вся строка не будет прочитана.

Если в строке нечетное количество символов или символы не соответствуют шестнадцатеричному формату, метод вернет nil. В противном случае он вернет массив байтов, полученных из шестнадцатеричного представления строки.

Байтовое представление пароля необходимо передать в функцию CCCrypt из фреймворка CommonCrypto. Указав вид шифра, его опции, и IV, данный метод проведет дешифровку данных из файла и запишет дешифрованные данные в созданную переменную.

Полный код функции decrypt:

private static func decrypt(fileName: String, password: String) -> Data? {

guard let filePath = Bundle(for: Self.self).path(forResource: "mldata/\(fileName).enc", ofType: nil) else {
return nil
}

let fileURL = URL(fileURLWithPath: filePath)

let key = password.hexStringBytes

// docoding
let iv = Array(repeating: UInt8(0), count: kCCBlockSizeDES)

guard let data = try? Data(contentsOf: fileURL) else {
return nil
}

let decryptLength = size_t(data.count + kCCBlockSizeDES)
var decryptData = Data(count: decryptLength)
var numBytesDecrypted: Int = 0

let cryptStatus = decryptData.withUnsafeMutableBytes { decrypBytes in
data.withUnsafeBytes { dataBytes in
CCCrypt(
CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmDES),
CCOptions(kCCOptionECBMode),
key,
kCCKeySizeDES,
iv,
dataBytes, cryptLength,
decrypBytes, decryptLength,
&numBytesDecrypted
)
}
}

guard cryptStatus == kCCSuccess else {
return nil
}

cryptData.removeSubrange(numBytesDecrypted..<decryptData.count)

return cryptData
}

Создание временного файла

С дешифровкой файлов разобрались, теперь нужно реализовать функцию, которая будет сохранять дешифрованные файлы для использование их фреймворком CoreML.

Всю работу с файловой системой содержит в себе функция decryptAndGetBundledFile:


private static func decryptAndGetBundledFile(
filename: String, needRename: Bool = true
) -> BundledFile? {
let pass = "<your-key>"

// decrypt
guard let data = decrypt(fileName: filename, password: pass) else {
return nil
}

// get data length
let dataLength = data.count

// get documents directory
guard let dir = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
).first else {
return nil
}

// create directory for decrypted files
let mldataDirectory = dir.appendingPathComponent("mldata", isDirectory: true)
if !FileManager.default.fileExists(atPath: tdataDirectory.path) {
do {
try FileManager.default.createDirectory(
at: mldataDirectory,
withIntermediateDirectories: true,
attributes: nil
)
} catch {
NSLog("Failed to create directory with \(error)")
return nil
}
}

// get original file name or create UUID
let uuid: String = needRename ? UUID().uuidString : filename

let addtionalPath = "mldata/\(uuid).mlmodel"

// add filename to path
let fileURL = dir.appendingPathComponent(addtionalPath)

guard let fileURL = fileURL else { return nil }

// file write operation
do {
try data.write(to: fileURL, options: .atomic)
NSLog("write to URL = \(fileURL) success")
} catch let error {
NSLog("failed to write in \(fileURL) with \(error)")
}

// get file bundle
guard let bundle = Bundle(url: dir) else { return nil }

// create result
let result = BundledFile(bundle: bundle, fileName: uuid, ext: "mlmodel")
return result

}

Заключение

Шифрование файлов при поставке готового XCFramework позволяет обеспечить безопасность и конфиденциальность данных. Однако, разработчики также должны учитывать возможные недостатки этого подхода.

Материалы:

https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle https://habr.com/ru/companies/true_engineering/articles/475816/ https://en.wikipedia.org/wiki/Data_Encryption_Standard https://habr.com/ru/articles/140404/