'AES encryption in swift

I'm trying to implement AES encryption in swift. The encryption decryption for Android and C# is working properly. I need to implement it in swift. It's current code for android and C# is followed by this.

I tried to use

  1. CryptoSwift
  2. Cross platform AES encryption

But none of it work. When I send the encrypted string on server it's not been decrypted.

Any help will be appreciated



Solution 1:[1]

Swift 5

I refactored @ingconti 's code.

import Foundation
import CommonCrypto

struct AES {

    // MARK: - Value
    // MARK: Private
    private let key: Data
    private let iv: Data


    // MARK: - Initialzier
    init?(key: String, iv: String) {
        guard key.count == kCCKeySizeAES128 || key.count == kCCKeySizeAES256, let keyData = key.data(using: .utf8) else {
            debugPrint("Error: Failed to set a key.")
            return nil
        }
    
        guard iv.count == kCCBlockSizeAES128, let ivData = iv.data(using: .utf8) else {
            debugPrint("Error: Failed to set an initial vector.")
            return nil
        }
    
    
        self.key = keyData
        self.iv  = ivData
    }


    // MARK: - Function
    // MARK: Public
    func encrypt(string: String) -> Data? {
        return crypt(data: string.data(using: .utf8), option: CCOperation(kCCEncrypt))
    }

    func decrypt(data: Data?) -> String? {
        guard let decryptedData = crypt(data: data, option: CCOperation(kCCDecrypt)) else { return nil }
        return String(bytes: decryptedData, encoding: .utf8)
    }

    func crypt(data: Data?, option: CCOperation) -> Data? {
        guard let data = data else { return nil }
    
        let cryptLength = data.count + key.count
        var cryptData   = Data(count: cryptLength)
    
        var bytesLength = Int(0)
    
        let status = cryptData.withUnsafeMutableBytes { cryptBytes in
            data.withUnsafeBytes { dataBytes in
                iv.withUnsafeBytes { ivBytes in
                    key.withUnsafeBytes { keyBytes in
                    CCCrypt(option, CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), keyBytes.baseAddress, key.count, ivBytes.baseAddress, dataBytes.baseAddress, data.count, cryptBytes.baseAddress, cryptLength, &bytesLength)
                    }
                }
            }
        }
    
        guard Int32(status) == Int32(kCCSuccess) else {
            debugPrint("Error: Failed to crypt data. Status \(status)")
            return nil
        }
    
        cryptData.removeSubrange(bytesLength..<cryptData.count)
        return cryptData
    }
}

Use like this

let password = "UserPassword1!"
let key128   = "1234567890123456"                   // 16 bytes for AES128
let key256   = "12345678901234561234567890123456"   // 32 bytes for AES256
let iv       = "abcdefghijklmnop"                   // 16 bytes for AES128

let aes128 = AES(key: key128, iv: iv)
let aes256 = AES(key: key256, iv: iv)

let encryptedPassword128 = aes128?.encrypt(string: password)
aes128?.decrypt(data: encryptedPassword128)

let encryptedPassword256 = aes256?.encrypt(string: password)
aes256?.decrypt(data: encryptedPassword256)

Results

enter image description here

Solution 2:[2]

Based on @zaph great answer, I create this Playground for:

Swift 5

import Foundation
import CommonCrypto

protocol Cryptable {
    func encrypt(_ string: String) throws -> Data
    func decrypt(_ data: Data) throws -> String
}

struct AES {
    private let key: Data
    private let ivSize: Int         = kCCBlockSizeAES128
    private let options: CCOptions  = CCOptions(kCCOptionPKCS7Padding)

    init(keyString: String) throws {
        guard keyString.count == kCCKeySizeAES256 else {
            throw Error.invalidKeySize
        }
        self.key = Data(keyString.utf8)
    }
}

extension AES {
    enum Error: Swift.Error {
        case invalidKeySize
        case generateRandomIVFailed
        case encryptionFailed
        case decryptionFailed
        case dataToStringFailed
    }
}

private extension AES {

