Quantcast
Channel: Scott James Remnant
Viewing all articles
Browse latest Browse all 20

Swift: Generating keys, and Encrypting and Decrypting text

$
0
0

Swift is still a very new programming language and as such there hasn’t been time yet for a large body of lore about the correct way to do things to be built up on the Internet. Apple have done a good job on the initial documentation, but the deep technical understanding of things, like the built-in protocols the language uses, hasn’t been built yet.

For a project I needed to be able to generate a public/private key pair, and then use those to encrypt and decrypt plain text. This requires a set of C APIs defined in Certificate, Key, and Trust Services Reference.

When I looked for examples on how to use these in Swift, I found some pretty terrible code out there. To me that’s always a warning sign; if code looks difficult and unwieldy to use, you’re probably doing it wrong. Of course, being a new language, it could be that Apple is doing it wrong, but in general the Swift language and compiler do try to give you a lot of support for bridging to the old APIs, so I was more suspicious about the examples.

For the rest of this blog post I’ll walk through what I learned and show some bad examples on the way, and then what I think is the correct way to do it.

First we need to import the security APIs for use into Swift:

import Security

Encrypting text

I’m going to skip over generating the key pair first and assume that we already have publicKey and privateKey populated, along with blockSize containing the size of each encryption block. This will allow us to understand a key feature of C API bridging and come back to the generation function once we understand it.

From the documentation we see that we want the SecKeyEncrypt function. It’s Objective-C prototype is pretty easy to understand and very typical of C APIs:

OSStatus SecKeyEncrypt ( SecKeyRef key, SecPadding padding, const uint8_t *plainText, size_t plainTextLen, uint8_t *cipherText, size_t *cipherTextLen );

Like many C APIs it uses buffers and lengths for the incoming and outgoing data. The incoming buffer is a pointer to constant byte data with the length indicating the length of the data in that buffer. The outgoing buffer is not a constant, and the length field is also a pointer; the length is read when the function is called to provide the size of the outgoing buffer, and is modified by the function before returning to provide the length of data in the buffer.

If we were to call this method from C, we would use malloc to allocate this buffer and probably just pass the character array directly in:

const char plain_text[] = "Some text to be encrypted";
size_t plain_text_len = strlen(plain_text);
uint8_t *encrypted_data = malloc(block_size);
size_t encrypted_data_len = block_size;

result = SecKeyEncrypt(key, padding, (const uint8_t *)plain_text, plain_text_len, encrypted_data, encrypted_data_len);

But when we call this from Objective-C we don’t consider this good style. We eschew malloc in favor of Foundation APIs, in particular NSData and NSMutableData as generic buffers.

