Swift AES CBC 256 Encryption With Static 32bit Key and 32bit IV

We have the below Implementation in Android and the same has to be integrated into Swift.

Key :-  "d95acd54b4a821ff32c52825q931c194"

IV :-  "687b9509c25a34b8ad076346s8353d67"

Here Both the Key and IV are 32 bits and below is the android code.

public class AESEncryption {
    private static final String key = "d95acd54c6a821ff32c52825b931c194";
    private static final String initVector = "687b9509c25a14b8ad076346d8353d67";
    static byte[] bte = hexToBytes(initVector);
    public static String encrypt(String strToEncrypt) {
        try {
            CommonCode.showLog("log", bte.toString());
            IvParameterSpec iv = new IvParameterSpec(bte);
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            CommonCode.showLog("IV after logs", iv.toString());
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(strToEncrypt.getBytes());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                return Base64.getEncoder().encodeToString(encrypted).trim();
            } else {
                return android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT).trim();
            }
        } catch (Exception e) {
            CommonCode.showLog("Error while encrypting: ", e.toString());
        }
        return null;
    }
    public static String decrypt(String strToDecrypt) {
        try {
            IvParameterSpec iv = new IvParameterSpec(bte);
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
            } else {
                return new String(cipher.doFinal(android.util.Base64.decode(strToDecrypt, android.util.Base64.DEFAULT)));
            }
        } catch (Exception e) {
            CommonCode.showLog("Error while decrypting: " , e.toString());
        }
        return null;
    }
}

How can we mimic the above in Swift? Here in Android they are using static byte[] bte = hexToBytes(initVector); to convert the 32bit IV into 16 bit Bytes Array

I Have Tried the same approach on Swift below are the code snippet


