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
Using CommonCrypto in a Swift Playground
The following code demonstrates AES256-CBC encryption, decryption and key derivation within a Swift Playground.
import Foundation
import CommonCryptoenum 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.
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
[1]: Wikipedia: Advanced Encryption Standard https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
[2]: Wikipedia: Cipher Block Chaining https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_(CBC)
[3]: Apple Open Source CommonCrypto: CommonCryptor.h https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60118.50.1/include/CommonCryptor.h.auto.html
[4]: Apple Developer Documentation shows withUnsafeBytes
and withUnsafeMutableBytes
as deprecated at https://developer.apple.com/documentation/foundation/data/1780450-withunsafebytes and https://developer.apple.com/documentation/foundation/data/1779823-withunsafemutablebytes
[5]: Wikipedia: Ket Derivation Function https://en.wikipedia.org/wiki/Key_derivation_function
[6]: Apple Open Source CommonCrypto: CommonKeyDerivation.h https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60118.50.1/include/CommonKeyDerivation.h.auto.html
[7]: Axx repository on GitHub: https://github.com/eneko/axx