【问题标题】:Swift 5 + kCCDecrypt (CommonCrypto): Failing to decryptSwift 5 + kCCDecrypt (CommonCrypto):解密失败
【发布时间】:2019-08-24 08:19:26
【问题描述】:

基于大量其他类似问题,尝试在Swift 5 中编写我自己的加密/解密函数——结果惨遭失败。

我正在使用CommonCrypto + CCCrypt 来加密/解密(AES,256 密钥,随机 iv)。

我更喜欢NSData.bytes 而不是withUnsafeBytes(这只是Swift 5 中的too confusing)。

我的encrypt 函数是这样的:

func encrypt(_ string: String) throws -> Data {
    guard let dataToEncrypt: Data = string.data(using: .utf8) else {
        throw AESError.stringToDataFailed
    }

    // Seems like the easiest way to avoid the `withUnsafeBytes` mess is to use NSData.bytes.
    let dataToEncryptNSData = NSData(data: dataToEncrypt)

    let bufferSize: Int = ivSize + dataToEncryptNSData.length + kCCBlockSizeAES128
    let buffer = UnsafeMutablePointer<NSData>.allocate(capacity: bufferSize)
    defer { buffer.deallocate() }

    let status: Int32 = SecRandomCopyBytes(
        kSecRandomDefault,
        kCCBlockSizeAES128,
        buffer
    )
    guard status == 0 else {
        throw AESError.generateRandomIVFailed
    }

    var numberBytesEncrypted: Int = 0

    let cryptStatus: CCCryptorStatus = CCCrypt( // Stateless, one-shot encrypt operation
        CCOperation(kCCEncrypt),                // op: CCOperation
        CCAlgorithm(kCCAlgorithmAES),           // alg: CCAlgorithm
        options,                                // options: CCOptions
        key.bytes,                              // key: the "password"
        key.length,                             // keyLength: the "password" size
        buffer,                                 // iv: Initialization Vector
        dataToEncryptNSData.bytes,              // dataIn: Data to encrypt bytes
        dataToEncryptNSData.length,             // dataInLength: Data to encrypt size
        buffer + kCCBlockSizeAES128,            // dataOut: encrypted Data buffer
        bufferSize,                             // dataOutAvailable: encrypted Data buffer size
        &numberBytesEncrypted                   // dataOutMoved: the number of bytes written
    )

    guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
        throw AESError.encryptDataFailed
    }

    return Data(bytes: buffer, count: numberBytesEncrypted + ivSize)
}

decrypt 函数:

func decrypt(_ data: Data) throws -> String {

    // Seems like the easiest way to avoid the `withUnsafeBytes` mess is to use NSData.bytes.
    let dataToDecryptNSData = NSData(data: data)

    let bufferSize: Int = dataToDecryptNSData.length - ivSize
    let buffer = UnsafeMutablePointer<NSData>.allocate(capacity: bufferSize)
    defer { buffer.deallocate() }

    var numberBytesDecrypted: Int = 0

    let cryptStatus: CCCryptorStatus = CCCrypt(         // Stateless, one-shot encrypt operation
        CCOperation(kCCDecrypt),                        // op: CCOperation
        CCAlgorithm(kCCAlgorithmAES128),                // alg: CCAlgorithm
        options,                                        // options: CCOptions
        key.bytes,                                      // key: the "password"
        key.length,                                     // keyLength: the "password" size
        dataToDecryptNSData.bytes,                      // iv: Initialization Vector
        dataToDecryptNSData.bytes + kCCBlockSizeAES128, // dataIn: Data to decrypt bytes
        bufferSize,                                     // dataInLength: Data to decrypt size
        buffer,                                         // dataOut: decrypted Data buffer
        bufferSize,                                     // dataOutAvailable: decrypted Data buffer size
        &numberBytesDecrypted                           // dataOutMoved: the number of bytes written
    )

    guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
        throw AESError.decryptDataFailed
    }

    let decryptedData = Data(bytes: buffer, count: numberBytesDecrypted)

    guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
        throw AESError.dataToStringFailed
    }

    return decryptedString
}

这些基于来自用户“@zaph”的this awesome answer

虽然encrypt 似乎可以工作,但decrypt 却失败了。

这一行具体:

guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
    throw AESError.dataToStringFailed
}

所以我当然错过了一些东西,但我无法弄清楚它是什么。可以吗?

这里是包含整个代码的 pastebin,您可以将其复制/粘贴到 Playground 中并点击播放。 Swift 5 为必填项:https://pastebin.com/raw/h6gacaHX

更新
我现在遵循@OOper 建议的方法。最终代码可以看这里:
https://github.com/backslash-f/aescryptable

【问题讨论】:

  • NSData.bytes 可能无法满足您的期望。如果你想编写在 Swift 中稳定运行的代码,千万不要使用它。

标签: aes commoncrypto swift5


【解决方案1】:

事实上,在 Swift 5 中使用 Data.withUnsafeBytes 有点混乱,尽管 NSData.bytes 并不是最简单的方法,因为使用它有时看起来可行,但有时却不行。 p>

如果您想使用Data 在 Swift 中编写始终有效的代码,您需要习惯使用 Data.withUnsafeBytes

struct AES {
    private let key: Data //<- Use `Data` instead of `NSData`

