What is the difference between hashing, a MAC, encryption and a digital signature, and when do you use CryptoKit instead of CommonCrypto?
Predict first
What is the SHA-256 digest of "abc", and the sizes of common digests?
import CryptoKit
import Foundation
let h = SHA256.hash(data: Data("abc".utf8))
print(h.map { String(format: "%02x", $0) }.joined())
print(SHA256.byteCount, Insecure.MD5.byteCount, SHA512.byteCount)
- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad, then 32 16 64
- a9993e364706816aba3e25717850c26c9cd0d89d, then 20 16 64
- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad, then 20 20 32
Predict first
A message is authenticated with an HMAC and then altered by one letter. What does verification say?
import CryptoKit
import Foundation
let key = SymmetricKey(size: .bits256)
let mac = HMAC<SHA256>.authenticationCode(for: Data("msg".utf8), using: key)
print(HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: Data("msg".utf8), using: key), HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: Data("msG".utf8), using: key))
- true false
- true true
- false false
Predict first
A sealed AES-GCM box is opened normally, with one byte changed, and with the wrong key. What happens?
import CryptoKit
import Foundation
let key = SymmetricKey(size: .bits256)
let box = try AES.GCM.seal(Data("secret".utf8), using: key)
print(String(data: try AES.GCM.open(box, using: key), encoding: .utf8)!)
var bytes = [UInt8](box.combined!)
bytes[bytes.count - 1] ^= 1
do { _ = try AES.GCM.open(try AES.GCM.SealedBox(combined: Data(bytes)), using: key); print("opened") } catch { print("tampered") }
do { _ = try AES.GCM.open(box, using: SymmetricKey(size: .bits256)); print("opened") } catch { print("wrong key") }
- secret, tampered, wrong key
- secret, opened, opened
- secret, tampered, opened
Mental model. A hash is a fingerprint of a document. A MAC is a fingerprint stamped with a secret seal only you and a friend own. Encryption is a locked box with a tamper seal. A signature is your notarised signature that anyone can check but only you can make.
Four tools, four questions. A hash (SHA256.hash(data:)) maps any input to a fixed-size digest (32 bytes for SHA-256): the same input always gives the same output, a one-bit change scrambles it, and it cannot be reversed. It answers “is this the same data?” (integrity checks, content addressing, deduplication) but has no secret, so anyone can recompute it, and it is the wrong tool for protecting passwords on its own. A MAC (HMAC<SHA256>) is a hash keyed with a shared secret: it answers “was this produced by someone holding the key, and is it unmodified?” (signing API requests, cookies, webhooks). Encryption answers “who can read this?”. Use authenticated encryption (AES-GCM or ChaCha20-Poly1305): it encrypts and attaches a tag, so tampering is detected when you open it (AES.GCM.open throws on a changed byte or a wrong key). Plain unauthenticated modes (ECB, CBC alone) are a bug waiting to happen. A digital signature (P256.Signing, Curve25519.Signing) uses a private key to sign and the public key to verify: it answers “who vouches for this?” without sharing a secret (verifying app updates, signed tokens, receipts).
CryptoKit (iOS 13+) is the right default: a small, type-safe Swift API over Apple’s audited implementations, hardware-accelerated, with safe defaults (random nonces, constant-time comparisons in isValidAuthenticationCode), keys as SymmetricKey values that are zeroed on release, and Secure Enclave support for P-256 keys. CommonCrypto is the older C API (CCCrypt, CC_SHA256): lower-level, easy to misuse (you choose modes, padding, IVs and lengths yourself), and not Swift-friendly. Use it only for legacy interoperability, and for algorithms CryptoKit omits (MD5 and SHA-1 live in Insecure for compatibility only, never for security). For anything beyond the platform (a custom protocol, post-quantum, TLS-level features) use well-reviewed libraries, not hand-written code. The cardinal rule: never design your own cryptographic scheme or algorithm.
The digest of "abc" is the well-known value starting ba7816bf. SHA-256 is 32 bytes, MD5 16 and SHA-512 64.
The same data verifies, and any change makes the MAC invalid. The check is constant-time.
Authenticated encryption rejects both a modified box and the wrong key instead of returning garbage.
Common traps
- Using a plain hash to “protect” a password or to authenticate a message. Anyone can recompute it; use a password KDF, or a MAC with a secret key.
- Encrypting without authentication (CBC or ECB alone). Tampering goes unnoticed; use AES-GCM or ChaCha20-Poly1305.
They might follow up with
- Which verifies without a shared secret? A signature: verify with the public key.
- Why does
AES.GCM.openthrow? The authentication tag did not match: the data changed or the key is wrong.
Related questions
- 14.1.2 How do you use cryptography safely: key management, nonces, key derivation, password storage and comparison?
- 14.2.1 How do Face ID and Touch ID with LocalAuthentication work, and what does the Secure Enclave add?
- 13.1.2 How does the Keychain work, how do you add, read and update an item, and what do accessibility classes and…
How do you use cryptography safely: key management, nonces, key… →