如何为密钥都可以存储为字符串的分布式应用程序实现非对称加密?

时间:2018-12-17 00:46:47

标签: ios swift encryption public-key-encryption

我似乎有一个简单的需求-我想生成一个可以放入应用程序中的公钥,并用来加密可能暴露给其他人的数据。后来,我希望使用只为自己所知的私钥来解密该数据。

到处都有解决这个问题的零散方案,但是我还没有找到有关如何做到这一点的很好的解释:

  • 生成可转换为字符串表示形式的密钥,然后将其用于重构密钥
  • 将数据对象传递给加密,并获取加密数据的字符串表示形式
  • 将表示加密数据的字符串转换回Data对象,然后将其解密为原始格式
  • 仅使用Swift 4.1或更高版本执行上述所有操作

我知道有些框架可以做到这一点,但是看来这最终应该只是一小段代码,因此框架是过大的。

1 个答案:

答案 0 :(得分:0)

此答案的灵感来自Swift 3 export SecKey to StringNSString Crypt.m gist

不知道什么是PKI(公钥加密)?然后是一个不错的教程:Everything you should know about certificates and PKI but are too afraid to ask

对大型数据集使用公共/私有加密的推荐方法是使用公共/私有加密首先共享一个临时对称密钥,然后使用该密钥进行加密,发送数据并允许远程端对其进行解密。

这似乎是一项复杂的任务,但后来发现Apple already provides this capability for iOS/macOS(搜索“实际上,证书,密钥和信任服务API提供了一种简单的方法来实现这一目标。”)

以下代码旨在作为一种测试工具,让任何人都可以尝试进行设置。您进行了两次锻炼-一次将#if设置为true,第二次设置为false。在第一次运行中(一定要使用模拟器),您将获得公钥和私钥的字符串表示形式。然后,将它们粘贴到类属性中,更改#if设置,然后重新运行test方法。

然后,test方法将重新创建两个密钥,对提供的数据进行加密,然后将已加密的数据交给解密。最后,将原始数据与解密后的数据进行比较,并打印出结果。原始代码is available as gist on github

该代码被构造为测试工具-您可以按原样运行它,以使其生成代表私钥和公钥的两个字符串,然后将其粘贴在下面以验证字符串本身是否与密钥一样完全像测试一样执行产生。该测试分为四个阶段:

  • 仅使用生成的密钥(SecKeys)运行加密和解密
  • 将公共密钥转换为字符串,然后从该字符串重新创建一个新的公共SecKey,然后进行加密/解密测试
  • 与上述相同,除了将私钥转换为字符串然后返回
  • 与上述相同,但两个键都转换为字符串并返回

上面的要点还包含一个类Asymmetric,该类仅使用公共密钥来加密数据-这是您将在应用程序中使用的类(但它完全基于此类中的方法)。

您可以使用RSA算法或“椭圆曲线”密钥对。在当前密钥大小的情况下,两者均使用AES 128位对称密钥进行实际数据加密(请参阅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
}

}