[Contents.swift](https://developer.apple.com/forums/content/attachment/60fab4f2-1496-4003-9f37-c195de95e94a)

Replies

How can we mimic the above in Swift?

My recommendation would be to open a TSI for extra help here. If you do so, please include a focused sample project of the code that you have in Swift so far, and what you expect the result to be compared to what you are currently seeing.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

CryptoKit does not support AES-CBC [1]. The standard API for AES-CBC on our platforms is CommonCrypto. The CryptoCompatibility sample code has examples of how to use CommonCrypto to create AES-CBC results that match other common security frameworks, like OpenSSL.

With regards this specific question:

Here in Android they are using … hexToBytes

Swift does not have a nice API for converting between hex and binary (r. 28005863)-: However, in this case that’s not necessary because these are constant values and you can just save them as bytes:

let key: [UInt8] = [
    0xd9, 0x5a, 0xcd, 0x54, 0xc6, 0xa8, 0x21, 0xff, 
    0x32, 0xc5, 0x28, 0x25, 0xb9, 0x31, 0xc1, 0x94, 
]

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Because its focus is on algorithms that are secure by default, and AES-CBC is very easy to use insecurely. And apropos that, using a hard-wired IV is a really bad idea, security-wise.

So, I’m helping vishalfromnashik in another context but, as part of that, I created a Swift wrapper around CommonCrypto’s AES CBC support. I’ve pasted it in below for the benefit of all.

This includes a simple test suite, derived from the one in the CryptoCompatibility sample code (which is my go-to reference for this sort of thing).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

import Foundation
import CommonCrypto

/// Encrypts data using AES with PKCS#7 padding in CBC mode.
///
/// - note: PKCS#7 padding is also known as PKCS#5 padding.
///
/// - Parameters:
///   - key: The key to encrypt with; must be a supported size (128, 192, 256).
///   - iv: The initialisation vector; must be of size 16.
///   - plaintext: The data to encrypt; the PKCS#7 padding means there are no
///     constraints on its length.
/// - Returns: The encrypted data; it’s length with always be an even multiple of 16.

func QCCAESPadCBCEncrypt(key: [UInt8], iv: [UInt8], plaintext: [UInt8]) throws -> [UInt8] {

    // The key size must be 128, 192, or 256.
    //
    // The IV size must match the block size.

    guard
        [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256].contains(key.count),
        iv.count == kCCBlockSizeAES128
    else {
        throw QCCError(code: kCCParamError)
    }

    // Padding can expand the data, so we have to allocate space for that.  The
    // rule for block cyphers, like AES, is that the padding only adds space on
    // encryption (on decryption it can reduce space, obviously, but we don't
    // need to account for that) and it will only add at most one block size
    // worth of space.

    var cyphertext = [UInt8](repeating: 0, count: plaintext.count + kCCBlockSizeAES128)
    var cyphertextCount = 0
    let err = CCCrypt(
        CCOperation(kCCEncrypt),
        CCAlgorithm(kCCAlgorithmAES),
        CCOptions(kCCOptionPKCS7Padding),
        key, key.count,
        iv,
        plaintext, plaintext.count,
        &cyphertext, cyphertext.count,
        &cyphertextCount
    )
    guard err == kCCSuccess else {
        throw QCCError(code: err)
    }
    
    // The cyphertext can expand by up to one block but it doesn’t always use the full block,
    // so trim off any unused bytes.
    
    assert(cyphertextCount <= cyphertext.count)
    cyphertext.removeLast(cyphertext.count - cyphertextCount)
    assert(cyphertext.count.isMultiple(of: kCCBlockSizeAES128))

    return cyphertext
}

/// Decrypts data that was encrypted using AES with PKCS#7 padding in CBC mode.
///
/// - note: PKCS#7 padding is also known as PKCS#5 padding.
///
/// - Parameters:
///   - key: The key to encrypt with; must be a supported size (128, 192, 256).
///   - iv: The initialisation vector; must be of size 16.
///   - cyphertext: The encrypted data; it’s length must be an even multiple of
///     16.
/// - Returns: The decrypted data.

func QCCAESPadCBCDecrypt(key: [UInt8], iv: [UInt8], cyphertext: [UInt8]) throws -> [UInt8] {

    // The key size must be 128, 192, or 256.
    //
    // The IV size must match the block size.
    //
    // The ciphertext must be a multiple of the block size.

    guard
        [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256].contains(key.count),
        iv.count == kCCBlockSizeAES128,
        cyphertext.count.isMultiple(of: kCCBlockSizeAES128)
    else {
        throw QCCError(code: kCCParamError)
    }

    // Padding can expand the data on encryption, but on decryption the data can
    // only shrink so we use the cyphertext size as our plaintext size.

    var plaintext = [UInt8](repeating: 0, count: cyphertext.count)
    var plaintextCount = 0
    let err = CCCrypt(
        CCOperation(kCCDecrypt),
        CCAlgorithm(kCCAlgorithmAES),
        CCOptions(kCCOptionPKCS7Padding),
        key, key.count,
        iv,
        cyphertext, cyphertext.count,
        &plaintext, plaintext.count,
        &plaintextCount
    )
    guard err == kCCSuccess else {
        throw QCCError(code: err)
    }
    
    // Trim any unused bytes off the plaintext.
    
    assert(plaintextCount <= plaintext.count)
    plaintext.removeLast(plaintext.count - plaintextCount)

    return plaintext
}

/// Wraps `CCCryptorStatus` for use in Swift.

struct QCCError: Error {
    var code: CCCryptorStatus
}

extension QCCError {
    init(code: Int) {
        self.init(code: CCCryptorStatus(code))
    }
}

func main() {
    // This test case was taken from the CryptoCompatibility sample code, and
    // specifically the `-testAES256PadCBCEncryption` methods and
    // `-testAES256PadCBCDecryption` methods.
    //
    // <https://developer.apple.com/library/mac/#samplecode/CryptoCompatibility/>
    let key: [UInt8] = [
        0x0C, 0x10, 0x32, 0x52, 0x03, 0x02, 0xEC, 0x85,
        0x37, 0xA4, 0xA8, 0x2C, 0x4E, 0xF7, 0x57, 0x9D,
        0x2b, 0x88, 0xe4, 0x30, 0x96, 0x55, 0xeb, 0x40,
        0x70, 0x7d, 0xec, 0xdb, 0x14, 0x3e, 0x32, 0x8a,
    ]
    let iv: [UInt8] = [
        0xAB, 0x5B, 0xBE, 0xB4, 0x26, 0x01, 0x5D, 0xA7,
        0xEE, 0xDC, 0xEE, 0x8B, 0xEE, 0x3D, 0xFF, 0xB7,
    ]
    let plaintext332: [UInt8] = [
        0x54, 0x68, 0x65, 0x20, 0x41, 0x70, 0x70, 0x6C,
        0x65, 0x20, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61,
        0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72,
        0x6F, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x62,
        0x79, 0x20, 0x41, 0x70, 0x70, 0x6C, 0x65, 0x20,
        0x6F, 0x6E, 0x20, 0x61, 0x6E, 0x20, 0x22, 0x41,
        0x53, 0x20, 0x49, 0x53, 0x22, 0x20, 0x62, 0x61,
        0x73, 0x69, 0x73, 0x2E, 0x20, 0x41, 0x50, 0x50,
        0x4C, 0x45, 0x20, 0x4D, 0x41, 0x4B, 0x45, 0x53,
        0x20, 0x4E, 0x4F, 0x20, 0x57, 0x41, 0x52, 0x52,
        0x41, 0x4E, 0x54, 0x49, 0x45, 0x53, 0x2C, 0x20,
        0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x20,
        0x4F, 0x52, 0x20, 0x49, 0x4D, 0x50, 0x4C, 0x49,
        0x45, 0x44, 0x2C, 0x20, 0x49, 0x4E, 0x43, 0x4C,
        0x55, 0x44, 0x49, 0x4E, 0x47, 0x20, 0x57, 0x49,
        0x54, 0x48, 0x4F, 0x55, 0x54, 0x20, 0x4C, 0x49,
        0x4D, 0x49, 0x54, 0x41, 0x54, 0x49, 0x4F, 0x4E,
        0x20, 0x54, 0x48, 0x45, 0x20, 0x49, 0x4D, 0x50,
        0x4C, 0x49, 0x45, 0x44, 0x20, 0x57, 0x41, 0x52,
        0x52, 0x41, 0x4E, 0x54, 0x49, 0x45, 0x53, 0x20,
        0x4F, 0x46, 0x20, 0x4E, 0x4F, 0x4E, 0x2D, 0x49,
        0x4E, 0x46, 0x52, 0x49, 0x4E, 0x47, 0x45, 0x4D,
        0x45, 0x4E, 0x54, 0x2C, 0x20, 0x4D, 0x45, 0x52,
        0x43, 0x48, 0x41, 0x4E, 0x54, 0x41, 0x42, 0x49,
        0x4C, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4E, 0x44,
        0x20, 0x46, 0x49, 0x54, 0x4E, 0x45, 0x53, 0x53,
        0x20, 0x46, 0x4F, 0x52, 0x20, 0x41, 0x20, 0x50,
        0x41, 0x52, 0x54, 0x49, 0x43, 0x55, 0x4C, 0x41,
        0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4F, 0x53,
        0x45, 0x2C, 0x20, 0x52, 0x45, 0x47, 0x41, 0x52,
        0x44, 0x49, 0x4E, 0x47, 0x20, 0x54, 0x48, 0x45,
        0x20, 0x41, 0x50, 0x50, 0x4C, 0x45, 0x20, 0x53,
        0x4F, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x20,
        0x4F, 0x52, 0x20, 0x49, 0x54, 0x53, 0x20, 0x55,
        0x53, 0x45, 0x20, 0x41, 0x4E, 0x44, 0x20, 0x4F,
        0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E,
        0x20, 0x41, 0x4C, 0x4F, 0x4E, 0x45, 0x20, 0x4F,
        0x52, 0x20, 0x49, 0x4E, 0x20, 0x43, 0x4F, 0x4D,
        0x42, 0x49, 0x4E, 0x41, 0x54, 0x49, 0x4F, 0x4E,
        0x20, 0x57, 0x49, 0x54, 0x48, 0x20, 0x59, 0x4F,
        0x55, 0x52, 0x20, 0x50, 0x52, 0x4F, 0x44, 0x55,
        0x43, 0x54, 0x53, 0x2E
    ]
    let cyphertextAES256CBC332: [UInt8] = [
        0xDD, 0x7B, 0x23, 0x4D, 0x67, 0x05, 0xFE, 0xBF,
        0x28, 0x8D, 0x45, 0xFD, 0x93, 0x78, 0x6C, 0xEB,
        0x20, 0x22, 0xD8, 0x24, 0x89, 0x88, 0xAD, 0x1F,
        0x1C, 0x9E, 0x7E, 0x61, 0x6A, 0xE6, 0x4E, 0x79,
        0xA2, 0x47, 0x1B, 0xA3, 0x7C, 0xFE, 0x51, 0xED,
        0xF1, 0xEB, 0x9F, 0x5E, 0x01, 0x9B, 0x47, 0x30,
        0xD7, 0x2F, 0x87, 0x22, 0xDF, 0xA9, 0xEB, 0x1F,
        0x5C, 0xA3, 0x5F, 0xEA, 0x18, 0xDE, 0x2D, 0x18,
        0x08, 0x48, 0x8C, 0x84, 0xBC, 0xE9, 0x83, 0xA7,
        0x4E, 0x48, 0x92, 0xBB, 0x76, 0xA7, 0x9C, 0x71,
        0x27, 0x8B, 0x40, 0x82, 0x2E, 0xC3, 0xC5, 0x92,
        0xA5, 0x9F, 0x87, 0xC3, 0x90, 0xF2, 0x8E, 0x75,
        0xCB, 0xDB, 0x94, 0xC1, 0xBF, 0xC6, 0x60, 0xC7,
        0x6A, 0xBA, 0xAA, 0x35, 0x26, 0xB0, 0xE3, 0x49,
        0xCA, 0x26, 0x39, 0x72, 0xEB, 0x89, 0x01, 0x9F,
        0xC9, 0x44, 0x67, 0x74, 0x1F, 0xCD, 0xBA, 0x2C,
        0xB1, 0x95, 0x99, 0xFE, 0x1C, 0xDA, 0x4D, 0xE7,
        0x32, 0x6B, 0xE8, 0xCE, 0x66, 0x0F, 0xD3, 0x8E,
        0x25, 0x17, 0x82, 0x5A, 0x9C, 0x21, 0xA2, 0x28,
        0xF0, 0x71, 0x99, 0xD8, 0x4A, 0x5A, 0x58, 0x80,
        0x94, 0x36, 0x9A, 0x7F, 0xA5, 0xFA, 0xC1, 0x6E,
        0xA7, 0x04, 0x68, 0xF5, 0x2E, 0x38, 0x73, 0x01,
        0xF4, 0x99, 0xF8, 0x48, 0x12, 0x53, 0xCF, 0x06,
        0x67, 0x0B, 0x07, 0xF2, 0x45, 0x3A, 0xEE, 0xD7,
        0x2B, 0x45, 0x03, 0xE7, 0x50, 0x1D, 0x52, 0xD8,
        0xFA, 0x73, 0xC6, 0xBE, 0x79, 0x65, 0x01, 0x11,
        0x81, 0x0B, 0x94, 0x0E, 0xA4, 0x96, 0x20, 0xC9,
        0x60, 0xBE, 0xB4, 0xEA, 0x1F, 0xCC, 0xC4, 0x22,
        0x6A, 0xE6, 0x52, 0xDE, 0x62, 0x25, 0x61, 0xCF,
        0xE1, 0x2D, 0x1C, 0x0B, 0x65, 0xD3, 0xC3, 0x08,
        0xEE, 0x00, 0x5B, 0x61, 0x06, 0x72, 0xAA, 0xB6,
        0x2F, 0x3D, 0xC7, 0x23, 0xAE, 0xF7, 0xA7, 0xC3,
        0xA9, 0x7E, 0xBF, 0xC7, 0xF0, 0x3B, 0x68, 0xB6,
        0xEB, 0x22, 0xA2, 0x8E, 0x70, 0xE3, 0xD3, 0x27,
        0x83, 0xDB, 0x1C, 0xD2, 0x48, 0x3C, 0xEE, 0x63,
        0x36, 0xAA, 0x9D, 0xD7, 0x2D, 0x24, 0x7C, 0xD2,
        0x22, 0x1E, 0xFB, 0x42, 0xE4, 0x4F, 0xA7, 0x48,
        0xB2, 0x3F, 0xB6, 0xD3, 0x25, 0xAD, 0x93, 0x72,
        0x02, 0x8F, 0x0E, 0x70, 0x57, 0xF4, 0x74, 0xC9,
        0x2D, 0x1E, 0xEB, 0xC6, 0x4A, 0x78, 0x21, 0x33,
        0x4F, 0x2A, 0x41, 0x44, 0xD1, 0xC9, 0x45, 0x13,
        0x5A, 0x0D, 0x64, 0x4E, 0xD6, 0xF5, 0x54, 0x9E,
    ]
    let cyphertext = try! QCCAESPadCBCEncrypt(key: key, iv: iv, plaintext: plaintext332)
    assert(cyphertext == cyphertextAES256CBC332)
    let plaintext = try! QCCAESPadCBCDecrypt(key: key, iv: iv, cyphertext: cyphertext)
    assert(plaintext == plaintext332)
    print("Success!")
}

main()