Swift 5 + kCCDecrypt (CommonCrypto): не удалось расшифровать

Пытаюсь написать свои собственные функции шифрования/дешифрования в Swift 5, основываясь на множестве других подобных вопросов, и с треском провалился.

Я использую CommonCrypto + CCCrypt для шифрования/дешифрования (AES, ключ 256, случайный iv).

Я предпочитаю NSData.byteswithUnsafeBytes (это просто слишком запутанный в Swift 5).

Моя функция 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».

Хотя encrypt вроде работает, decrypt не работает.

В частности, эта строка:

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

Так что, конечно, я что-то упускаю, но я не могу понять, что это такое. Можешь ли ты?

Вот pastebin со всем кодом, который вы можете скопировать/вставить в игровую площадку и нажать кнопку воспроизведения. Swift 5 требуется: https://pastebin.com/raw/h6gacaHX

Update
I'm now following @OOper's suggested approach. The final code can be seen here:
https://github.com/backslash-f/aescryptable

NSData.bytes может не дать вам того, что вы ожидаете. Если вы хотите написать код, который стабильно работает в Swift, никогда не используйте его.
OOPer 03.04.2019 02:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
7 938
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На самом деле, использование Data.withUnsafeBytes в Swift 5 — это своего рода беспорядок, хотя NSData.bytes не может быть самый простой способ, так как иногда его использование будет работать, а иногда — нет.

Вам нужно привыкнуть работать с Data.withUnsafeBytes, если вы хотите писать всегда работающий код на Swift с Data.

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 03.04.2019 09:54

@backslash-f, например, документ NSData.bytes говорит Для неизменяемого объекта данных возвращаемый указатель действителен до тех пор, пока объект данных не будет освобожден. И в оптимизированном контексте трудно ожидать продолжительности жизни NSData. Который может быть освобожден сразу после последнего использования. Но в вашем случае критической в ​​вашем коде является эта строка: let buffer = UnsafeMutablePointer<NSData>.allocate(capacity: bufferSize), которая выделяет кучу, которая может содержать несколько чисел (=bufferSize) из NSData.

OOPer 03.04.2019 10:15

Понимаю. Я пропустил эту строку из документов. :/ Хорошо, я принимаю твой ответ. Спасибо за ваше время, я ценю это.

backslash-f 03.04.2019 10:23

Приведенный выше код получил ошибку «dataToStringFailed» для защиты, позволяющую decryptedString = String (данные: decryptedData, encoding: .utf8) else { throw AESError.dataToStringFailed}

Vasu 11.11.2019 09:32

@ Васу, я не могу воспроизвести ту же проблему. Вам лучше начать свою собственную тему с достаточным количеством информации, чтобы воспроизвести проблему.

OOPer 11.11.2019 13:33

Другие вопросы по теме