    private let ivSize: Int                     = kCCBlockSizeAES128
    private let options: CCOptions              = CCOptions(kCCOptionPKCS7Padding)

    init(keyString: String) throws {
        guard keyString.count == kCCKeySizeAES256 else {
            throw AESError.invalidKeySize
        }
        guard let keyData: Data = keyString.data(using: .utf8) else {
            throw AESError.stringToDataFailed
        }
        self.key = keyData
    }
}

extension AES: Cryptable {

    func encrypt(_ string: String) throws -> Data {
        guard let dataToEncrypt: Data = string.data(using: .utf8) else {
            throw AESError.stringToDataFailed
        }

        let bufferSize: Int = ivSize + dataToEncrypt.count + kCCBlockSizeAES128
        var buffer = Data(count: bufferSize)

        let status: Int32 = buffer.withUnsafeMutableBytes {bytes in
            SecRandomCopyBytes(
                kSecRandomDefault,
                kCCBlockSizeAES128,
                bytes.baseAddress!
            )
        }
        guard status == 0 else {
            throw AESError.generateRandomIVFailed
        }

        var numberBytesEncrypted: Int = 0

        let cryptStatus: CCCryptorStatus = key.withUnsafeBytes {keyBytes in
            dataToEncrypt.withUnsafeBytes {dataBytes in
                buffer.withUnsafeMutableBytes {bufferBytes in
                    CCCrypt( // Stateless, one-shot encrypt operation
                        CCOperation(kCCEncrypt),                // op: CCOperation
                        CCAlgorithm(kCCAlgorithmAES),           // alg: CCAlgorithm
                        options,                                // options: CCOptions
                        keyBytes.baseAddress,                   // key: the "password"
                        key.count,                              // keyLength: the "password" size
                        bufferBytes.baseAddress,                // iv: Initialization Vector
                        dataBytes.baseAddress,                  // dataIn: Data to encrypt bytes
                        dataToEncrypt.count,                    // dataInLength: Data to encrypt size
                        bufferBytes.baseAddress! + kCCBlockSizeAES128, // dataOut: encrypted Data buffer
                        bufferSize,                             // dataOutAvailable: encrypted Data buffer size
                        &numberBytesEncrypted                   // dataOutMoved: the number of bytes written
                    )
                }
            }
        }

        guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
            throw AESError.encryptDataFailed
        }

        return buffer[..<(numberBytesEncrypted + ivSize)]
    }

    func decrypt(_ data: Data) throws -> String {

        let bufferSize: Int = data.count - ivSize
        var buffer = Data(count: bufferSize)

        var numberBytesDecrypted: Int = 0

        let cryptStatus: CCCryptorStatus = key.withUnsafeBytes {keyBytes in
            data.withUnsafeBytes {dataBytes in
                buffer.withUnsafeMutableBytes {bufferBytes in
                    CCCrypt(         // Stateless, one-shot encrypt operation
                        CCOperation(kCCDecrypt),                        // op: CCOperation
                        CCAlgorithm(kCCAlgorithmAES128),                // alg: CCAlgorithm
                        options,                                        // options: CCOptions
                        keyBytes.baseAddress,                           // key: the "password"
                        key.count,                                      // keyLength: the "password" size
                        dataBytes.baseAddress,                          // iv: Initialization Vector
                        dataBytes.baseAddress! + kCCBlockSizeAES128,    // dataIn: Data to decrypt bytes
                        bufferSize,                                     // dataInLength: Data to decrypt size
                        bufferBytes.baseAddress,                        // dataOut: decrypted Data buffer
                        bufferSize,                                     // dataOutAvailable: decrypted Data buffer size
                        &numberBytesDecrypted                           // dataOutMoved: the number of bytes written
                    )
                }
            }
        }

        guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
            throw AESError.decryptDataFailed
        }

        let decryptedData = buffer[..<numberBytesDecrypted]

        guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
            throw AESError.dataToStringFailed
        }

        return decryptedString
    }

}

【讨论】:

  • 天哪……我试图避免这种情况并处理所有这些强制展开的东西。但也许我过度设计/过度复杂化它。您能否详细了解为什么 NSData.bytes 工作不规律?
  • @backslash-f,例如NSData.bytes的文档说对于不可变的数据对象,返回的指针在数据对象被释放之前是有效的。在优化的上下文中,NSData 的生命周期是难以预料的。这可能会在最后一次使用后被释放。但是在您的情况下,您的代码中至关重要的是这一行:let buffer = UnsafeMutablePointer&lt;NSData&gt;.allocate(capacity: bufferSize),它分配了一个堆,该堆可以容纳NSData 的多个数字(=bufferSize)。
  • 我明白了。我错过了文档中的那一行。 :/好吧,我接受你的回答。感谢您的宝贵时间,我很感激。
  • 试过上面的代码得到了 'dataToStringFailed' 错误 for guard let decryptedString = String(data: decryptedData, encoding: .utf8) else { throw AESError.dataToStringFailed }
  • @Vasu,我无法重现同样的问题。你最好用足够的信息来开始你自己的线程来重现这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-30
  • 1970-01-01
  • 2018-02-23
  • 1970-01-01
  • 1970-01-01
  • 2018-11-28
  • 2015-03-29
相关资源
最近更新 更多