    func generateRandomIV(for data: inout Data) throws {

        try data.withUnsafeMutableBytes { dataBytes in

            guard let dataBytesBaseAddress = dataBytes.baseAddress else {
                throw Error.generateRandomIVFailed
            }

            let status: Int32 = SecRandomCopyBytes(
                kSecRandomDefault,
                kCCBlockSizeAES128,
                dataBytesBaseAddress
            )

            guard status == 0 else {
                throw Error.generateRandomIVFailed
            }
        }
    }
}

extension AES: Cryptable {

    func encrypt(_ string: String) throws -> Data {
        let dataToEncrypt = Data(string.utf8)

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

        var numberBytesEncrypted: Int = 0

        do {
            try key.withUnsafeBytes { keyBytes in
                try dataToEncrypt.withUnsafeBytes { dataToEncryptBytes in
                    try buffer.withUnsafeMutableBytes { bufferBytes in

                        guard let keyBytesBaseAddress = keyBytes.baseAddress,
                            let dataToEncryptBytesBaseAddress = dataToEncryptBytes.baseAddress,
                            let bufferBytesBaseAddress = bufferBytes.baseAddress else {
                                throw Error.encryptionFailed
                        }

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

                        guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
                            throw Error.encryptionFailed
                        }
                    }
                }
            }

        } catch {
            throw Error.encryptionFailed
        }

        let encryptedData: Data = buffer[..<(numberBytesEncrypted + ivSize)]
        return encryptedData
    }

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

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

        var numberBytesDecrypted: Int = 0

        do {
            try key.withUnsafeBytes { keyBytes in
                try data.withUnsafeBytes { dataToDecryptBytes in
                    try buffer.withUnsafeMutableBytes { bufferBytes in

                        guard let keyBytesBaseAddress = keyBytes.baseAddress,
                            let dataToDecryptBytesBaseAddress = dataToDecryptBytes.baseAddress,
                            let bufferBytesBaseAddress = bufferBytes.baseAddress else {
                                throw Error.encryptionFailed
                        }

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

                        guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
                            throw Error.decryptionFailed
                        }
                    }
                }
            }
        } catch {
            throw Error.encryptionFailed
        }

        let decryptedData: Data = buffer[..<numberBytesDecrypted]

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

        return decryptedString
    }
}

do {
    let aes = try AES(keyString: "FiugQTgPNwCWUY,VhfmM4cKXTLVFvHFe")

    let stringToEncrypt: String = "please encrypt meeee"
    print("String to encrypt:\t\t\t\(stringToEncrypt)")

    let encryptedData: Data = try aes.encrypt(stringToEncrypt)
    print("String encrypted (base64):\t\(encryptedData.base64EncodedString())")

    let decryptedData: String = try aes.decrypt(encryptedData)
    print("String decrypted:\t\t\t\(decryptedData)")

} catch {
    print("Something went wrong: \(error)")
}

Output:

output

I also created a Swift Package based on it:

https://github.com/backslash-f/aescryptable ??

Solution 3:[3]

my two cents:

swift 4 / xcode 9 extension for Data:

extension Data{

    func aesEncrypt( keyData: Data, ivData: Data, operation: Int) -> Data {
        let dataLength = self.count
        let cryptLength  = size_t(dataLength + kCCBlockSizeAES128)
        var cryptData = Data(count:cryptLength)

        let keyLength = size_t(kCCKeySizeAES128)
        let options = CCOptions(kCCOptionPKCS7Padding)


        var numBytesEncrypted :size_t = 0

        let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
            self.withUnsafeBytes {dataBytes in
                ivData.withUnsafeBytes {ivBytes in
                    keyData.withUnsafeBytes {keyBytes in
                        CCCrypt(CCOperation(operation),
                                CCAlgorithm(kCCAlgorithmAES),
                                options,
                                keyBytes, keyLength,
                                ivBytes,
                                dataBytes, dataLength,
                                cryptBytes, cryptLength,
                                &numBytesEncrypted)
                    }
                }
            }
        }

        if UInt32(cryptStatus) == UInt32(kCCSuccess) {
            cryptData.removeSubrange(numBytesEncrypted..<cryptData.count)

        } else {
            print("Error: \(cryptStatus)")
        }

