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:
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:
withUnsafeBytes
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)
}
}
}
}
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)
}
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)
}
}
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)!)
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.
(Comp Sci, Hons - UTS)
Software 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