When CryptoKit is not Enough

This article is a continuation to my Common Cryptographic Operations with CryptoKit article. If you want to learn how to use CryptoKit, read that one instead, and come to this one when you need a feature not offered by it.

As I have been playing with the amazing CryptoKit framework in the past few weeks, I have discovered a few more things that CryptoKit currently doesn’t do. This is not generally a bad thing, and I think these limitations are related to what seem to be the goal of the framework:

  • Dealing with cryptography is hard, and Snej makes a good point in a comment in my previous CryptoKit article: It gives you easy and relatively safe operations without needing to dive deeply into the cryptographic primitives underneath it. This prevents us from writing dangerous apps that could destroy its security with an oversight or wrong settings.
  • Interoperability is not much of a concern (also a good point by Snej) because Apple has a whole ecosystem where all the platforms inside it can interpret and work with content generated by CryptoKit. CryptoKit is open source anyway, so it’s not unreasonable to expect other platform providers to port it or adopt its format in the future.

Making working with cryptography easier and providing developer with tools to implement it safely seems to be a great general goal for the framework, and for this reason it’s missing some features that may be surprising to some. Some may find it surprising, for example, that CryptoKit doesn’t support AES-CBC out of the box. Reading Twitter, I found a discussion where Frederic Jacobs says the following regarding AES in CBC mode:

“That call is AES in CBC mode. It is not supported in CryptoKit. AES-CBC does not provide authentication and is hard to use correctly.

Moving to an authenticated mode of operation such as GCM is recommended." - Frederic Jacobs, in a Tweet

AES-CBC being hard to use correctly sounds like a very reasonable reason to leave it out of CryptoKit, but AES in CBC mode - along with other cryptographic tools - are very popular elsewhere. So what can we do in this situation? Dropping down to the Security and CommonCrypto frameworks.

In this article, we will explore some operations that CryptoKit can’t currently do, and what we can do when we need them.

A Word on Missing Operations.

CryptoKit can do the vast majority of operations from specific hashes to applying specific cyphers. CryptoKit basically has all the bases for primitives covered, and most of its limitations seem to be related to missing ciphers and hashes rather than missing features. There is just one missing feature that I consider is a little bit important in some contexts, but it’s not hard to implement with the lower level frameworks.

Missing Algorithms.

Missing Hashing Algorithms.

CryptoKit by default provides strong hashes (SHA256, SHA385, and SHA512) , and an Insecure container for insecure but insanely popular hashes (SHA1, MD5).

On Apple’s platforms, you can use a wide array of other hashes too, but unless it is for compatibility reasons, you are never going to end up using them.

Dropping down to CommonCrypto will allow you to calculate MD2, MD4, and SHA224 hashes for any piece of data. It being a lower-level framework, its usage is more verbose than CryptoKit can provide.

In the following example, we will calculate a MD2 hash with CommonCrypto. You can use the other hashing algorithms by replacing CC_MD2 with CC_SHA224, CC_MD4, and you can even use the hashes provided by CryptoKit this way with CC_SHA256, CC_SHA512, and more.

let data = string.data(using: .utf8)!

var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))

data.withUnsafeBytes {
    _ = CC_MD2($0.baseAddress, CC_LONG(data.count), &digest)
}

// Digest now has the hash.

Leaving the verbosity of the code aside, there’s a few other differences compared to how CryptoKit works. First, the usage of references just doesn’t exist in CryptoKit’s version. Because of this, CryptoKit can just return it to you, whereas the CommonCrypto requires us to define a “holder” variable where the hash will exist after CC_MD2 returns. Third, the CommonCrypto version will write the hash in a [UInt8] array, and CryptoKit returns a Digest object of the type of the hashing used. For example if you use SHA512.hash(data:), CryptoKit returns a SHA512Digest object. CryptoKit wrap a lot of things for you so you never have to worry about such details.

The area where CommonCrypto and CryptoKit are the same is when you need a string representation of the hash. In both CryptoKit you need to manually convert the data (in CryptoKit’s case, you have to get the Data out first with withUnsafeBytes, as shown in my previous CryptoKit article linked above) into a string:

func dataToHexString(_ data: Data) -> String {
  let hexBytes = digest.map { String(format: "%02hhx", $0) }
  return hexBytes.joined()
}

I believe retrieving a textual representation of a hash is a very common operation to do, so I’m a bit surprised it’s not supported by CryptoKit out of the box. Luckily, getting the data out is very easy. You could write an extension to do this for you in either case.

Working with other Encryption Algorithms

AES-CBC is just one of the algorithms that CryptoKit doesn’t support. There’s a few other cyphers and modes that, if you want to use, you will have to once again drop down to CommonCrypto.

CommonCrypto offers the CCCrypt function to encrypt and decrypt anything. It’s just one function, but it has so many parameters it can be intimidating to see it for the first time. Among the bunch of information it takes, there’s the algorithm and the mode.