        return cryptData;
    }

}




    func testAES() -> Bool {

        let message     = "secret message"
        let key         = "key890123456"
        let ivString     = "abcdefghijklmnop"   // 16 bytes for AES128

        let messageData = message.data(using:String.Encoding.utf8)!
        let keyData     = key.data(using: .utf8)!
        let ivData      = ivString.data(using: .utf8)!

        let encryptedData = messageData.aesEncrypt( keyData:keyData, ivData:ivData, operation:kCCEncrypt)
        let decryptedData = encryptedData.aesEncrypt( keyData:keyData, ivData:ivData, operation:kCCDecrypt)
        let decrypted     = String(bytes:decryptedData, encoding:String.Encoding.utf8)!

        return message == decrypted

    }

Solution 4:[4]

I have used CryptoSwift.

First I have install cryptoSwift in the pod file. Then in my view controller I have import CryptoSwift.

Here is the code that I have used:

let value = "xyzzy".  // This is the value that we want to encrypt
let key = "abc".      // This is the key 

let EncryptedValue = try! value.aesEncrypt(key: key)
let DecryptedValue = try! EncryptedValue.aesDecrypt(key: key)

Then, using String extension:

extension String {

    func aesEncrypt(key: String) throws -> String {

        var result = ""

        do {

            let key: [UInt8] = Array(key.utf8) as [UInt8]
            let aes = try! AES(key: key, blockMode: .ECB, padding: .pkcs5) // AES128 .ECB pkcs7
            let encrypted = try aes.encrypt(Array(self.utf8))

            result = encrypted.toBase64()!

            print("AES Encryption Result: \(result)")

        } catch {

            print("Error: \(error)")
        }

        return result
    }

    func aesDecrypt(key: String) throws -> String {

        var result = ""

        do {

            let encrypted = self
            let key: [UInt8] = Array(key.utf8) as [UInt8]
            let aes = try! AES(key: key, blockMode: .ECB, padding: .pkcs5) // AES128 .ECB pkcs7
            let decrypted = try aes.decrypt(Array(base64: encrypted))

            result = String(data: Data(decrypted), encoding: .utf8) ?? ""

            print("AES Decryption Result: \(result)")

        } catch {

            print("Error: \(error)")
        }

        return result
    }
}

In this I have not used iv and encrypted.toBase64() to encrypt like result = encrypted.toBase64()! in place of result = encrypted.toStringHex()! in encryption

and similar in decryption let decrypted = try aes.decrypt(Array(base64: encrypted)) in place of let decrypted = try aes.decrypt(Array(Hex: encrypted))

Solution 5:[5]

This is easy to use the extension and also supported all platforms for decryption as well.

import CryptoSwift

extension String {
    func aesEncrypt() throws -> String {
        do {
            let encrypted = try AES(key: "your_encryption_key", iv: "your_iv", padding: .pkcs7).encrypt([UInt8](self.data(using: .utf8)!))
            return Data(encrypted).base64EncodedString()
        } catch {
            
        }
        return ""
    }
    
    func aesDecrypt() throws -> String {
        do {
            guard let data = Data(base64Encoded: self) else { return "" }
            let decrypted = try AES(key: "your_encryption_key", iv: "your_iv", padding: .pkcs7).decrypt([UInt8](data))
            return decrypted
        } catch {
            
        }
        return ""
    }
}

Solution 6:[6]

Found a nice library named RNCryptor implemented in swift language for AES encryption/ decryption.

Installation can be done with Cocoapods or Carthage. Here is the sample code for encryption and decryption.

// Encryption
let data = "sample data string".data(using: String.Encoding.utf8)
let password = "Secret password"
let encryptedData = RNCryptor.encrypt(data: data, withPassword: password)

// Decryption
do {
    let originalData = try RNCryptor.decrypt(data: encryptedData, withPassword: password)
    // ...
} catch {
    print(error)
}

Solution 7:[7]

For anyone who cannot transform array of bytes to a String

String(data: Data(decrypted), encoding: .utf8)

This is my example string extension

extension String {

    func decryptAES(key: String, iv: String) -> String {
        do {
            let encrypted = self
            let key = Array(key.utf8)
            let iv = Array(iv.utf8)
            let aes = try AES(key: key, blockMode: CTR(iv: iv), padding: .noPadding)
            let decrypted = try aes.decrypt(Array(hex: encrypted))
            return String(data: Data(decrypted), encoding: .utf8) ?? ""
        } catch {
            return "Error: \(error)"
        }
    }
}

