43

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

Community
  • 1
  • 1
Ankita Shah
  • 2,178
  • 3
  • 25
  • 42
  • Just compare output of working implementation. Standard test vectors for AES in various modes of operation are available. –  Jun 07 '16 at 13:11
  • @TruthSerum I tried to compare the encrypted value but it's not matching. I don't have access to android code, so I can't debug it too. I just got this link as a reference which I had already added in question – Ankita Shah Jun 07 '16 at 13:13
  • Then look at the input parameters. You will have a 16byte plaintext block, a 16-20 bytes key (depending on AES-128, AES-256 .etc variant) and an IV initialization vector. All three need to match, for every block. You also need to make sure the padding protocol is the same between versions. –  Jun 07 '16 at 13:15
  • 1
    I'm getting only `kCCOptionPKCS7Padding`. How can I set it to `PKCS5` padding with CBC mode? Checked but didn't found any solution for it too – Ankita Shah Jun 07 '16 at 13:18
  • 1
    You need to use the same padding mode to decrypt and encrypt. If your API does not support it, you will have to implement it yourself. This might involve unpadding the wrong format, then repadding to the correct format. Again, you will find test vectors to verify that it's working correctly at every stage. –  Jun 07 '16 at 13:22
  • It is best to avoid using CryptoSwift, amoung other things it is 500 to 1000 times slower than Common Crypto based implementations. Apple's Common Crypto is FIPS certified and as such has been well vetted, using CryptoSwift is taking a chance on correctness and security. Additionally a US Encryption Registration (ERN) must be optained. – zaph Jun 07 '16 at 13:24

11 Answers11

57

