AES256-CBC File Encryption from the Command Line with Swift

Xcode 10.0 and Swift 4.2 were officially released back in September 2018. Since then, using CommonCrypto in Swift has been easier than ever before.

Using CommonCrypto before Swift 4.2 wasn’t trivial. It required defining a module map and importing CommonCrypto.h in the bridging header. This was not too bad for applications. However, if two or more imported frameworks relied on CommonCrypto, conflicts would arise, requiring users to namespace each module map (use different module names).

Fortunately, there is no longer need to worry about defining and using these module maps. CommonCrypto's C functions are still a bit cumbersome to work with, but, as will be shown below, they can be wrapped in much easier-to-use Swift methods.

About Encryption and Key Derivation

  • What is AES256-CBC Encryption?
  • AES256-CBC Encryption with CommonCrypto in Swift
  • PBKDF2 Key Derivation with CommonCrypto in Swift

Using CommonCrypto with Swift

  • Using CommonCrypto in Xcode projects
  • Using CommonCrypto in a Swift Playground
  • Using CommonCrypto with Swift Package Manager

Introducing axx Command Line Tool

  • Generating Encryption Keys with axx
  • Encrypting Files from the Terminal
  • Decrypting Files from the Terminal

About Encryption and Key Derivation

What is AES256-CBC Encryption?

AES stands for Advanced Encryption Standard¹, an specification for the encryption of electronic data used worldwide.

256 is the number of bits in the encryption key: 256 bits = 32 bytes. AES keys can be 128bit, 192bit, or 256bit, being the latest the most secure one.

CBC stands for Cipher Block Chaining², a mode of operation where the output of each encrypted block is used in the encryption of the next block. CBC is far superior to plain AES encryption, since it produces unique ciphertext for each encrypted block, even if the encrypted blocks contained the same input. This output uniqueness considerably prevents dictionary-based attacks.

AES256-CBC Encryption with CommonCrypto in Swift

Within theCommonCrypto framework, the method CCCrypt handle both symmetric encryption and decryption of data with AES-CBC.

The signature of the CCCrypt method is as follows (documentation³):

CCCryptorStatus
CCCrypt(CCOperation op, CCAlgorithm alg, CCOptions options,
const void *key, size_t keyLength, const void *iv,
const void *dataIn, size_t dataInLength, void *dataOut,
size_t dataOutAvailable, size_t *dataOutMoved);

This method, a priori quite daunting, requires eleven parameters. Fortunately, almost half of these parameters are just to indicate the size of data buffers used to pass in the encryption key, initialization vector, and input data, and to store the encrypted output data.

The following method can be used to encrypt some Data with AES256-CBC. This method takes in the input data, the encryption key and the IV, which is stored together with the ciphertext:

func encrypt(data: Data, key: Data, iv: Data) throws -> Data {
// Output buffer (with padding)
let outputLength = data.count + kCCBlockSizeAES128
var outputBuffer = Array<UInt8>(repeating: 0,
count: outputLength)
var numBytesEncrypted = 0
    let status = CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
Array(key),
kCCKeySizeAES256,
Array(iv),
Array(data),
data.count,
&outputBuffer,
outputLength,
&numBytesEncrypted)
    guard status == kCCSuccess else {
throw Error.encryptionError(status: status)
}
let outputBytes = iv + outputBuffer.prefix(numBytesEncrypted)
return Data(bytes: outputBytes)
}

The following method can be used to decrypt some ciphertext encrypted with with AES256-CBC. This method takes in the encrypted data and key. It extracts the stored IV from the input data.

func decrypt(data cipherData: Data, key: Data) throws -> Data {
// Split IV and cipher text
let iv = cipherData.prefix(kCCBlockSizeAES128)
let cipherTextBytes = cipherData
.suffix(from: kCCBlockSizeAES128)
let cipherTextLength = cipherTextBytes.count
    // Output buffer
var outputBuffer = Array<UInt8>(repeating: 0,
count: cipherTextLength)
var numBytesDecrypted = 0
    let status = CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
Array(key),
kCCKeySizeAES256,
Array(iv),
Array(cipherTextBytes),
cipherTextLength,
&outputBuffer,
cipherTextLength,
&numBytesDecrypted)
    guard status == kCCSuccess else {
throw Error.decryptionError(status: status)
}
    // Read output discarding any padding
let outputBytes = outputBuffer.prefix(numBytesDecrypted)
return Data(bytes: outputBytes)
}

The above two methods take advantage of direct conversion between Data and Array<UInt8>, together with pointer indirection for outputBuffer, numBytesEncrypted, and numBytesDecrypted. These methods could be written using Swift’s Data().withUnsafeBytes and Data().withUnsafeMutableBytes closures, but note these are marked as deprecated in Apple’s documentation⁴.

PBKDF2 Key Derivation with CommonCrypto in Swift

AES encryption keys are binary. This is, the key is a collection of raw bytes (32 for AES256), as opposed to a string of text.

This is an important thing to keep in mind when creating encryption keys. Contrary to string keys (or passphrases), binary keys use the full range of values available on each byte. From 0x00 to 0xFF, all values are valid. Strings in the other hand, are limited to non-special characters. as many values are reserved and cannot be represented as plain text. Binary keys are often base64 encoded, to make it easier to copy/paste and transfer via text based interfaces (web forms, etc).

The process of generating a binary key from a plain text passphrase string is called “key derivation”.

PBKDF stands for Passphrase-based Key Derivation Function⁵. CommonCrypto provides a function to securely derivate encryption keys from salted passphrases.

The signature of CCKeyDerivationPBKDF method is as follows (documentation⁶):

