Swift Common Crypto

I tried recently to find a pure-Swift CommonCrypto wrapper, but all I could find were Obj-C wrapper libraries that were overkill. So, here I’d like to demonstrate how to bridge from Swift (version 5) to all those awkward UnsafeRawPointer and UnsafeMutableRawPointer types that face you when using libraries like CommonCrypto.

I’ll also share some best-practices when using encryption, to wit:

  • Use CBC mode, [not ECB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_(ECB).
  • Use PKCS7 for padding so that your input size doesn’t have to be a multiple of 128 bits.
  • Generate a fresh Initialisation Vector each encryption. It doesn’t need to be hidden, just random.

CommonCrypto wrapper

Note that operation/algorithm/options are ints, because that’s the type of the constants (kCCEncrypt, kCCDecrypt, kCCOptionPKCS7Padding, etc), whereas the param types are Int32. This is intended to make life more convenient for the caller.

CCrypt wants inputs (eg: key, iv, dataIn) like so: iv: UnsafeRawPointer!. To bridge from Swift to C, these steps are use for input data:

  • Convert Data to UnsafeRawBufferPointer by calling withUnsafeBytes
  • Then convert UnsafeRawBufferPointer to UnsafeRawPointer by calling baseAddress

Output data is a param of type UnsafeMutableRawPointer. To handle these, first allocate a buffer with: UnsafeMutableRawPointer.allocate(byteCount:alignment:). You’re responsible to deallocate that, so defer it immediately: defer { dataOut.deallocate() }. Finally, copy its contents back to a Swift Data with Data(bytes:count:).

func crypt(operation: Int, algorithm: Int, options: Int, key: Data,
        initializationVector: Data, dataIn: Data) -> Data? {
    return key.withUnsafeBytes { keyUnsafeRawBufferPointer in
        return dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in
            return initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in
                // Give the data out some breathing room for PKCS7's padding.
                let dataOutSize: Int = dataIn.count + kCCBlockSizeAES128*2
                let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize,
                    alignment: 1)
                defer { dataOut.deallocate() }
                var dataOutMoved: Int = 0
                let status = CCCrypt(CCOperation(operation), CCAlgorithm(algorithm),
                    CCOptions(options),
                    keyUnsafeRawBufferPointer.baseAddress, key.count,
                    ivUnsafeRawBufferPointer.baseAddress,
                    dataInUnsafeRawBufferPointer.baseAddress, dataIn.count,
                    dataOut, dataOutSize, &dataOutMoved)
                guard status == kCCSuccess else { return nil }
                return Data(bytes: dataOut, count: dataOutMoved)
            }
        }
    }
}

Random bytes generation

Here’s my pure-Swift wrapper for generating random bytes:

func randomGenerateBytes(count: Int) -> Data? {
    let bytes = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: 1)
    defer { bytes.deallocate() }
    let status = CCRandomGenerateBytes(bytes, count)
    guard status == kCCSuccess else { return nil }
    return Data(bytes: bytes, count: count)
}

Simple encryption and decryption

Here’s my using-the-good-options version of encryption/decryption:

extension Data {
    /// Encrypts for you with all the good options turned on: CBC, an IV, PKCS7
    /// padding (so your input data doesn't have to be any particular length).
    /// Key can be 128, 192, or 256 bits.
    /// Generates a fresh IV for you each time, and prefixes it to the
    /// returned ciphertext.
    func encryptAES256_CBC_PKCS7_IV(key: Data) -> Data? {
        guard let iv = randomGenerateBytes(count: kCCBlockSizeAES128) else { return nil }
        // No option is needed for CBC, it is on by default.
        guard let ciphertext = crypt(operation: kCCEncrypt,
                                    algorithm: kCCAlgorithmAES,
                                    options: kCCOptionPKCS7Padding,
                                    key: key,
                                    initializationVector: iv,
                                    dataIn: self) else { return nil }
        return iv + ciphertext
    }
    
    /// Decrypts self, where self is the IV then the ciphertext.
    /// Key can be 128/192/256 bits.
    func decryptAES256_CBC_PKCS7_IV(key: Data) -> Data? {
        guard count > kCCBlockSizeAES128 else { return nil }
        let iv = prefix(kCCBlockSizeAES128)
        let ciphertext = suffix(from: kCCBlockSizeAES128)
        return crypt(operation: kCCDecrypt, algorithm: kCCAlgorithmAES,
            options: kCCOptionPKCS7Padding, key: key, initializationVector: iv,
            dataIn: ciphertext)
    }
}

Example

And here’s how you’d use it, although you shouldn’t be force-unwrapping things in practice!

let key = randomGenerateBytes(count: 256/8)!
let superDuperSecret = "The quick brown fox jumps over the lazy dog".data(using: .utf8)!
let encrypted = superDuperSecret.encryptAES256_CBC_PKCS7_IV(key: key)!
let decrypted = encrypted.decryptAES256_CBC_PKCS7_IV(key: key)!
print(String(data: decrypted, encoding: .utf8)!)

CryptoKit

Once you’re happy to only support iOS13+, you should consider using CryptoKit over CommonCrypto: https://developer.apple.com/documentation/cryptokit/

Having said that, I hope this post is still a good example of how to handle C libraries that require UnsafeRawPointers.

MIT license applies. No warranties. You should get a security team to review all code in your project, such as this.

Thanks for reading, and have a great week!

Photo by Gabriel Wasylko on Unsplash

Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.

Chris Hulbert

(Comp Sci, Hons - UTS)

iOS Developer (Freelancer / Contractor) in Australia.

I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!

Get in touch:
[email protected]
github.com/chrishulbert
linkedin



 Subscribe via RSS