I decided to look into it a bit more. I had help from Mike Ash, as well as another knowledgable guy. Looking at the header helped a lot as well. It is extremely detailed. Since the project was somewhat challenging, I thought I would sketch out what I learned and post the code.
CommonCrypto is a C library. So, for example, the function that does one-step encryption or decryption,
CCCrypt, is declared like this in
The first two arguments can be defined in Swift like this:
let operation = CCOperation(kCCEncrypt)
let algorithm = CCAlgorithm(kCCAlgorithmAES)
We're first going to encrypt, and the encryption scheme will be AES.
If we look in the header, it tells us that the block size for AES is 16 bytes or 128 bits.
kCCAlgorithmAES128 Advanced Encryption Standard, 128-bit block
One option for block ciphers is padding, as defined in PKCS7; when padding is enabled, the total amount of data encrypted does not have to be an even multiple of the block size, and the actual length of plaintext is calculated during decryption.
Another option for block ciphers is Cipher Block Chaining, known as CBC mode. When using CBC mode, an Initialization Vector (IV) is provided along with the key when starting an encrypt or decrypt operation. If CBC mode is selected and no IV is provided, an IV of all zeroes will be used.
To begin with we'll use ECB with PKCS7 padding because it seems simpler.
let options = CCOptions(kCCOptionPKCS7Padding | kCCOptionECBMode)
CCCrypttakes three arguments of type
const void *and one of type
void *. The three
constarguments are the key, the initialization vector
iv, and the plaintext or data (
dataIn). These can be provided as Swift arrays:
[UInt8], or even (for the key and plaintext) as Swift Strings. No need for NSData or UnsafePointer
The return type for this function is
CCCryptorStatus, which is 0 for success, and something else for an error. The codes are also shown in the header:
So, when I received -4301 as the result, which at first I thought was garbage,
CCCryptwas actually telling me "insufficient buffer provided for the specified operation."
Of special note: I found that although the message does not have to be 128 bits or a multiple, the key does. If it is not, the encrypt operation returns 0 for success and some encrypted data, but when decrypted we don't get our plaintext back!
A buffer is needed, into which the encrypted data will be written.
let bufferSize = 128
var cipherData = [UInt8](count: bufferSize, repeatedValue: 0)
var resultLen = 0
var status: Int32 = 0
Another thing that confused me was that these sizes (except for resultLen and status) are in bits, not bytes.
When the buffer is passed into
CCCryptwe need a cast:
We provide the address of the Int variable
&resultLen, and after the function returns, that value tells how much data was written.
Finally, the other argument is the initialization vector
In our first pass at this, we specify ECB mode and PKCS7Padding, so no IV is needed, and we just pass
nilfor this argument.
It works. The first 9 bytes of the output at the end are the same as what we put in. I put the playground on github here.
The only thing I haven't figured out with this one is how to know the size of the message when decrypting.
The second approach uses CBC. It's the same as the first, except we change the options to the default
let options = CCOptions()
define an initialization vector, and then fill it with random bytes.
var iv = [UInt8](count: blockSize, repeatedValue: 0)
SecRandomCopyBytes(kSecRandomDefault, blockSize, &iv)
Other than that, the only thing is to be sure and pad the message to 16 bytes. It works. The playground is here.
UPDATE: I implemented the other code sketched out in Mike's article: encrypting in steps for a longer message (playground), and key stretching (playground).