CommonCrypto can use the following cyphers:

  • AES (in both 256 and 128 bits)
  • DES
  • RC2
  • RC4
  • 3DES
  • CAST
  • Blowfish

For the operation modes, I was only able to find references to CBC (which is the default for CommonCrypto) and ECB. Interestingly, I wasn’t able to find the GCM mode in CommonCrypto. According to the first comment in this StackOverflow question, Apple will no longer develop CommonCrypto, which makes me think AES-GCM was exclusively implemented in CryptoKit for the first time. I cannot confirm or deny that Apple will no longer develop CommonCrypto.

Giving an example for this is redundant because this is one area that has been covered by other people. One such example is here, where it implements AES256 in CBC mode.

Missing Public-Key Cryptography

The Security framework can generate RSA keys. Many applications still use RSA, so if you ever need it, you can drop down to Security to implement it.

This StackOverflow Answer has a complete example to generate a RSA key-pair using Swift.

Other than RSA, you can also generate ECDSA and EC key-pairs with the same framework.

Cryptographically Secure Data and Key Derivation

Generating Cryptographically Secure Random Bytes

When I started working on the framework for an upcoming crypto app, I noticed that there’s no way to generate a bunch of cryptographically secure random bytes with CryptoKit.

If you need to do this, you can drop down to the Security framework again. If you need to generate random salts or other kind of cryptographically secure data, you can use a function that looks like this:

/// Returns cryptographically secure random data.
///
/// - Parameter length: Length of the data in bytes.
/// - Returns: Generated data of the specified length.
func randomData(length: Int) -> Data {
    var data = Data(count: length)
    _ = data.withUnsafeMutableBytes {
      SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!)
    }
    return data
  }

This function will generate a random blob of data of the specified size for you. CryptoKit can handle a lot of data that is supposed to be random for you - It can automatically generate nonces when you are using the symmetric cyphers like ChaChaPoly or AES-GCM, but you still need to generate your own salts for some operations like when doing key agreement, and SecRandomCopyBytes is a good way of doing it.

Key Derivation

I wasn’t able to find a way to do key derivation with CryptoKit. If you write an app and expect users to provide their own password, you shouldn’t use their password for the cryptographic operations directly. Instead, you should derive a cryptographically secure key using the user’s password and use that key to encrypt and decrypt instead.

CommonCrypto offers the CCKeyDerivationPBKDF function which you can use to derive a key based on a password.

To derive a password using PBKDF2, you can use a function like this:

func pbkdf2(hash: CCPBKDFAlgorithm,
            password: String,
            salt: Data,
            keyByteCount: Int,
            rounds: Int) -> Data? {
    guard let passwordData = password.data(using: .utf8) else { return nil }

    var derivedKeyData = Data(repeating: 0, count: keyByteCount)
    let derivedCount = derivedKeyData.count

  let derivationStatus: OSStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
      let derivedKeyRawBytes = derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress
        return salt.withUnsafeBytes { saltBytes in
          let rawBytes = saltBytes.bindMemory(to: UInt8.self).baseAddress
          return CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                password,
                passwordData.count,
                rawBytes,
                salt.count,
                hash,
                UInt32(rounds),
                derivedKeyRawBytes,
                derivedCount)
        }
    }

    return derivationStatus == kCCSuccess ? derivedKeyData : nil
}

Unfortunately there isn’t much documentation on this so I wasn’t able to find different combinations for parameters. In particular I wasn’t able to find a different CCPBKDFAlgorithm.

Last Option - Third Party Libraries

If you come to the point in which you need to use a cipher, hash, or public key algorithms that is not covered by any framework on an Apple OS, you will need to use a third-party library.

OpenSSL is a toolkit for TSL and SSL protocolos, but it is also a general-purpose cryptographic library. It offers a bunch of features. The huge downside of this library is that, it’s core being written in C, it is very complicated to use with Swift. That said, if you ever need any of the following features, you can use OpenSSL. I’m listing only the algorithms that aren’t already covered by CryptoKit, CommonCrypto, and Security:

Cyphers

  • Camellia
  • Poly1305
  • SEED
  • IDEA
  • RC5
  • GOST 2814-89
  • SM4

Hash Functions

  • SHA-3
  • RIPEMD-160
  • MDC-2
  • GOST R 34.11-94
  • BLAK2
  • Whirlpool
  • SM3

Public Key Cryptography

  • Ed25519
  • X448
  • GOST R 34.10-2001

Finally, if you need to implement signature verification and validation in your iOS app, then OpenSSL is the way to go. Apple themselves don’t mention their own libraries for parsing and recommend using a statically-linked library such as OpenSSL instead1.

Conclusion

CryptoKit is going to be enough for many modern crypto apps, but there will be cases in which CryptoKit won’t have everything you need. It’s not unreasonable to expect that CryptoKit will add support for more cryptographic algorithms in the future, but until then, we have some reasonable alternatives to the things that CryptoKit cannot currently do.