Be sure to use the same parameters which seem to be AES with CBC mode with iv, PKCS5Padding (actually PKCS#7) padding and a 16-byte (128-bit) key.

PKCS#5 padding and PKCS#7 padding are essentially the same, sometimes for historic reasons PKCS#5 padding is specified for use with AES but the actual padding is PKCS#7.

Make sure the encodings of the key, iv and encrypted data all match. Hex dump them on both platforms to ensure they are identical. Encryption functions are not difficult to use, if all the input parameters are correct the output will be correct.

To make this more secure the iv should be random bytes and prepended to the encrypted data for use during decryption.

The Cross platform AES encryption uses a 256-bit key so will not work as-is.

Example:

Swift 2

// operation: kCCEncrypt or kCCDecrypt
func testCrypt(data data:[UInt8], keyData:[UInt8], ivData:[UInt8], operation:Int) -> [UInt8]? {
    let cryptLength  = size_t(data.count+kCCBlockSizeAES128)
    var cryptData    = [UInt8](count:cryptLength, repeatedValue:0)

    let keyLength             = size_t(kCCKeySizeAES128)
    let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
    let options:  CCOptions   = UInt32(kCCOptionPKCS7Padding)

    var numBytesEncrypted :size_t = 0

    let cryptStatus = CCCrypt(CCOperation(operation),
                              algoritm,
                              options,
                              keyData, keyLength,
                              ivData,
                              data, data.count,
                              &cryptData, cryptLength,
                              &numBytesEncrypted)

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

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

    return cryptData;
}

let message       = "Don´t try to read this text. Top Secret Stuff"
let messageData   = Array(message.utf8)
let keyData       = Array("12345678901234567890123456789012".utf8)
let ivData        = Array("abcdefghijklmnop".utf8)
let encryptedData = testCrypt(data:messageData,   keyData:keyData, ivData:ivData, operation:kCCEncrypt)!
let decryptedData = testCrypt(data:encryptedData, keyData:keyData, ivData:ivData, operation:kCCDecrypt)!
var decrypted     = String(bytes:decryptedData, encoding:NSUTF8StringEncoding)!

print("message:       \(message)");
print("messageData:   \(NSData(bytes:messageData,   length:messageData.count))");
print("keyData:       \(NSData(bytes:keyData,       length:keyData.count))");
print("ivData:        \(NSData(bytes:ivData,        length:ivData.count))");
print("encryptedData: \(NSData(bytes:encryptedData, length:encryptedData.count))");
print("decryptedData: \(NSData(bytes:decryptedData, length:decryptedData.count))");
print("decrypted:     \(String(bytes:decryptedData,encoding:NSUTF8StringEncoding)!)");

Output:

message:       Don´t try to read this text. Top Secret Stuff  
messageData:   446f6ec2 b4742074 72792074 6f207265 61642074 68697320 74657874 2e20546f 70205365 63726574 20537475 6666  
keyData:       31323334 35363738 39303132 33343536 37383930 31323334 35363738 39303132  
ivData:        61626364 65666768 696a6b6c 6d6e6f70  
encryptedData: b1b6dc17 62eaf3f8 baa1cb87 21ddc35c dee803ed fb320020 85794848 21206943 a85feb5b c8ee58fc d6fb664b 96b81114  
decryptedData: 446f6ec2 b4742074 72792074 6f207265 61642074 68697320 74657874 2e20546f 70205365 63726574 20537475 6666  
decrypted:     Don´t try to read this text. Top Secret Stuff  

Swift 3 with [UInt8] type

func testCrypt(data:[UInt8], keyData:[UInt8], ivData:[UInt8], operation:Int) -> [UInt8]? {
    let cryptLength  = size_t(data.count+kCCBlockSizeAES128)
    var cryptData    = [UInt8](repeating:0, count:cryptLength)

    let keyLength             = size_t(kCCKeySizeAES128)
    let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
    let options:  CCOptions   = UInt32(kCCOptionPKCS7Padding)

    var numBytesEncrypted :size_t = 0

    let cryptStatus = CCCrypt(CCOperation(operation),
                              algoritm,
                              options,
                              keyData, keyLength,
                              ivData,
                              data, data.count,
                              &cryptData, cryptLength,
                              &numBytesEncrypted)

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

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

    return cryptData;
}

Swift 3 & 4 with Data type

func testCrypt(data:Data, keyData:Data, ivData:Data, operation:Int) -> Data {
    let cryptLength  = size_t(data.count + 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
        data.withUnsafeBytes {dataBytes in
            ivData.withUnsafeBytes {ivBytes in
                keyData.withUnsafeBytes {keyBytes in
                    CCCrypt(CCOperation(operation),
                              CCAlgorithm(kCCAlgorithmAES),
                              options,
                              keyBytes, keyLength,
                              ivBytes,
                              dataBytes, data.count,
                              cryptBytes, cryptLength,
                              &numBytesEncrypted)
                }
            }
        }
    }

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

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

    return cryptData;
}

let message     = "Don´t try to read this text. Top Secret Stuff"
let messageData = message.data(using:String.Encoding.utf8)!
let keyData     = "12345678901234567890123456789012".data(using:String.Encoding.utf8)!
let ivData      = "abcdefghijklmnop".data(using:String.Encoding.utf8)!

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

Example from sunsetted documentation section:

AES encryption in CBC mode with a random IV (Swift 3+)

The iv is prefixed to the encrypted data

aesCBC128Encrypt will create a random IV and prefixed to the encrypted code.
aesCBC128Decrypt will use the prefixed IV during decryption.

Inputs are the data and key are Data objects. If an encoded form such as Base64 if required convert to and/or from in the calling method.

The key should be exactly 128-bits (16-bytes), 192-bits (24-bytes) or 256-bits (32-bytes) in length. If another key size is used an error will be thrown.

PKCS#7 padding is set by default.

This example requires Common Crypto
It is necessary to have a bridging header to the project:
#import <CommonCrypto/CommonCrypto.h>
Add the Security.framework to the project.

This is example, not production code.

enum AESError: Error {
    case KeyError((String, Int))
    case IVError((String, Int))
    case CryptorError((String, Int))
}

// The iv is prefixed to the encrypted data
func aesCBCEncrypt(data:Data, keyData:Data) throws -> Data {
    let keyLength = keyData.count
    let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
    if (validKeyLengths.contains(keyLength) == false) {
        throw AESError.KeyError(("Invalid key length", keyLength))
    }

    let ivSize = kCCBlockSizeAES128;
    let cryptLength = size_t(ivSize + data.count + kCCBlockSizeAES128)
    var cryptData = Data(count:cryptLength)

    let status = cryptData.withUnsafeMutableBytes {ivBytes in
        SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, ivBytes)
    }
    if (status != 0) {
        throw AESError.IVError(("IV generation failed", Int(status)))
    }

    var numBytesEncrypted :size_t = 0
    let options   = CCOptions(kCCOptionPKCS7Padding)

    let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
        data.withUnsafeBytes {dataBytes in
            keyData.withUnsafeBytes {keyBytes in
                CCCrypt(CCOperation(kCCEncrypt),
                        CCAlgorithm(kCCAlgorithmAES),
                        options,
                        keyBytes, keyLength,
                        cryptBytes,
                        dataBytes, data.count,
                        cryptBytes+kCCBlockSizeAES128, cryptLength,
                        &numBytesEncrypted)
            }
        }
    }

    if UInt32(cryptStatus) == UInt32(kCCSuccess) {
        cryptData.count = numBytesEncrypted + ivSize
    }
    else {
        throw AESError.CryptorError(("Encryption failed", Int(cryptStatus)))
    }

    return cryptData;
}