int 
CCKeyDerivationPBKDF(CCPBKDFAlgorithm algorithm,
const char *password, size_t passwordLen,
const uint8_t *salt, size_t saltLen,
CCPseudoRandomAlgorithm prf, unsigned rounds,
uint8_t *derivedKey, size_t derivedKeyLen);

The following method takes in a passphrase and salt strings, producing a secure encryption key that can be used with the methods seen before for encryption/decryption of data:

func derivateKey(passphrase: String, salt: String) throws -> Data {
let rounds = UInt32(45_000)
var outputBytes = Array<UInt8>(repeating: 0,
count: kCCKeySizeAES256)
    let status = CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passphrase,
passphrase.utf8.count,
salt,
salt.utf8.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
rounds,
&outputBytes,
kCCKeySizeAES256)
    guard status == kCCSuccess else {
throw Error.keyDerivationError(status: status)
}
return Data(bytes: outputBytes)
}

As before, these method takes advantage of direct conversion from Data to Array<UInt8>, and vice-versa. String buffers can be directly passed in, without any type aliasing.


Using CommonCrypto with Swift

The methods above demonstrate how to encrypt, decrypt and derivate encryption keys in Swift. Here is how to add the CommonCrypto framework to an existing project.

Using CommonCrypto in Xcode projects

Since Xcode 10.0 and Swift 4.2, using CommonCrypto on iOS, watchOS, tvOS, or macOS projects can be done with two easy steps:

  • Step1: Add Security.framework to a project target
  • Step 2: Use import CommonCrypto as needed
Adding Security.framework to an Xcode project

Using CommonCrypto in a Swift Playground

The following code demonstrates AES256-CBC encryption, decryption and key derivation within a Swift Playground.

import Foundation
import CommonCrypto
enum Error: Swift.Error {
case encryptionError(status: CCCryptorStatus)
case decryptionError(status: CCCryptorStatus)
case keyDerivationError(status: CCCryptorStatus)
}
func encrypt(data: Data, key: Data, iv: Data) throws -> Data {
// Output buffer (with padding)
let outputLength = data.count + kCCBlockSizeAES128
var outputBuffer = Array<UInt8>(repeating: 0,
count: outputLength)
var numBytesEncrypted = 0
    let status = CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
Array(key),
kCCKeySizeAES256,
Array(iv),
Array(data),
data.count,
&outputBuffer,
outputLength,
&numBytesEncrypted)
    guard status == kCCSuccess else {
throw Error.encryptionError(status: status)
}
let outputBytes = iv + outputBuffer.prefix(numBytesEncrypted)
return Data(outputBytes)
}
func decrypt(data cipherData: Data, key: Data) throws -> Data {
// Split IV and cipher text
let iv = cipherData.prefix(kCCBlockSizeAES128)
let cipherTextBytes = cipherData
.suffix(from: kCCBlockSizeAES128)
let cipherTextLength = cipherTextBytes.count
    // Output buffer
var outputBuffer = Array<UInt8>(repeating: 0,
count: cipherTextLength)
var numBytesDecrypted = 0
    let status = CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
Array(key),
kCCKeySizeAES256,
Array(iv),
Array(cipherTextBytes),
cipherTextLength,
&outputBuffer,
cipherTextLength,
&numBytesDecrypted)
    guard status == kCCSuccess else {
throw Error.decryptionError(status: status)
}
    // Discard padding
let outputBytes = outputBuffer.prefix(numBytesDecrypted)
return Data(outputBytes)
}
func derivateKey(passphrase: String, salt: String) throws -> Data {
let rounds = UInt32(45_000)
var outputBytes = Array<UInt8>(repeating: 0,
count: kCCKeySizeAES256)
    let status = CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passphrase,
passphrase.utf8.count,
salt,
salt.utf8.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
rounds,
&outputBytes,
kCCKeySizeAES256)

guard status == kCCSuccess else {
throw Error.keyDerivationError(status: status)
}
return Data(outputBytes)
}
let key = try derivateKey(passphrase: "hello", salt: "world")
let iv = Data([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6])
let input = Data("foobar".utf8)
let ciphertext = try encrypt(data: input, key: key, iv: iv)
let plain = try decrypt(data: ciphertext, key: key)
print(plain == input) // true

Using CommonCrypto with Swift Package Manager

For SwiftPM projects there is no need to link system frameworks, as these can be imported directly into the code. This means importing CommonCrypto is all needed to start using the library.

This also applies to using CommonCrypto in the Swift REPL.

Importing CommonCrypto in the Swift REPL

Introducing `axx` Command Line Tool

axx is an educational command line application (tool) that leverages the power of CommonCrypto to encrypt and decrypt files directly from the command line interface.

The full source code is available on GitHub⁷, including encryption, decryption, and key derivation methods seen above.

axx uses 256bit encryption keys stored on text files. These files follow the standard for base64 encoded keys. Example key file:

-----BEGIN KEY-----
ZGVa402uiNuN97Vy7YvQSWoxfsmqpp9z/DuAPVppkbE=
-----END KEY-----

Generating Encryption Keys with `axx`

Key files can be generated as follows:

$ axx k > key.pem

Encrypting Files from the Terminal

Encryption requires a key file and one or more files to encrypt:

$ axx e -i key.pem fileA.txt fileB.txt ...

The above command will generate fileA.txt.enc, fileB.txt.enc, etc, automatically appending .enc extension to the input file names.

Decrypting Files from the Terminal

Similarly, decryption requires a key file and one or more files to decrypt:

$ axx d -i key.pem fileA.txt.enc fileB.txt.enc ...

This will generate fileA.txt.enc.plain, fileB.txt.enc.plain, etc, automatically appending .plain extension to the input file names.


As demonstrated, using CommonCrypto in command line tools written in Swift is pretty easy