Реализация цифрового конверта в iOS

В связи с повсеместным распространением интернета и ростом сетевых приложений возникает первостепенная необходимость обеспечить им защиту. 

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

Криптография  —  это наука о методах шифрования и древнее искусство, которое гарантирует секретность и защищенность сообщений (записей) от посторонних лиц.

Назначение криптографии 

Криптография решает многие проблемы и обеспечивает: 

  • конфиденциальность (приватность); 
  • целостность данных;
  • аутентификацию; 
  • невозможность отказа от факта совершения действия. 

Криптография с секретным ключом (симметричная) 

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

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

Криптография с открытым ключом (асимметричная) 

В настоящее время для обмена ключами и цифровых подписей применяются следующие алгоритмы криптографии с открытым ключом: RSA, Rabin, ElGamal, Paillier, Elliptic Curve Cryptography (ECC, криптография на эллиптических кривых), Cramer-Shoup и многие другие. 

Гибридный алгоритм шифрования

В статье рассматривается технология гибридного шифрования для защищенной передачи данных посредством двух методов. Алгоритм AES зашифровывает открытый текст, а алгоритм RSA  —  ключ AES, обеспечивая безопасную передачу данных между сторонами “клиент-клиент” и “клиент-сервер” и усложняя злоумышленникам доступ к этим данным. 

В алгоритме исходные данные поступают в систему, а на выходе мы получаем данные, закодированные ключом AES. Сам ключ AES зашифровывается открытым ключом RSA. Все это происходит не на разных, а на одном этапе выполнения. На стороне получателя осуществляется процесс расшифрования. 

Гибридное шифрование 
  1. Отправитель генерирует общий ключ для шифрования открытого текста. 
  2. Открытый текст шифруется общим ключом, сгенерированным отправителем. 
  3. Отправитель применяет открытый ключ принимающей стороны для шифрования общего ключа. 
  4. Отправитель отсылает зашифрованный текст вместе с зашифрованным общим ключом. 
  5. Принимающая сторона расшифровывает зашифрованный общий ключ с помощью своего секретного ключа. 
  6. Принимающая сторона преобразует зашифрованный текст в открытый путем расшифровки общего ключа. 

AES (Advanced Encryption Standard, т.е. передовой стандарт шифрования)  —  это алгоритм шифрования с общим ключом, созданный вслед за DES и действующий как современный стандарт шифрования по умолчанию. 

RSA  —  это алгоритм шифрования с открытым ключом. На его работу влияют возможности вычислительной машины, поскольку этот алгоритм основан на сложности задачи факторизации простых чисел. Он требует надлежащей длины ключа, указанной в руководстве “Проблемные вопросы алгоритма шифрования в 2010 году”. На данный момент стандартная длина составляет 2048 бит. 

Реализация 

Перед реализацией цифрового конверта кратко обозначим несколько моментов.

  1. При реализации цифрового конверта необходимо привести в соответствие среду шифрования и бэкенд. Например, при намерении бэкенда использовать RSA/ECB/OAEPWithSHA-256 следует реализовать ту же самую среду на стороне клиента. 
  2. Различные фреймворки помогают ускорить разработку и сократить количество ошибок, возможных в процессе реализации.

Демо-проект

Предлагаем ссылку на демо-проект, описывающий процесс реализации цифрового конверта. 

Сначала реализуем генератор IV и KEY для алгоритма AES, который создает простую произвольную строку String длиной 16 бит:

import Foundation

func generateRandomString(length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}

Существуют 3 варианта длины ключей шифрования AES. Каждая длина включает разное количество возможных комбинаций ключей. Несмотря на то, что длина ключа данного метода шифрования варьируется, размер блока (128 бит или 16 байт) остается фиксированным.

На следующем этапе реализуем пару ключей. RSAKeyPair  —  это имя для открытого и закрытого ключей, используемых алгоритмом RSA. Открытый ключ RSA представляет собой ключ шифрования. Закрытый ключ предназначен для расшифрования и держится в секрете. Он гарантирует, что прочитать данные сможет только обозначенный получатель:

import Foundation

static func generateRSAKeyPair() throws -> RSAKeyPair {

// параметры закрытого ключа
let privateKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrApplicationTag as String: RSAConfig.kRSAApplicationTag as AnyObject
]

// параметры открытого ключа
let publicKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrApplicationTag as String: RSAConfig.kRSAApplicationTag as AnyObject
]

// глобальные параметры генерации ключа
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: RSAConfig.keySize as AnyObject,
kSecPublicKeyAttrs as String: publicKeyParams as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject,
]

// проверка состояния после генерации ключа
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)

if status != errSecSuccess {
var error = CryptoException.unknownError
switch (status) {
case errSecDuplicateItem: error = .duplicateFoundWhileTryingToCreateKey
case errSecItemNotFound: error = .keyNotFound
case errSecAuthFailed: error = .authFailed
default: break
}
throw error
}
return RSAKeyPair(privateKey: privKey, publicKey: pubKey)
}

