Пытаюсь написать свои собственные функции шифрования/дешифрования в 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





На самом деле, использование 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, например, документ NSData.bytes говорит Для неизменяемого объекта данных возвращаемый указатель действителен до тех пор, пока объект данных не будет освобожден. И в оптимизированном контексте трудно ожидать продолжительности жизни NSData. Который может быть освобожден сразу после последнего использования. Но в вашем случае критической в вашем коде является эта строка: let buffer = UnsafeMutablePointer<NSData>.allocate(capacity: bufferSize), которая выделяет кучу, которая может содержать несколько чисел (=bufferSize) из NSData.
Понимаю. Я пропустил эту строку из документов. :/ Хорошо, я принимаю твой ответ. Спасибо за ваше время, я ценю это.
Приведенный выше код получил ошибку «dataToStringFailed» для защиты, позволяющую decryptedString = String (данные: decryptedData, encoding: .utf8) else { throw AESError.dataToStringFailed}
@ Васу, я не могу воспроизвести ту же проблему. Вам лучше начать свою собственную тему с достаточным количеством информации, чтобы воспроизвести проблему.
NSData.bytesможет не дать вам того, что вы ожидаете. Если вы хотите написать код, который стабильно работает в Swift, никогда не используйте его.