Вдохновение для этого ответа приходит от Swift 3 экспорта SecKey в строку и NSString Crypt.m gist .
Не знаю, что такое PKI (шифрование с открытым ключом)является?Тогда хороший учебник: Все, что вы должны знать о сертификатах и PKI, но слишком боитесь спрашивать
Рекомендуемый способ использовать публичное / частное шифрование для больших наборов данных - это использовать публичный / приватныйшифрование, чтобы сначала поделиться временным симметричным ключом, затем использовать этот ключ для шифрования, отправки данных и разрешения удаленной стороне расшифровывать их.
Это казалось сложной задачей, но потом выяснилось, что Apple уже предоставляет эту возможность для iOS / macOS (поиск "На самом деле, API сертификатов, ключей и служб доверия")предоставляет простой способ сделать это. ")
Приведенный ниже код предназначен для тестирования, чтобы позволить любому экспериментировать с настройками.Вы выполняете это дважды - один с #if
, установленным на true
, второй с false
.При первом запуске (точно используя Simulator) вы получаете строковые представления открытого и закрытого ключей.Затем вы вставляете их в свойства класса, изменяете настройку #if
и повторно запускаете метод test
.
Затем метод test
заново создает оба ключа, шифрует предоставленные данные, затемруки, которые зашифровали данные для расшифровки.В конце исходные данные сравниваются с расшифрованными данными, и результат распечатывается.Необработанный код доступен в виде gist на github .
Код сконструирован как тестовый инструмент - вы можете запустить его так, как если бы он генерировал две строки, представляющие закрытый и открытый ключи,затем вставьте приведенные ниже, чтобы убедиться, что сами строки работают точно так же, как тест со свежесгенерированными ключами.Тест состоит из четырех этапов:
- запустить шифрование и дешифрование только сгенерированными ключами (SecKeys)
- преобразовать открытый ключ в строку, а затем воссоздать новый открытый SecKey изстроку, затем выполните тест шифрования / дешифрования
- так же, как указано выше, за исключением того, что преобразуйте закрытый ключ в строку и обратно
- так же, как указано выше, но оба ключа преобразованы в строки и обратно
Сущность выше также содержит класс Asymmetric
, который просто использует открытый ключ для шифрования данных - это то, что вы будете использовать в приложении (но оно полностью основано на методах этого класса).
Выиспользуйте алгоритм RSA или пару ключей эллиптической кривой.При текущем размере ключа оба используют 128-битный симметричный ключ AES для фактического шифрования данных (см. Заголовки Apple).
#if true
private let keyType = kSecAttrKeyTypeRSA // kSecAttrKeyTypeEC
private let algorithm = SecKeyAlgorithm.rsaEncryptionOAEPSHA512AESGCM // EncryptionOAEPSHA512AESGCM
private let keySize = 4096 // SecKey.h states that with this size, you get AES 256-bit encoding of the payload
#else
private let keyType = kSecAttrKeyTypeECSECPrimeRandom // kSecAttrKeyTypeECSECPrimeRandom
private let algorithm = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA512AESGCM
private let keySize = 384 // SecKey.h states that with this size, you get AES 256-bit encoding of the payload
#endif
@ objcMembers финальный класс AsymmetricTest: NSObject {
Здесь вы вставите ключи, сгенерированные при запуске программы.
// Some Key pair I generated - replace with your own
private let publicStr = """
BFZjQQZVrcHitn13Af89ASrRT2VVPa4yGCreBJim52R/d3yJj3iTroanc7XW+YLJpijFBMei6ddf
lb2PjJLvXNJy8hQItCFRlpbGj7ddSCOuBNyjQP+cpmddgFhy8KCbgw==
"""
private let privateStr = """
BFZjQQZVrcHitn13Af89ASrRT2VVPa4yGCreBJim52R/d3yJj3iTroanc7XW+YLJpijFBMei6ddf
lb2PjJLvXNJy8hQItCFRlpbGj7ddSCOuBNyjQP+cpmddgFhy8KCbg+Sy8M4IjGDI5gdzNmWhDQp2
mggdySIqrjVobCL5NcAg5utA/2QdJGCy9mPw0GkFHg==
"""
var publicKey: Data = Data()
var privateKey: Data = Data()
Запустите4 теста.Вы предоставляете данные - я протестировал с несколькими тысячами байтов, но он должен работать для любого размера данных.
func test(_ testData: Data) -> Bool {
func key2string(key: SecKey) -> String {
guard let keyData = secKey2data(key: key) else { fatalError("key2string FAILED!!!") }
let base64publicKey = keyData.base64EncodedString(options: [.lineLength76Characters, .endLineWithCarriageReturn])
return base64publicKey
}
func string2key(str: String, cfType: CFString) -> SecKey? {
let d = Data(base64Encoded: str, options: [.ignoreUnknownCharacters])
print("string2key: dataSize =", d?.count ?? "-1")
guard
let data = Data(base64Encoded: str, options: [.ignoreUnknownCharacters]),
let key = data2secKey(keyData: data, cfType: cfType)
else { return nil }
return key
}
func runTest(data testData: Data, keys: (public: SecKey, private: SecKey)) {
let d1 = Date()
let _ = self.encryptData(data: testData, key: keys.public)
print("Time:", -d1.timeIntervalSinceNow) // measure performance
if
let d1 = self.encryptData(data: testData, key: keys.public)
,
let d2 = self.decryptData(data: d1, key: keys.private)
{
print("Input len:", d1.count, "outputLen:", d2.count)
print("Reconstructed data is the same as input data:", testData == d2 ? "YES" : "NO")
} else {
print("TEST FAILED")
}
}
Если вы установите строку ниже false
, то вместо генерации ключей будетиспользуйте две строки в верхней части класса.
#if true // set to true, then copy the two strings to publicStr and privateStr above and set this to false
guard let keys = createKey(keySize: keySize) else { print("WTF"); return false } // size is important smaller failed for me
print("PUBLIC:\n\(key2string(key: keys.public))\n")
print("PRIVATE:\n\(key2string(key: keys.private))\n")
runTest(data: testData, keys: keys) // Original Keys
do { // So suppose we have our public app - it gets the public key in base64 format
let base64key = key2string(key: keys.public)
guard let key = string2key(str: base64key, cfType: kSecAttrKeyClassPublic) else { fatalError("FAILED!") }
runTest(data: testData, keys: (key, keys.private)) // Reconstructed public
}
do { // So suppose we have our private app - it gets the private key in base64 format
let base64key = key2string(key: keys.private)
guard let key = string2key(str: base64key, cfType: kSecAttrKeyClassPrivate) else { fatalError("FAILED!") }
runTest(data: testData, keys: (keys.public, key)) // Reconstructed private
}
do {
let base64keyPublic = key2string(key: keys.public)
guard let keyPublic = string2key(str: base64keyPublic, cfType: kSecAttrKeyClassPublic) else { fatalError("FAILED!") }
let base64keyPrivate = key2string(key: keys.private)
guard let keyPrivate = string2key(str: base64keyPrivate, cfType: kSecAttrKeyClassPrivate) else { fatalError("FAILED!") }
runTest(data: testData, keys: (keyPublic, keyPrivate)) // Reconstructed private
}
#else
do {
guard let keyPublic = string2key(str: publicStr, cfType: kSecAttrKeyClassPublic) else { fatalError("FAILED!") }
guard let keyPrivate = string2key(str: privateStr, cfType: kSecAttrKeyClassPrivate) else { fatalError("FAILED!") }
runTest(data: testData, keys: (keyPublic, keyPrivate)) // Reconstructed private
}
#endif
return true
}
Шифрует предоставленные данные с помощью предоставленного ключа (который должен быть открытым ключом):
func encryptData(data: Data, key: SecKey) -> Data? {
//var status: OSStatus = noErr
var error: Unmanaged<CFError>?
let cfData: CFData = data as NSData as CFData
guard SecKeyIsAlgorithmSupported(key, .encrypt, algorithm) else {
fatalError("Can't use this algorithm with this key!")
}
if let encryptedCFData = SecKeyCreateEncryptedData(key, algorithm, cfData, &error) {
return encryptedCFData as NSData as Data
}
if let err: Error = error?.takeRetainedValue() {
print("encryptData error \(err.localizedDescription)")
}
return nil
}
Расшифровывает предоставленные данныес помощью прилагаемого ключа (который должен быть закрытым ключом):
func decryptData(data: Data, key: SecKey) -> Data? {
var error: Unmanaged<CFError>?
let cfData: CFData = data as NSData as CFData
guard SecKeyIsAlgorithmSupported(key, .decrypt, algorithm) else {
fatalError("Can't use this algorithm with this key!")
}
if let decryptedCFData = SecKeyCreateDecryptedData(key, algorithm, cfData, &error) {
return decryptedCFData as NSData as Data
} else {
if let err: Error = error?.takeRetainedValue() {
print("Error \(err.localizedDescription)")
}
return nil
}
}
Генерируйте ключ - вам нужно только сделать его в реальной ситуации, а затем убедиться, что закрытый ключ остается закрытым:
func createKey(keySize: Int) -> (public: SecKey, private: SecKey)? {
var sanityCheck: OSStatus = 0
let publicKeyAttr:[CFString: Any] = [
kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : "com.asymmetric.publickey".data(using: .ascii)!
]
let privateKeyAttr:[CFString: Any] = [
kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : "com.asymmetric.privatekey".data(using: .ascii)!
]
let keyPairAttr:[CFString: Any] = [
kSecAttrKeyType : keyType,
kSecAttrKeySizeInBits : keySize,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr
]
var publicKey: SecKey? = nil
var privateKey: SecKey? = nil
sanityCheck = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
if sanityCheck == noErr {
return (publicKey!, privateKey!)
} else {
print("Fucked!")
return nil
}
}
Метод преобразования SecKey
в Data
:
func secKey2data(key: SecKey) -> Data? {
var error:Unmanaged<CFError>?
guard let keyData = SecKeyCopyExternalRepresentation(key, &error) as Data? else { error?.release(); return nil }
//print("secKey2data size \(keyData.count)")
return keyData
}
Метод преобразования Data
в SecKey
:
func data2secKey(keyData: Data, cfType: CFString) -> SecKey? {
var error:Unmanaged<CFError>?
let attrs: [CFString: Any] = [
kSecAttrKeyType: keyType,
kSecAttrKeyClass: cfType
]
let key = SecKeyCreateWithData(keyData as CFData, attrs as CFDictionary, &error)
if let err: Error = error?.takeRetainedValue() {
//let nsError: NSError = realErr
print("data2secKey ERR: \(err.localizedDescription)")
}
return key
}
}