// The iv is prefixed to the encrypted data
func aesCBCDecrypt(data:Data, keyData:Data) throws -> Data? {
    let keyLength = keyData.count
    let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
    if (validKeyLengths.contains(keyLength) == false) {
        throw AESError.KeyError(("Invalid key length", keyLength))
    }

    let ivSize = kCCBlockSizeAES128;
    let clearLength = size_t(data.count - ivSize)
    var clearData = Data(count:clearLength)

    var numBytesDecrypted :size_t = 0
    let options   = CCOptions(kCCOptionPKCS7Padding)

    let cryptStatus = clearData.withUnsafeMutableBytes {cryptBytes in
        data.withUnsafeBytes {dataBytes in
            keyData.withUnsafeBytes {keyBytes in
                CCCrypt(CCOperation(kCCDecrypt),
                        CCAlgorithm(kCCAlgorithmAES128),
                        options,
                        keyBytes, keyLength,
                        dataBytes,
                        dataBytes+kCCBlockSizeAES128, clearLength,
                        cryptBytes, clearLength,
                        &numBytesDecrypted)
            }
        }
    }

    if UInt32(cryptStatus) == UInt32(kCCSuccess) {
        clearData.count = numBytesDecrypted
    }
    else {
        throw AESError.CryptorError(("Decryption failed", Int(cryptStatus)))
    }

    return clearData;
}

Example usage:

let clearData = "clearData0123456".data(using:String.Encoding.utf8)!
let keyData   = "keyData890123456".data(using:String.Encoding.utf8)!
print("clearData:   \(clearData as NSData)")
print("keyData:     \(keyData as NSData)")

var cryptData :Data?
do {
    cryptData = try aesCBCEncrypt(data:clearData, keyData:keyData)
    print("cryptData:   \(cryptData! as NSData)")
}
catch (let status) {
    print("Error aesCBCEncrypt: \(status)")
}

let decryptData :Data?
do {
    let decryptData = try aesCBCDecrypt(data:cryptData!, keyData:keyData)
    print("decryptData: \(decryptData! as NSData)")
}
catch (let status) {
    print("Error aesCBCDecrypt: \(status)")
}

Example Output:

clearData:   <636c6561 72446174 61303132 33343536>
keyData:     <6b657944 61746138 39303132 33343536>
cryptData:   <92c57393 f454d959 5a4d158f 6e1cd3e7 77986ee9 b2970f49 2bafcf1a 8ee9d51a bde49c31 d7780256 71837a61 60fa4be0>
decryptData: <636c6561 72446174 61303132 33343536>

Notes:
One typical problem with CBC mode example code is that it leaves the creation and sharing of the random IV to the user. This example includes generation of the IV, prefixed the encrypted data and uses the prefixed IV during decryption. This frees the casual user from the details that are necessary for CBC mode.

For security the encrypted data also should have authentication, this example code does not provide that in order to be small and allow better interoperability for other platforms.

