带有应用密码的 Swift 对称加密

Posted

技术标签:

【中文标题】带有应用密码的 Swift 对称加密【英文标题】:Swift Symmetric Encryption with app passcode 【发布时间】:2020-07-19 19:03:40 【问题描述】:

如果我能提供一些关于使用 Swift CryptoKit 进行数据加密的建议,我会非常有帮助。

目标

我想对一些数据进行 AES 加密,将其保存到应用程序包中,并在用户键入单独的密码后从应用程序中访问它。密码将单独分发,并以某种方式与对称密钥交互以解密数据。如果密码被泄露,我可以重新加密数据并使用新的加密数据更新应用程序包并分发新密码。

约束

数据必须包含在 app bundle 中,不能联网。 TouchId 不可用。 如果可能,我想避免使用苹果密码系统。相反,我想在应用程序本身中要求输入密码,以便使用自定义密码条目。

我尝试过的

我在 Swift 中做了一个对称加密命令行程序。此 AES 对一些本地数据进行加密、保存并提供密钥。

let keyForData = SymmetricKey(size: .bits256)
                
do 
// Get the documents
let fileURLs = try fileManager.contentsOfDirectory(at: inputDirURL, includingPropertiesForKeys: nil)
                    
for file in fileURLs 
   if (file.pathExtension == "json")
       let fileName = String(file.path.split(separator: "/" ).last ?? "")
       consoleIO.writeMessage("Processing \(fileName)")
       let data = getData(file)
       // Encrytion
       let encryptedData = try! AES.GCM.seal(data, using: keyForData)
       let encryptedDataFileURL = outputDirURL.appendingPathComponent(fileName + ".enc")
       if let combinedData = encryptedData.combined 
           try combinedData.write(to: encryptedDataFileURL)
           consoleIO.writeMessage("File saved to \(encryptedDataFileURL.path.split(separator: "/" ).last ?? "")")
        else 
           consoleIO.writeMessage("Problem daving encrypted file")
       
   

let keyString = keyForData.serialize()
let fileName = ".KEY_DO_NOT_COMMIT"
let encryptedKeyURL = outputDirURL.appendingPathComponent(fileName)
try keyString.write(to: encryptedKeyURL, atomically: false, encoding: .utf8)
consoleIO.writeMessage("Key saved to \(fileName)")

现在在设备包中我有加密的数据和密钥。

Apple 建议将密钥存储在钥匙串中。或者将其存储在 Secure Enclave 中。

这是我陷入最佳实践的地方。

实施思路

    首次加载时将密钥保存在钥匙串中,然后删除密钥。 (这似乎不安全,并且没有将对称密钥与设备密码结合起来。) 将密码传递给命令行应用程序,使用它来“加扰”对称密钥,然后将对称密钥保存到钥匙串并在用户输入应用程序密码时“解扰”。 (这似乎有道理,但我不确定如何在 Swift 中使用字符串“加扰”对称密钥。) 首次加载时,使用密码和对称密钥以某种方式在 Secure Enclave 中创建密钥对。在未来打开应用程序时,密码提供对称密钥。这个问题似乎是 Secure Enclave 从不要求存储密钥的一部分,它只是提供了一个密钥对来使用,但在我的情况下,一半的密钥对已经存在。

如果您的回答是“去学习加密操作”,我理解。老实说,我已经阅读了几篇文章:Generating New Cryptographic Keys、CryptoKit and Secure Enclave、Common CryptoKit operation,但我看不到涉及密码的用例。对于正确方向的观点,我将不胜感激。

这里的关键是我想要在加密时选择的密码与加密密钥进行交互。如果该加密密钥可以存储在 Secure Enclave 中,那将是锦上添花。

【问题讨论】:

【参考方案1】:

这未经测试,我不能确定这是最佳做法。

我的解决方案是使用密钥派生函数 (KDF) more info。根据我的学习,这需要两个输入密码和一个盐,并产生一个密钥。然后我可以保密的密码和盐可以伴随数据。一个重要方面似乎是为每项数据使用不同的盐。

这个代码示例帮助我理解了:

https://gist.github.com/hfossli/7165dc023a10046e2322b0ce74c596f8

具体来说:

let password = "foo"
let salt = randomSalt()

let key = try createKey(password: password.data(using: .utf8)!, salt: salt)


func randomSalt() -> Data 
    return randomData(length: 8)


func createKey(password: Data, salt: Data) throws -> Data 
    let length = kCCKeySizeAES256
    var status = Int32(0)
    var derivedBytes = [UInt8](repeating: 0, count: length)
    password.withUnsafeBytes  (passwordBytes: UnsafePointer<Int8>!) in
        salt.withUnsafeBytes  (saltBytes: UnsafePointer<UInt8>!) in
            status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),                  // algorithm
                                          passwordBytes,                                // password
                                          password.count,                               // passwordLen
                                          saltBytes,                                    // salt
                                          salt.count,                                   // saltLen
                                          CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),   // prf
                                          10000,                                        // rounds
                                          &derivedBytes,                                // derivedKey
                                          length)                                       // derivedKeyLen
        
    
    guard status == 0 else 
        throw Error.keyGeneration(status: Int(status))
    
    return Data(bytes: UnsafePointer<UInt8>(derivedBytes), count: length)

然后您就拥有了用于对称加密的密钥。并且您可以从应用程序中的密码和盐中获取它。

这可能更像是 crypto.stackexchange.com 的一个问题,但我试图用正确的方向来回答它。

这未经测试,我不能确定这是最佳做法。

【讨论】:

以上是关于带有应用密码的 Swift 对称加密的主要内容,如果未能解决你的问题,请参考以下文章

密码技术--非对称加密算法及Go语言应用

入门密码学④非对称加密

非对称加密和对称加密

对称加密,非对称加密,公钥和私钥

k8s系列-03-认证的密码学原理之对称加密和非对称加密

密码技术--对称加密算法及Go语言应用