Trying to write my own encrypt/decrypt functions in Swift 5
, based on tons of other similar questions -- and failing miserably.
I'm using CommonCrypto
+ CCCrypt
to encrypt/decrypt (AES, 256 key, random iv).
I'm favouring NSData.bytes
over withUnsafeBytes
(which is just too confusing in Swift 5
).
My encrypt
function goes like this:
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)
}
The decrypt
function:
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
}
Those were based on this awesome answer from the user "@zaph".
Although encrypt
seems to be working, decrypt
fails.
This line specifically:
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
throw AESError.dataToStringFailed
}
So of course I'm missing something, but I can't figure out what it is. Could you?
Here is the pastebin with the entire code, which you could copy/paste in a Playground and hit play. Swift 5
is required: 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
In fact, using Data.withUnsafeBytes
is sort of a mess in Swift 5, though NSData.bytes
cannot be the easiest way, as using it would sometimes seemingly work, and sometimes not.
You need to be accustomed work with Data.withUnsafeBytes
if you want to write an always-works code in Swift with 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
}
}