Also missing is key derivation of the key from a password, it is suggested that PBKDF2 be used is text passwords are used as keying material.

For robust production ready multi-platform encryption code see RNCryptor.

zaph
  • 111,848
  • 21
  • 189
  • 228
  • Implementation contains ECB mode. How can I set CBC mode? – Ankita Shah Jun 08 '16 at 04:35
  • Even the example contains ECB mode. `let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)`, code block is not working for my case – Ankita Shah Jun 08 '16 at 12:56
  • Look at the example in the answer, there is no ECB. – zaph Jun 08 '16 at 13:07
  • 1
    please help. I want the encryption in String. I tried to convert [UInt8] to String but it's returning nil. Please help. How can I get encrypted string value – Ankita Shah Jun 20 '16 at 11:27
  • 1
    Not all bytes are representable as printable characters and most are not representable as unicode characters. Encryption is a data operation and the output is essentially random 8-bit bytes and as such will not be representable directly as string characters. The answer is to convert the encrypted output to a different encoding, Base64 and hexadecimal are the usual encodings and on decryption to convert the string representation back to `[Uint8]`. – zaph Jun 20 '16 at 11:39
  • Can you please help, I am getting the half of actual plain text while using the above method. The encrypted base 64 string I received from server side. – Sujit Baranwal Jul 12 '18 at 06:33
  • You should ask a question with a [mcve], that means including code and input/output arguments and data. – zaph Jul 12 '18 at 13:27
  • @zaph you did a good job, written a detailed explanation of the code, which is hard to find nowadays. – Vizllx Mar 22 '19 at 04:47
  • @zaph, would you take a look at this one, which is based on your answer but for Swift 5 (and using `NSData.bytes` instead of `withUnsafeBytes`)? https://stackoverflow.com/q/55484384/584548 I thank you in advance. – backslash-f Apr 02 '19 at 22:30
  • I agree, don't use NSData.bytes in Swift. – zaph Apr 15 '19 at 10:31
  • What is the difference between **key (key128/key256)** and **iv** – Rizwan Jun 01 '19 at 11:28
  • I need to encrypt some information with some unique key and send it back to server where it can be de-crypted at server end with same key, But I am not able to get encrypted string from this code - **let encryptedPassword128 = aes128?.encrypt(string: password)** – Rizwan Jun 01 '19 at 11:29
  • where i have to send password string and key string? – Ashu Sep 24 '19 at 08:42
  • AES/GCM/NoPadding, is there any sample code for this encryption? Because google recommend to this encryption, and I have to keep same encryption for iOS, too. – Green Y. Feb 05 '20 at 14:29
  • @zaph nice anwer. My question is why is kCCBlockSizeAES128 to cryptSize: let cryptLength = size_t(ivSize + data.count + kCCBlockSizeAES128). I imagine it has something to do with padding, and making data multiple of kCCBlockSizeAES128, but I am not sure how this works? – MegaManX Oct 31 '22 at 13:56
  • When the protocol states padding is needed there must always be a last paddimg block—even if the encrypted data is a block multiple one must add a padding block. – zaph Nov 01 '22 at 18:36
48

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

Den
  • 3,179
  • 29
  • 26
  • 1
    What is the difference between **key (key128/key256)** and **iv** – Rizwan Jun 01 '19 at 10:58
  • 1
    I need to encrypt some information with some unique key and send it back to server where it can be de-crypted at server end with same key, But I am not able to get encrypted string from this code - 'let encryptedPassword128 = aes128?.encrypt(string: password)' – Rizwan Jun 01 '19 at 11:22
  • 1
    @Den But after migration to Swift 5.0 it is showing warning as.. withUnsafeBytes' is deprecated: use withUnsafeBytes (_: (UnsafeRawBufferPointer) throws -〉 R) rethrows -〉 R instead – Rakesh Jun 13 '19 at 06:00
  • Hi, I create the key256 and iv with C#. I get c9 d7 eb c0 0f 77 ca 56 56 69 83 64 6b 19 86 cb 86 54 c7 d3 03 da e2 20 58 c4 04 b2 67 78 f2 82 as bytes for the aes256. How can I use these bytes with your code? :) – rcpfuchs Jun 18 '20 at 15:33