Solution 8:[8]

I was looking for AES encryption ECB Mode with PKC5 padding without using any pod. I found a proper way to solve my problem by gathering different information. Maybe it could be helpful for others.

Note: There is no difference between PKCS5 and PKCS7 padding.

import CommonCrypto
 
func encryptionAESModeECB(messageData data: Data, key: String) -> Data? {
    guard let keyData = key.data(using: String.Encoding.utf8) else { return nil }
    guard let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) else { return nil }
    
    let keyLength               = size_t(kCCKeySizeAES128)
    let operation:  CCOperation = UInt32(kCCEncrypt)
    let algoritm:   CCAlgorithm = UInt32(kCCAlgorithmAES)
    let options:    CCOptions   = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
    let iv:         String      = ""
    
    var numBytesEncrypted: size_t = 0
    
    let cryptStatus = CCCrypt(operation,
                              algoritm,
                              options,
                              (keyData as NSData).bytes, keyLength,
                              iv,
                              (data as NSData).bytes, data.count,
                              cryptData.mutableBytes, cryptData.length,
                              &numBytesEncrypted)
    
    if UInt32(cryptStatus) == UInt32(kCCSuccess) {
        cryptData.length = Int(numBytesEncrypted)
        let encryptedString = cryptData.base64EncodedString(options: .lineLength64Characters)
        return encryptedString.data(using: .utf8)
    } else {
        return nil
    }
}

func decryptionAESModeECB(messageData: Data, key: String) -> Data? {
    guard let messageString = String(data: messageData, encoding: .utf8) else { return nil }
    guard let data = Data(base64Encoded: messageString, options: .ignoreUnknownCharacters) else { return nil }
    guard let keyData = key.data(using: String.Encoding.utf8) else { return nil }
    guard let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) else { return nil }
    
    let keyLength               = size_t(kCCKeySizeAES128)
    let operation:  CCOperation = UInt32(kCCDecrypt)
    let algoritm:   CCAlgorithm = UInt32(kCCAlgorithmAES)
    let options:    CCOptions   = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
    let iv:         String      = ""
    
    var numBytesEncrypted: size_t = 0
    
    let cryptStatus = CCCrypt(operation,
                              algoritm,
                              options,
                              (keyData as NSData).bytes, keyLength,
                              iv,
                              (data as NSData).bytes, data.count,
                              cryptData.mutableBytes, cryptData.length,
                              &numBytesEncrypted)
    
    if UInt32(cryptStatus) == UInt32(kCCSuccess) {
        cryptData.length = Int(numBytesEncrypted)
        return cryptData as Data
    } else {
        return nil
    }
}

Use it like this:

let encryptedData = encryptionAESModeECB(messageData: data, key: "keyString")

let decryptedData = decryptionAESModeECB(messageData: data, key: "keyString")

Solution 9:[9]

I know this is an old question.

Since 2019 You can use CryptoKit from Apple. It introduced AES.GCM

import CryptoKit

let key = SymmetricKey(size: .bits256)
let data = Data(...)
do {
    let enc = try AES.GCM.seal(data, using: key).combined!
    let dec = try AES.GCM.open(try AES.GCM.SealedBox(combined: data), using: key))
} catch {...}

Also I've made a useful swift package with extension to CryptoKit to allow AES.CBC encryption (https://github.com/gal-yedidovich/CryptoExtensions)

Then, just import CBC

import CryptoKit
import CBC

let data: Data = ... //some data to encrypt
let iv: Data = ... //an initial vector
let key: SymmetricKey = ... //encryption key

//one shot crypto operation
do {
    let encrypted = try AES.CBC.encrypt(data, using: key, iv: iv)
    let decrypted = try AES.CBC.decrypt(encrypted, using: key, iv: iv)
} catch {...}

//using cipher
do {
    let cipher = try AES.CBC.Cipher(.encrypt, using: key, iv: iv)
    var enc = Data()
    enc += try cipher.update(...)
    enc += try cipher.update(...)
    enc += try cipher.finalize()
} catch {...}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 ingconti
Solution 4 aguilarpgc
Solution 5 Avadh Bambhroliya
Solution 6 Alex Andrews
Solution 7 Khemmachart Chutapetch
Solution 8
Solution 9