Операции AES256-CBC 

Данный алгоритм также широко известен как “блочный шифр”, поскольку делит данные на части, называемые блоками.

  • Ключ шифрования AES делит данные на массивы (4*4), в каждом из которых по 16 байтов. 
  • Каждый из этих байтов содержит максимум 8 бит. Но такое деление не меняет размер зашифрованного текста. Размер как исходного, так и зашифрованного текста составляет 128 бит. 

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

import Foundation
import CommonCrypto

private func aes_CBC_256(operation: Int,
algorithm: Int,
options: Int,
key: Data,
initializationVector: Data,
dataIn: Data) -> Data? {
return initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in
return key.withUnsafeBytes { keyUnsafeRawBufferPointer in
return dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in
// Предоставление данным сбодного места для заполнения по стандарту PKCS7.
let dataOutSize: Int = dataIn.count + kCCBlockSizeAES128 * 2
let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize, alignment: 1)
defer { dataOut.deallocate() }
var dataOutMoved: Int = 0
let status = CCCrypt(CCOperation(operation),
CCAlgorithm(algorithm),
CCOptions(options),
keyUnsafeRawBufferPointer.baseAddress, key.count,
ivUnsafeRawBufferPointer.baseAddress,
dataInUnsafeRawBufferPointer.baseAddress, dataIn.count,
dataOut, dataOutSize,
&dataOutMoved)
guard status == kCCSuccess else { return nil }
return Data(bytes: dataOut, count: dataOutMoved)
}
}
}
}

Для выполнения низкоуровневых криптографических операций, подобных AES в рассматриваемой реализации, по-прежнему рекомендуется работать с библиотекой Common Crypto.

Как видно в представленном методе, наряду с алгоритмом и заполнением (англ. padding) он получает входные данные с именем IV и KEY.

IV

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

KEY

Поскольку AES поддерживает несколько размеров ключей, целесообразно выбрать наилучший из них. В коммерческих приложениях чаще всего предпочтение отдается AES-128, устанавливающему баланс между безопасностью и скоростью. В целях обеспечения максимальной безопасности национальные правительства обычно выбирают AES-192 и AES-256.

Операции RSA-PKCS1 

Алгоритм RSA обеспечивает безопасность ключа в AES. Как и в случае с AES, RSA предусматривает соблюдение ряда требований:

  • Не следует отправлять зашифрованный текст вместе с закрытым ключом.
  • Не стоит применять 1024-битные ключи RSA, поскольку они ненадежны. Выбирайте ключи длиной не менее 2048 бит. 
  • Шифрование выполняется с помощью открытого ключа, а расшифрование  —  закрытого.
  • Шифрование с ключами RSA имеет свои ограничения: во-первых, оно зашифровывает и расшифровывает только небольшие объемы данных. Во-вторых, медленно работает

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

Далее приведем пример открытого и закрытого ключей. 

Открытый ключ

Открытый ключ RSA, зашифрованный в традиционном формате RSA, выглядит так: 

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApwni+ErA4h6wyqAYz39p
f3dOlvgRX8I1npz2Cx3Y1ASNl0zfhCK+9r48FisEuRb36iEz8OPk4O7hZIWb2cHg
7wNXwUL09jO0rdSquGyPiJXNM/v04CTZo61r5iZ1cLSnLSw0NU4BOedK2mZaFqJh
FJDeu44TGmz/x+8l50JAgD3XGk/NlTyYgRGwqpu8TFcCT8XoxEYq2QScfxq+2FnG
NFX6bVi1zDSj0yBv90uelsM226zwzdGO0MZnls4AqwfzayTL4zQlI/2CFajnf4no
agjbkR8jdFk4je5kLa58smRKA+ce1cb6UHfPQJD6+lVgSLU2uHmoj2KGmPDHtCDE
twIDAQAB
-----END PUBLIC KEY-----

Закрытый ключ 

Пример 2048-битного закрытого ключа RSA, соответствующего открытому ключу выше:

