Apple recently introduced a new framework named CryptoKit at WWDC19, which makes generating hashes, encrypting data and verifying/authenticating data even easier.
Previously, the only way to use cryptography in Swift was via lower level APIs and complicated solutions. CryptoKit improves on this as it is much easier to use and doesn't require knowledge of low level operations.
In this tutorial, we'll explore how to perform different cryptographic operations using CryptoKit, including signing data, encrypting information, creating hashes, and performing key agreement.
Getting started
Note: This tutorial requires Xcode 11 and the latest beta versions of macOS 10.15 or iOS 13, which are currently in beta, as it relies on new features or frameworks that are not available on previous versions.
As CryptoKit is built into Apple's platforms, no installation or configuration is needed. However, Apple has a sample Swift playground that demonstrates different cryptographic operations, which I'll use as the base for this tutorial.
I'll include examples in this tutorial, but feel free to download the playground if you'd like to follow along and test out different examples.
If you aren't using Apple's sample playground, make sure to add import CryptoKit
to the top of your file to be able to use it in your code.
Generating Hashes
Hash functions generate a unique key that will stay the same as long as the input data is exactly the same. For example, this could be very useful to verify that a shared piece of data is the same.
CryptoKit makes it easy to generate different types of hashes with good collision resistance, which prevents the possibility of two different pieces of input data generating the same hash.
CryptoKit supports the SHA256
, SHA384
, and SHA512
algorithms (as well as some older, insecure algorithms that should not be used). As computers have gotten more powerful, hash sizes have continued to increase to make it harder for attackers to attempt to force collisions.
Use the hash(using:)
function on any algorithm to generate a hash for a piece of data, such as a string or a file.
Generating a hash for a file
if let filePath = Bundle.main.path(forResource: "JulianSchiavo", ofType: "blog"),
let data = FileManager.default.contents(atPath: filePath) {
let hash = SHA256.hash(data: data)
print(hash.description) // SHA256 digest: 8f9809a8ce23abad17966f88545989294d912146feab32682012ed116f4ea267
}
As you can see, generating hashes for files is as easy as loading the file, selecting an algorithm, and hashing it. Apple's Swift playground also includes an example of hashing files via a buffer.
Generating a hash for text or other data
if let data = "Julian Schiavo's Blog".data(using: .utf8) {
let hash = SHA256.hash(data: data)
print(hash.description) // SHA256 digest: 527f640b5cd67ac074eaf63e4fb3d1ceb2a22339318b95177a756ce60fec8a23
}
To generate a hash for text or other data, just do the same thing as with generating file hashes, except directly with the data or by encoding the string as data.
Encrypting Data
Encrypting files allows you to achieve both authenticity and confidentiality by converting the input data into cipher text that can only be read with the original, randomly generated key that is shared between parties.
CryptoKit supports both the AES-GCM
and ChaChaPoly
algorithms, although ChaChaPoly
is preferred as it is typically faster on mobile devices.
To encrypt a file, just seal it with the chosen algorithm:
let key = SymmetricKey(size: .bits256)
if let filePath = Bundle.main.path(forResource: "JulianSchiavo", ofType: "blog"),
let encryptedData = FileManager.default.contents(atPath: filePath),
let sealedBox = try? ChaChaPoly.seal(encryptedData, using: key) {
// Share the `sealedBox.combined` data to the other party
}
The sealed box contains 3 outputs from the operation: the sealedBox.ciphertext
(the encrypted data, always the same size as the input data), the sealedBox.tag
(used to ensure the data remains intact), and the sealedBox.nonce
(which should be different every time an encryption operation is performed, and is generated randomly if not provided during encryption).
Share the sealedBox.combined
property with the other party, which is generated from the tag, nonce, and cipher text.
// Receive the `sealedBox.combined` data from the other party and use it to decrypt the data
if let sealedBox = ChaChaPoly.SealedBox(combined: data),
let decryptedData = try? ChaChaPoly.open(sealedBox, using: key) {
// Use the `decryptedData` as necessary
}
Then, the receiving party, or client, can decrypt the content using the same key (which should not be shared in the same way to avoid attackers from gaining access to the key).
Creating and Validating Digital Signatures
Digital signatures are used to validate the authenticity and integrity of a message or piece of data. After signing the data with a private key, others can verify the signature using your public key.
CryptoKit has built in support for 4 different elliptic curve types, which are used to create and verify cryptographic signatures: Curve25519
, P521
, P384
, and P256
. The different types have different levels of security and speed, but Curve25519
is typically best.
First, generate a random secure public and private key-pair, and publish the public key.
let privateKey = Curve25519.Signing.PrivateKey() // Keep the private key safe
let publicKey = privateKey.publicKey
let publicKeyData = publicKey.rawRepresentation // Publish the public key
Then, sign a piece of data with the private key, such as a string.
if let data = "Julian Schiavo's Blog".data(using: .utf8),
let signature = try? privateKey.signature(for: data) {
// Share the data (for example, a document) and corresponding signature publicly
}
Finally, anyone can check that the signature is indeed authentic and from you using the public key:
if publicKey.isValidSignature(signature, for: data) {
print("The signature is valid")
}
If anyone tries to publish a fake signature, it will be rejected when someone tries to validate it.
Performing Key Agreement
Key agreement allows multiple parties to determine a shared encryption key that can be used to sign or encrypt data that they want to exchange.
First, create a unique salt that you will use when deriving keys. Here, we are using an if let
block to avoid the salt's encoded data from being optional, but you can also use a guard let
block instead.
if let salt = "Salty Blog".data(using: .utf8) {
// Derive keys using the salt
}
Then, both users A and B generate a public and private key-pair, and publish the public key while keeping the private key secret. You can use any of the elliptic curve types from the digital signatures section, such as P521
.
let privateKeyA = P521.KeyAgreement.PrivateKey()
let publicKeyA = privateKeyA.publicKey
let privateKeyB = P521.KeyAgreement.PrivateKey()
let publicKeyB = privateKeyB.publicKey
User A derives a shared secret with both their private key and User B's public key.
let sharedSecretA = try? privateKeyA.sharedSecretFromKeyAgreement(with: publicKeyB)
let symmetricKeyA = sharedSecretA.hkdfDerivedSymmetricKey(using: SHA256.self, salt: salt, sharedInfo: Data(), outputByteCount: 32)
Then, User B performs the same operation using their private key and User A's public key.
let sharedSecretB = try? privateKeyB.sharedSecretFromKeyAgreement(with: publicKeyA)
let symmetricKeyB = sharedSecretB.hkdfDerivedSymmetricKey(using: SHA256.self, salt: salt, sharedInfo: Data(), outputByteCount: 32)
You can check that both of the generated keys in this example are equal like this:
if symmetricKeyA == symmetricKeyB {
print("User A and User B's symmetric keys are equal")
}
Both User A and User B now have a copy of the same key that they can each use to authenticate or encrypt messages to and from each other.
Conclusion
In this tutorial, you explored multiple different cryptographic operations that can be performed using Apple's new CryptoKit framework in iOS 13.
Apple has a sample Swift playground that demonstrates similar cryptographic operations as in this tutorial, which I've included below as a reference.
I hope this tutorial was useful and helped you to learn more about using CryptoKit. If you have any questions or feedback, feel free to send them or email me: [email protected]. Thanks for reading 🔑