How do you use cryptography safely: key management, nonces, key derivation, password storage and comparison?
Predict first
Sealing the same plaintext twice with the same key: are the ciphertexts equal? What are the sizes of the parts?
import CryptoKit
import Foundation
let key = SymmetricKey(size: .bits256)
let a = try AES.GCM.seal(Data("secret".utf8), using: key)
let b = try AES.GCM.seal(Data("secret".utf8), using: key)
print(a.ciphertext == b.ciphertext, Data(a.nonce) == Data(b.nonce))
print(a.nonce.withUnsafeBytes { $0.count }, a.tag.count, a.ciphertext.count, a.combined!.count)
- false false, then 12 16 6 34
- true true, then 12 16 6 34
- false false, then 16 12 6 34
Predict first
Two parties agree on a shared secret and derive a key with the same salt. Do they get the same key? And a signature checked against a different message:
import CryptoKit
import Foundation
let a = Curve25519.KeyAgreement.PrivateKey(), b = Curve25519.KeyAgreement.PrivateKey()
let s1 = try a.sharedSecretFromKeyAgreement(with: b.publicKey)
let s2 = try b.sharedSecretFromKeyAgreement(with: a.publicKey)
let salt = Data("s".utf8)
let k1 = s1.hkdfDerivedSymmetricKey(using: SHA256.self, salt: salt, sharedInfo: Data(), outputByteCount: 32)
let k2 = s2.hkdfDerivedSymmetricKey(using: SHA256.self, salt: salt, sharedInfo: Data(), outputByteCount: 32)
print(k1 == k2)
let priv = P256.Signing.PrivateKey()
let sig = try priv.signature(for: Data("m".utf8))
print(priv.publicKey.isValidSignature(sig, for: Data("m".utf8)), priv.publicKey.isValidSignature(sig, for: Data("n".utf8)))
- true, then true false
- false, then true false
- true, then true true
Mental model. A nonce is a fresh envelope seal number used once per letter. Re-using a seal number lets a clever attacker forge letters. Keys are like house keys: made by a locksmith (random generator), kept in a safe (Keychain), and never copied onto a sticky note on the door (the binary).
Most crypto failures are not broken algorithms but misused ones. Keys: generate them with the system’s random source (SymmetricKey(size: .bits256)), never derive them from a hard-coded string, and store them in the Keychain (or a Secure Enclave key reference), not in UserDefaults or the binary. A key and the data it protects should not live in the same place. Decide the key’s lifetime and plan rotation (a key version stored with the ciphertext lets you re-encrypt). Nonces: AES-GCM needs a unique nonce per message under one key; reusing one destroys confidentiality and authenticity. CryptoKit’s seal picks a fresh random 96-bit nonce for you, which is why sealing the same plaintext twice with the same key gives different ciphertexts, and the nonce (12 bytes) and tag (16 bytes) travel with the ciphertext in combined (12 + payload + 16 bytes). Do not supply your own nonce unless you must, and never a counter that can repeat after a restart.
Derive, do not reuse: use HKDF (hkdfDerivedSymmetricKey) to turn a shared secret (from key agreement, such as Curve25519 or P-256 ECDH) into one purpose-specific key per use; both sides computing the same derivation get the same key. For passwords, never store or hash them with plain SHA; a server should use a slow, salted password hash (Argon2id, scrypt or bcrypt, PBKDF2 at minimum), and a client app should usually not store the password at all, only a token. When you must derive a key from a user passphrase on device, use a proper KDF with a random salt and a high work factor (CCKeyDerivationPBKDF or a vetted library). Comparison: compare secrets and MACs with the library’s constant-time function (isValidAuthenticationCode), not ==, which can leak through timing. Authenticate before you trust: verify the tag or signature before parsing the data. Do not roll your own protocol: for transport, use TLS; for end-to-end messaging use a reviewed design. Finally, treat anything on the device as attacker-readable if the device is compromised: client-side encryption protects data at rest and against other apps, not against the user or a jailbroken device with your process hooked.
A random nonce makes each seal different. A 12-byte nonce, a 6-byte ciphertext and a 16-byte tag make a 34-byte combined box.
ECDH gives both sides the same secret, so HKDF with the same inputs yields the same key. A signature only verifies for the message that was signed.
Common traps
- Hard-coding a key or deriving it from a constant string. Anyone with the binary has the key; generate it and keep it in the Keychain.
- Reusing an AES-GCM nonce. It breaks both secrecy and integrity; let the library generate a random one per message.
They might follow up with
- Why is the same plaintext encrypted differently each time? A fresh random nonce is used for every seal.
- How do two parties get a shared key without sending it? Key agreement (ECDH), then HKDF to derive the working key.
Related questions
- 14.1.1 What is the difference between hashing, a MAC, encryption and a digital signature, and when do you use…
- 14.4.2 Why are secrets in the app binary not secret, and what do you do instead?
- 13.1.2 How does the Keychain work, how do you add, read and update an item, and what do accessibility classes and…
← What is the difference between hashing, a MAC, encryption and a…