- - -BEGIN RSA PRIVATE KEY - - -
MIIEowIBAAKCAQEApwni+ErA4h6wyqAYz39pf3dOlvgRX8I1npz2
Cx3Y1ASNl0zfhCK+9r48FisEuRb36iEz8OPk4O7hZIWb2cHg7wNX
wUL09jO0rdSquGyPiJXNM/v04CTZo61r5iZ1cLSnLSw0NU4BOedK
2mZaFqJhFJDeu44TGmz/x+8l50JAgD3XGk/NlTyYgRGwqpu8TFcC
T8XoxEYq2QScfxq+2FnGNFX6bVi1zDSj0yBv90uelsM226zwzdGO
0MZnls4AqwfzayTL4zQlI/2CFajnf4noagjbkR8jdFk4je5kLa58
smRKA+ce1cb6UHfPQJD6+lVgSLU2uHmoj2KGmPDHtCDEtwIDAQAB
AoIBABDyJyflUuLIa6BtftbeKDJu73bQEoMnzWTFVmNo/cGp90Ct
jdIhQZpVUPyMFLM/qfBYufpARHdar1xmqZmn2k1P24FBwl7lKU6m
UMx0EXyXJpff0eWCsuuIPonq1ZpyA6vI1odCxwiuNdQoZHA8MmzV
hqqSTSEcQE0OSDYTyQzTTrwX+3g41WRHH24uN479DWQfIVcPX7u3
k8UjfgwtD3TYLQ2kiOawQ5WbxOPtLMPsa8GA8/PDNit9DSaDQuTv
4mATnwuJMp2FeUa9m3M/bcaEgTiEHq77kJZ8srJF/r+OwKbrxPE3
eeSPEfuP+wkg5AgOjhLnrdzwVRUDFGWvOECgYEAyIk7F0S0AGn2a
ryhw9CihDfimigCxEmtIO5q7mnItCfeQwYPsX721fLpJNgfPc9DD
fhAZ2hLSsBlAPLUOa0Cuny9PCBWVuxi1WjLVaeZCV2bF11mAgW2f
jLkAXT34IX+HZl60VoetSWq9ibfkJHeCAPnh/yjdB3Vs+2wxNkU8
m8CgYEA1TzmmjJq7M6f+zMo7DpRwFazGMmrLKFmHiGBY6sEg7Emo
eH2CkAQePIGQw/Rk16gWJR6DtUZ9666sjCH6/79rx2xg+9AB76XT
FFzIxOk9cm49cIosDMk4mogSfK0Zg8nVbyW5nEb//9JCrZ18g4lD
3IrT5VJoF4MhfdBUjAS1jkCgYB+RDIpv3+bNx0KLgWpFwgNOmb66
7B6SW2ya4x227KdBPFkwD9HYosnQZDdOxvIvmUZObPLqJan1aaDR
2Krgi1SoNJCNpZGmwbMGvTU1Pd+Nys9NfjR0ykKIx7/b9fXzman2
ojDovvs0W/pF6bzD3V/FH5HWKLOrS5u4X3JJGqVDwKBgQCd953Fw
W/gujld+EpqpdGGMTRAOrXqPC7QR3X5Beo0PPonlqOUeF07m9/zs
jZJfCJBPM0nS8sO54w7ESTAOYhpQBAPcx/2HMUsrnIjHBxqUOQKe
6l0zo6WhJQi8/+cU8GKDEmlsUlS3iWYIA9EICJoTOW08R04BjQ00
jS71A1AUQKBgHlHrV/6S/4hjvMp+30hX5DpZviUDiwcGOGasmIYX
AgwXepJUq0xN6aalnT+ykLGSMMY/LABQiNZALZQtwK35KTshnThK6
zB4e9p8JUCVrFpssJ2NCrMY3SUqw87K1W6engeDrmunkJ/PmvSDLY
eGiYWmEKQbLQchTxx1IEddXkK
 - - -END RSA PRIVATE KEY - - -

Apple предоставляет фреймворк безопасности для обработки RSA. Он потребуется для защиты конфиденциальных данных. В примере такими данными является ключ AES. 

Шифрование RSA

import Foundation
import CommonCrypto
import Security

func encryptBase64(text: String) -> String? {

guard let pubKey = RSAKeyManager.getPublicKey() else {return nil}
guard let data = text.data(using: String.Encoding.utf8) else {return nil}
var error: Unmanaged<CFError>?
if let encryptedData: Data = SecKeyCreateEncryptedData(pubKey,
RSAConfig.rsaAlgorithm,
data as CFData, &error) as? Data {
if error != nil {
return nil
} else {
return encryptedData.base64EncodedString()
}
} else {
return nil
}
}

Расшифрование RSA

import Foundation
import CommonCrypto
import Security

func decpryptBase64(encrpted: String) -> String? {

guard let privateKey = RSAKeyManager.getPrivateKey() else {return nil}
let data = NSData(base64Encoded: encrpted, options: .ignoreUnknownCharacters)!
var error: Unmanaged<CFError>?
if let decryptedData: Data = SecKeyCreateDecryptedData(privateKey,
RSAConfig.rsaAlgorithm,
data as CFData, &error) as? Data {
if error != nil {
return nil
} else {
return String(decoding: decryptedData, as: UTF8.self)
}
} else {
return nil
}
}

Заключение 

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

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

Ссылка на репозиторий GitHub

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Mehran Kamalifard: Build a Secure Envelope in iOS

Предыдущая статьяПочему стоит избегать метода push при разработке на JavaScript
Следующая статьяСупербыстрый веб-фреймворк Astro: подробный обзор