10

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

backslash-f
  • 7,923
  • 7
  • 52
  • 80
  • 1
    `stringToDataFailed` is pointless. String conversion to utf8 data will never fail. `self.key = Data(keyString.utf8)` and `let dataToEncrypt = Data(string.utf8)`. Btw I would declare AES error enumeration inside your AES struct and just rename it to Error. Then you can call it `AES.Error.whatever` or simply `Error.whatever` when `Self` is `AES`. `extension AES { enum Error: Swift.Error { case invalidKeySize, generateRandomIVFailed, encryptionFailed, decryptionFailed, dataToStringFailed } }` – Leo Dabus Apr 04 '19 at 13:34
  • 1
    These are great suggestions. I changed the code accordingly. Thank you. – backslash-f Apr 04 '19 at 14:28
  • @backslash-f got 'dataToStringFailed' error when used above code.please help i am stuck from the last 4 days . – Vasu Nov 12 '19 at 04:48
  • @Vasu I just ran the above code in a Playground and it seems to be working fine. What is the exact String that you are trying to encrypt / decrypt? – backslash-f Nov 12 '19 at 11:36
  • @backslash-f decryption not working, encrypted string coming from server I am using your decrypt method to decrypt but getting this error 'dataToStringFailed'. – Ravi Sharma Apr 23 '20 at 13:30
  • invalid key size error which means this code is not generic – Abhishek Thapliyal Jun 08 '20 at 13:20
  • Why do we do data.count - ivSize in decrypt method. I am able to encrypt and while decrypting I dont get first 16 characters of the string ? they have gone missing, Any suggestions. – Max Feb 01 '21 at 08:32
7

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

    }
ingconti
  • 10,876
  • 3
  • 61
  • 48
  • 1
    1. The key should be the correct length, 16-bytes given `kCCKeySizeAES128` in the code. Relying on undocumented key extension should not be done. 2. This is one example from the accepted answer wrapped in an extension with out attribution. But it is pleasing that @ingconti saw fit to use my code. – zaph Oct 30 '17 at 20:58
  • What is the difference between **key (key128/key256)** and **iv** – Rizwan Jun 01 '19 at 11:27
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))

aguilarpgc
  • 1,181
  • 12
  • 24
Ayush Bansal
  • 103
  • 9
3

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 {...}
Gal Yedidovich
  • 757
  • 7
  • 16
2

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 ""
    }
}
1

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")
Mohammad Sadegh Panadgoo
  • 3,929
  • 1
  • 10
  • 12
0

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)
}
Alex Andrews
  • 1,498
  • 2
  • 19
  • 33
0

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)"
        }
    }
}
0

For someone also met issues when trying to decrypt files from JAVA, here's another approach that without fixed key length:

func encryptAES(data: Data, key: Data, iv: Data) -> Data? {
    var buffer = [UInt8](repeating: 0, count: data.count + kCCBlockSizeAES128)
    var numBytesEncrypted = 0
    
    let status = key.withUnsafeBytes { keyUnsafeRawBufferPointer in
        iv.withUnsafeBytes { ivUnsafeRawBufferPointer in
            data.withUnsafeBytes { dataUnsafeRawBufferPointer in
                CCCrypt(
                    CCOperation(kCCEncrypt),
                    CCAlgorithm(kCCAlgorithmAES128),
                    CCOptions(kCCOptionPKCS7Padding),
                    keyUnsafeRawBufferPointer.baseAddress,
                    key.count,
                    ivUnsafeRawBufferPointer.baseAddress,
                    dataUnsafeRawBufferPointer.baseAddress,
                    data.count,
                    &buffer,
                    buffer.count,
                    &numBytesEncrypted
                )
            }
        }
    }
    
    guard status == kCCSuccess else {
        print("Error: \(status)")
        return nil
    }
    
    let encryptedData = Data(bytes: buffer, count: numBytesEncrypted)
    return encryptedData
}
ChengEn
  • 91
  • 5