Common cryptographic operations in Swift with CryptoKit

Dimitri James Tsiflitzis
The Startup
Published in
7 min readOct 9, 2019

What is CryptoKit?

Apple’s CryptoKit is a new (as of summer ’19) library available from iOS 13 and onwards that performs cryptographic operations securely and efficiently. Its features include:

  • A (nice) Swift API.
  • Access to cryptographic functions.
  • Ease of use for easy to misuse functions.
  • An improvement over legacy systems.

Existing cryptography solutions on iOS

There are two popular, trusted and open-source solutions. The first one is OpenSSL (official) which is an open-source C based library that you can statically link to your app at the cost of increasing its size though. The second one is CommonCrypto (official and source) which is also an open-source C library used in your apps as a dynamically linked framework (so your app's size doesn’t increase).

If you use the first solution the size of your app will be inflated by several megabytes, if you use the second solution you might be exposed to a library swap attack where an attacker can replace your copy with their own (also possible, but harder, in the first case).

Both of these are lower-level systems and regardless of you which one you chose to use, you had to deal with raw pointers and explicit memory management. CryptoKit frees your app from this by providing a high-level swift API that also overwrites sensitive data during memory deallocation.

When to use cryptography in your apps

There are several reasons that you might want to use cryptography in your applications; You maybe want to protect something, or you perhaps want to keep something secret.

  • You might want to enforce the protection of your users or your organizations' data by keeping sensitive information inaccessible to anyone without explicit access; transaction and account information in a banking app for instance.
  • You also might want to maintain the confidentiality of communication between peers in a chat or email application.
  • Finally, you might want to protect and provide authentication of the origin of some information; for example, if you are performing receipt validation on in-app purchases.

So you can see that the applications are quite broad and diverse and it’s possible that they might apply to your own use case.

Note: Please never use an untrusted version of OpenSSL. Just download the library from a trusted source (i.e. https://www.openssl.org/source/) and doublecheck the download hash.

Note 2: It’s worth mentioning that you could have rolled your own crypto which I cannot recommend at all, unfortunately. It’s a monumental task with so many things that could go wrong (defending against man-in-the-middle attacks, birthday attacks, known key attacks, etc.). You should consider contributing to an existing project instead. I f you are a cryptographer though and chose to go down this route though good on you 🙌

Getting started

We are going to go through some of the most common cases covered by CryptoKit. These are:

  • Hashing
  • Encrypting and decrypting data
  • Creating and Verifying Signatures
  • Authenticating Data
  • Performing Key Agreement

Hashing Data

A hash function is any function that can be used to map data of arbitrary size to fixed-size values. This means that a hash function takes an input and transforms it into an output (a hash or digest) that represents this input. This hash value is virtually unique.

Image courtesy of Wikipedia https://en.wikipedia.org/wiki/Cryptographic_hash_function

You can use hashing on any given data as an input. These could be, but not limited to, raw data, files, images, audio or video. You can hash the contents of a video file for instance and send the hash to a receiver along with the actual video file. The receiver can run the same hashing function on the data on his end to see if the same digest is produced. If it isn’t, then the file has been tampered with or is not the same file that was originally transmitted.

This is how to hash a video file:

import CryptoKitimport Foundationlet path = Bundle.main.path(forResource: "video", ofType: "mp4")!let data = FileManager.default.contents(atPath: path)!let digest = SHA256.hash(data: data)print(digest)// or a better representation
let stringHash = hash.map { String(format: "%02hhx", $0) }.joined()
print(stringHash)

Note: Prefer the .map method to obtain a hash representation because print(digest) uses the description property which Apple can change at any time they want to and that could break your code.

Sometime the file might be too large to load and fit in memory. In this case, you can use swift streams to get a digest:

var hasher = SHA256()let path = Bundle.main.path(forResource: "video", ofType: "mp4")!let stream = InputStream(fileAtPath: path)!stream.open()let bufferSize = 512let buffer = UnsafeMutablePointer<UInt8>
.allocate(capacity: bufferSize)
while stream.hasBytesAvailable { let read = stream.read(buffer, maxLength: bufferSize) let bufferPointer = UnsafeRawBufferPointer(start: buffer, count: read) hasher.update(bufferPointer: bufferPointer)}let digest = hasher.finalize()print(digest)

Encrypting and decrypting data

In the data encryption security method, information is encoded in a way that can only be accessed with the correct encryption key that is held by an authorized party. If the encryption key is incorrect or missing the data cannot be accessed and appears random and unreadable.

You can encrypt the contents of a super-secret message you wish to send to someone. Presuming both and only both of you have the key which was used to encrypt the message, then both of you are the only ones that can read the messages exchanged between you. Like so:

let key = SymmetricKey(size: .bits256)let path = Bundle.main.path(forResource: "video", ofType: "mp4")!let data = FileManager.default.contents(atPath: path)!// To encryptlet encryptedData = try! ChaChaPoly.seal(data, using: key)// To decryptlet sealedBox = try! ChaChaPoly.SealedBox(combined: encryptedData)let decryptedData = try! ChaChaPoly.open(sealedBox, using: key)

ChaChaPoly is an implementation of the ChaCha20-Poly1305 cipher in case you were wondering.

Creating and Verifying Signatures

A digital signature in cryptography is a mathematical scheme for verifying the authenticity of digital information such as messages or documents. A signature that has been confirmed to be valid by a recipient, gives them a strong indication that the information was indeed created by the person claiming to have created it and hasn’t been tampered with.

Image courtesy of Wikipedia https://en.wikipedia.org/wiki/Digital_signature

It’s straight forward to sign data using CryptoKit:

let privateKey = Curve25519.Signing.PrivateKey()let publicKey = privateKey.publicKey // publicize freelylet publicKeyData = publicKey.rawRepresentationlet signingPublicKey = try! Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData)// sign some data with your private keylet data = "All your base are belong to us".data(using: .utf8)!let signature = try! privateKey.signature(for: data)if signingPublicKey.isValidSignature(signature, for: data) {    print("The signature is valid.")}

Authenticating Data

In data authentication, the aim is to verify the identity of a user. A common case where we would be interested in doing that is when a user requests to send data to a server we control but we would like them to verify their identity before we allow them to do so.

let path = Bundle.main.path(forResource: "video", ofType: "mp4")!let data = FileManager.default.contents(atPath: path)!let key = SymmetricKey(size: .bits256) // key shared by both partieslet authentication = HMAC<SHA256>
.authenticationCode(for: data,
using: key)let authenticationData = Data(authentication)
// ~~~~~~ travel accross the network ~~~~~~~
if (HMAC<SHA256>.isValidAuthenticationCode(authenticationData, authenticating: data, using: key)) { print("Validated ✅")}

Performing Key Agreement

In cryptography, a key-agreement protocol is a process during which two parties, each with their own separate private key, establish a shared secret between them.

Image courtesy of Wikipedia https://en.wikipedia.org/wiki/Alice_and_Bob

This allows them to encrypt or sign data they wish to exchange. Also, this protocol, when executed, prevents unauthorized parties from eavesdropping. Let’s see how:

let salt = "Salt".data(using: .utf8)!
let firstPrivateKey = P256.KeyAgreement.PrivateKey()let firstPublicKey = firstPrivateKey.publicKeylet secondPrivateKey = P256.KeyAgreement.PrivateKey()let secondPublicKey = secondPrivateKey.publicKeylet firstSharedSecret = try! firstPrivateKey.sharedSecretFromKeyAgreement(with: secondPublicKey)let firstSymmetricKey = firstSharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: salt, sharedInfo: Data(), outputByteCount: 32)let secondSharedSecret = try! secondPrivateKey.sharedSecretFromKeyAgreement(with: firstPublicKey)let secondSymmetricKey = secondSharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: salt, sharedInfo: Data(), outputByteCount: 32)if firstSymmetricKey == secondSymmetricKey { print("First and second have the same key to do as they please")}

Conclusion

So far, we have explored five different cases using CrypoKit, but there are so many more (the hardware-based key manager comes to mind)! If you are interested and would like to find out more there are plentiful code examples that Apple has provided in the documentation both as playgrounds and Xcode projects.

I hope you learned more about encryption with CryptoKit by reading this. Its ease of use compared to pre-iOS 13 solutions definitely makes it more appealing in the long run, and will most likely help many more developers make their apps more secure 🚀.

--

--

Dimitri James Tsiflitzis
The Startup

Developer, scrum enthusiast, @sequeapp building productivity apps; formerly @nimber, @peopleperhour, @taxibeat; game dev @sprimp and orgniser @cocoaheadsgr