NSString *plainText = @"Some text to be encrypted";
NSData *plainTextData = [plainText dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *encryptedData = [[NSMutableData alloc] initWithCapacity:blockSize];
size_t encryptedDataLen = blockSize;
result = SecKeyEncrypt(key, padding, (const uint8_t *)plainTextData.bytes, plainTextData.length, (uint8_t *)encryptedData.bytes, &encryptedDataLen);

Since we’re pretty familiar with this approach from all the times we’ve used it in Objective-C, we might think it’s a good approach in Swift too:

// Do not do this. This is the wrong way.
let plainText = "Some text to be encrypted"
let plainTextData = plainText.dataUsingEncoding(NSUTF8StringEncoding)
let encryptedData = NSMutableData(capacity: blockSize)
var encryptedDataLength = blockSize
result = SecKeyEncrypt(publicKey,
    SecPadding(kSecPaddingPKCS1),
    UnsafePointer<UInt8>(plainTextData!.bytes),
    UInt(plainTextData!.length),
    UnsafeMutablePointer<UInt8>(encryptedData!.bytes),
    UnsafeMutablePointer<UInt>(bitPattern: encryptedDataLength))

To my eye, there are at least three big clues that this is the wrong way to do things.

  1. Swift has native methods for obtaining the UTF-8 encoding of a String, but we’re using a Foundation method instead.
  2. We have a lot of optionals to unwrap, there’s no reason for a Swift object to need to be nil for a large number of cases.
  3. We’ve ended up with the wrong types, so need bizarre casts to things like UnsafePointer<T> and UnsafeMutablePointer<T> to fit the types of the method call.

Getting it right

So what is the right way? First let’s look at the Swift prototype of that method:

func SecKeyEncrypt(_ key: SecKey!,
                 _ padding: SecPadding,
                 _ plainText: UnsafePointer<UInt8>,
                 _ plainTextLen: UInt,
                 _ cipherText: UnsafeMutablePointer<UInt8>,
                 _ cipherTextLen: UnsafeMutablePointer<UInt>) -> OSStatus

Useful pieces of documentation to read at this point are the Interacting with C APIs section of the Using Swift with Cocoa and Objective-C book, and more usefully, Interacting with C Pointers on Apple’s Swift Blog.

We learn two key things here:

  • Pointer arguments in C become inout arguments in Swift.
  • Swift arrays can be passed in to pointer arguments.

Keeping in mind Swift’s strict type-safety, what we need for the ingoing plain text is to convert the String object into an array of UInt8 bytes representing the equivalent UTF-8 encoding of that string. As mentioned earlier, there’s a native Swift way of getting that encoding, the String.utf8 property which returns a String.UTF8View object. It turns out that object is a collection of UInt8 objects already, almost exactly what we need.

We can use the Array sequence constructor to get exactly what we do need:

let plainText = "A string to be encrypted"

let plainTextData = [UInt8](plainText.utf8)
let plainTextDataLength = UInt(plainText.count)

For the outgoing encrypted data we need another array of UInt8 bytes with enough capacity to hold the data block. There’s a Swift array constructor that accepts an initial length, why not just use that?

var encryptedData = [UInt8](count: Int(blockSize), repeatedValue: 0)
var encryptedDataLength = blockSize

The buffer is even zeroed for us, which fits better the general safe approach of Swift.

We have the right types, now we just need to deal with those unsafe and mutable pointer wrappers to pass them in. Happily we actually don’t. Swift handles these conversions automatically, if you ever find yourself having to force them, it should be a sign that you’ve got things wrong ahead of time and should backtrack a little.

The array and length can be passed directly into the parameters for the incoming buffer, because it’s a constant pointer and we’re passing in a constant array value.

For the outgoing parameters we’re using mutable pointers, but that’s ok because we’re also using an array stored in a variable. We simply have to use the & operator to indicate we’re aware we’re passing to an inout parameter. Remember that this does not mean address of like it does in C.

Putting it all together we end up with the simplest call possible:

result = SecKeyEncrypt(publicKey, SecPadding(kSecPaddingPKCS1),
    textData, textDataLength, &encryptedData, &encryptedDataLength)

Decrypting

Decrypting data is now easy as pie, we use exactly the same approach:

var decryptedData = [UInt8](count: Int(blockSize), repeatedValue: 0)
var decryptedDataLength = blockSize

result = SecKeyDecrypt(privateKey, SecPadding(kSecPaddingPKCS1),
    encryptedData, encryptedDataLength,
    &decryptedData, &decryptedDataLength)

The final thing we need to do is convert the decrypted data bytes back into a String object, interpreting as UTF-8 encoding. Sometimes things looking ugly means that we’re doing it wrong, but sometimes it does mean Apple have got it wrong. Swift actually provides no native way to do this, so we have to fall back on an extension from NSString:

let decryptedText = String(bytes: decryptedData,
    encoding:NSUTF8StringEncoding)

Generating the key pair

Now we’ve seen how to use unsafe and mutable pointers, the SecKeyGeneratePair function call is a little less scary:

func SecKeyGeneratePair(_ parameters: CFDictionary!,
    _ publicKey: UnsafeMutablePointer<Unmanaged<SecKey>?>,
    _ privateKey: UnsafeMutablePointer<Unmanaged<SecKey>?>) -> OSStatus

We know that the UnsafeMutablePointer is effectively documentation telling us that we should treat this parameter as an inout parameter and pass in a variable to it using the & operator:

var publicKeyPtr, privateKeyPtr: Unmanaged<SecKey>?
result = SecKeyGeneratePair(parameters, &publicKeyPtr, &privateKeyPtr)

Now let’s peel off the layers of the Unmanaged<SecKey>? type that we’re passing in and make sense of it.

The optional part actually makes perfect sense straight away. We don’t have a SecKey to pass in, so will be passing in nil. It’s the SecKeyGeneratePair function that fills the optional and provides us a value. On return we would check result and that both optionals contain a value, and handle error cases if not.

The Unmanaged<T> part is a little more complicated; we can find the documentation for these in the Working with Cocoa Data Types chapter of Using Swift with Cocoa and Objective-C. These wrappers are used when the C API has not been annotated to tell the compiler how the memory of the returned object is allocated.

Simply put, the compiler doesn’t know whether the object contained in this pointer on return is already retained or not. It provides two methods; takeRetainedValue to get the value if it was already retained before returning, and takeUnretainedValue to get a value that needs to be retained first.

The documentation tells us that the keys are already retained and need releasing:

let publicKey = publicKeyPtr!.takeRetainedValue()
let privateKey = privateKeyPtr!.takeRetainedValue()

let blockSize = SecKeyGetBlockSize(publicKey)

We might hope for later SDK updates to annotate this method call and make this unnecessary.


Viewing all articles
Browse latest Browse all 20

Trending Articles