Как реализовать асимметричную криптографию для распределенного приложения, где ключи могут храниться как строки? - PullRequest
0 голосов
/ 17 декабря 2018

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

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

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

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

1 Ответ

0 голосов
/ 17 декабря 2018

Вдохновение для этого ответа приходит от 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
}

}


...