Skill v1.0.1
currentLLM-judged scan100/100+3 new
version: "1.0.1" name: cryptokit description: "Use Apple CryptoKit for Swift cryptographic primitives. Use when hashing with SHA-2 or SHA-3, generating HMACs, encrypting with AES-GCM or ChaChaPoly, signing with P256/P384/P521/Curve25519 or ML-DSA keys, performing ECDH, HPKE, ML-KEM, or X-Wing key exchange, using Secure Enclave CryptoKit keys, or migrating CommonCrypto code to CryptoKit."
CryptoKit
Apple CryptoKit provides a Swift-native API for cryptographic operations: hashing, message authentication, symmetric encryption, public-key signing, key agreement, HPKE, quantum-secure key encapsulation/signing, and Secure Enclave-backed keys. Most core primitives are available on iOS 13+; check availability for HPKE (iOS 17+) and SHA-3 / post-quantum APIs (iOS 26+). Prefer CryptoKit over CommonCrypto or raw Security framework APIs for new cryptographic primitive code targeting Swift 6.3+.
Contents
- Hashing
- HMAC
- Symmetric Encryption
- Public-Key Signing
- Key Agreement
- HPKE
- Post-Quantum CryptoKit
- Secure Enclave
- Common Mistakes
- Review Checklist
- References
Hashing
CryptoKit provides SHA256, SHA384, and SHA512 hash functions on iOS 13+. SHA3_256, SHA3_384, and SHA3_512 are available on iOS 26+. All conform to the HashFunction protocol.
One-shot hashing
import CryptoKitlet data = Data("Hello, world!".utf8)let digest = SHA256.hash(data: data)let hex = digest.compactMap { String(format: "%02x", $0) }.joined()
SHA384 and SHA512 work identically -- substitute the type name.
SHA-3 availability
Use SHA-3 only behind an availability check unless the deployment target is iOS 26+:
if #available(iOS 26.0, *) {let digest = SHA3_256.hash(data: data)}
Incremental hashing
For large data or streaming input, hash incrementally:
var hasher = SHA256()hasher.update(data: chunk1)hasher.update(data: chunk2)let digest = hasher.finalize()
Digest comparison
Compare CryptoKit digest values directly. Do not convert digests to strings or arrays for security-sensitive equality checks.
let expected = SHA256.hash(data: reference)let actual = SHA256.hash(data: received)if expected == actual {// Data integrity verified}
HMAC
HMAC provides message authentication using a symmetric key and a hash function.
Computing an authentication code
let key = SymmetricKey(size: .bits256)let data = Data("message".utf8)let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
Verifying an authentication code
let isValid = HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: data, using: key)
This uses constant-time comparison internally.
Incremental HMAC
var hmac = HMAC<SHA256>(key: key)hmac.update(data: chunk1)hmac.update(data: chunk2)let mac = hmac.finalize()
Symmetric Encryption
CryptoKit provides two authenticated encryption ciphers: AES-GCM and ChaChaPoly. Both produce a sealed box containing the nonce, ciphertext, and authentication tag.
AES-GCM
The default choice for symmetric encryption. Hardware-accelerated on Apple silicon.
let key = SymmetricKey(size: .bits256)let plaintext = Data("Secret message".utf8)// Encryptlet sealedBox = try AES.GCM.seal(plaintext, using: key)let ciphertext = sealedBox.combined! // nonce + ciphertext + tag// Decryptlet box = try AES.GCM.SealedBox(combined: ciphertext)let decrypted = try AES.GCM.open(box, using: key)
ChaChaPoly
Use ChaChaPoly when AES hardware acceleration is unavailable or when interoperating with protocols that require ChaCha20-Poly1305 (e.g., TLS, WireGuard).
let sealedBox = try ChaChaPoly.seal(plaintext, using: key)let combined = sealedBox.combined // Always non-optional for ChaChaPolylet box = try ChaChaPoly.SealedBox(combined: combined)let decrypted = try ChaChaPoly.open(box, using: key)
Authenticated data
Both ciphers support additional authenticated data (AAD). The AAD is authenticated but not encrypted -- useful for metadata that must remain in the clear but be tamper-proof.
let header = Data("v1".utf8)let sealedBox = try AES.GCM.seal(plaintext, using: key, authenticating: header)let decrypted = try AES.GCM.open(sealedBox, using: key, authenticating: header)
Use .bits256 as the default SymmetricKey size for AES-256-GCM or ChaChaPoly. To create a key from existing data:
let key = SymmetricKey(data: existingKeyData)
Public-Key Signing
CryptoKit supports ECDSA signing with NIST curves and Ed25519 via Curve25519.
NIST curves: P256, P384, P521
let signingKey = P256.Signing.PrivateKey()let publicKey = signingKey.publicKey// Signlet signature = try signingKey.signature(for: data)// Verifylet isValid = publicKey.isValidSignature(signature, for: data)
P384 and P521 use the same API -- substitute the curve name.
NIST keys support DER, PEM, X9.63, and raw representations. See references/cryptokit-patterns.md for serialization examples.
Curve25519 / Ed25519
let signingKey = Curve25519.Signing.PrivateKey()let publicKey = signingKey.publicKey// Signlet signature = try signingKey.signature(for: data)// Verifylet isValid = publicKey.isValidSignature(signature, for: data)
Curve25519 keys use rawRepresentation only (no DER/PEM/X9.63).
Choosing a curve
| Curve | Signature Scheme | Key Size | Typical Use | |
|---|---|---|---|---|
| P256 | ECDSA | 256-bit | General purpose; Secure Enclave support | |
| P384 | ECDSA | 384-bit | Higher security requirements | |
| P521 | ECDSA | 521-bit | Maximum NIST security level | |
| Curve25519 | Ed25519 | 256-bit | Fast; simple API; no Secure Enclave |
Use P256 by default. Use Curve25519 when interoperating with Ed25519-based protocols.
Key Agreement
Key agreement lets two parties derive a shared symmetric key from their public/private key pairs using ECDH.
ECDH with P256
// Alicelet aliceKey = P256.KeyAgreement.PrivateKey()// Boblet bobKey = P256.KeyAgreement.PrivateKey()// Alice computes shared secretlet sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(with: bobKey.publicKey)// Derive a symmetric key using HKDFlet symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self,salt: Data("salt".utf8),sharedInfo: Data("my-app-v1".utf8),outputByteCount: 32)
Bob computes the same sharedSecret using his private key and Alice's public key. Both derive the same symmetricKey.
ECDH with Curve25519
let aliceKey = Curve25519.KeyAgreement.PrivateKey()let bobKey = Curve25519.KeyAgreement.PrivateKey()let sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(with: bobKey.publicKey)let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self,salt: Data(),sharedInfo: Data("context".utf8),outputByteCount: 32)
Key derivation functions
SharedSecret is not directly usable as a SymmetricKey. Always derive a key using one of:
| Method | Standard | Use | |
|---|---|---|---|
hkdfDerivedSymmetricKey | HKDF (RFC 5869) | Recommended default | |
x963DerivedSymmetricKey | ANSI X9.63 | Interop with X9.63 systems |
Always provide a non-empty sharedInfo string to bind the derived key to a specific protocol context.
HPKE
HPKE is available on iOS 17+ for public-key encryption workflows. Prefer it over hand-rolled ECDH + HKDF + AEAD protocols when encrypting to a recipient public key.
let info = Data("my-protocol-v1".utf8)let recipientKey = Curve25519.KeyAgreement.PrivateKey()var sender = try HPKE.Sender(recipientKey: recipientKey.publicKey,ciphersuite: .Curve25519_SHA256_ChachaPoly,info: info)let encapsulatedKey = sender.encapsulatedKeylet ciphertext = try sender.seal(plaintext,authenticating: Data("metadata".utf8))var recipient = try HPKE.Recipient(privateKey: recipientKey,ciphersuite: .Curve25519_SHA256_ChachaPoly,info: info,encapsulatedKey: encapsulatedKey)
HPKE.Sender and HPKE.Recipient are stateful; keep them as var, send encapsulatedKey alongside the ciphertext, and open messages in the same order they were sealed. See references/cryptokit-patterns.md for ciphersuite selection and post-quantum HPKE.
Post-Quantum CryptoKit
iOS 26+ adds quantum-secure APIs:
- Key encapsulation:
MLKEM768,MLKEM1024 - Hybrid HPKE:
XWingMLKEM768X25519with.XWingMLKEM768X25519_SHA256_AES_GCM_256 - Digital signatures:
MLDSA65,MLDSA87 - Secure Enclave variants:
SecureEnclave.MLKEM768,SecureEnclave.MLKEM1024,
SecureEnclave.MLDSA65, SecureEnclave.MLDSA87
Use hybrid mechanisms for migration when both classical and quantum-secure resistance matter. Account for much larger public keys, ciphertexts, and signatures than P256 or Curve25519.
Secure Enclave
The Secure Enclave provides hardware-backed key storage. Private keys never leave the hardware. For classical elliptic-curve CryptoKit, Secure Enclave supports P256 signing and key agreement. On iOS 26+ supported hardware, CryptoKit also exposes Secure Enclave ML-KEM key encapsulation and ML-DSA signing types.
Availability check
guard SecureEnclave.isAvailable else {// Fall back to software keysreturn}
Creating a Secure Enclave signing key
let privateKey = try SecureEnclave.P256.Signing.PrivateKey()let publicKey = privateKey.publicKey // Standard P256.Signing.PublicKeylet signature = try privateKey.signature(for: data)let isValid = publicKey.isValidSignature(signature, for: data)
Access control
Use SecAccessControl with .privateKeyUsage when the key requires biometric or passcode-gated use. Keep detailed Keychain policy decisions in the swift-security domain.
Persisting Secure Enclave keys
The dataRepresentation is an encrypted blob that only the same device's Secure Enclave can restore. Store it in the Keychain.
// Exportlet blob = privateKey.dataRepresentation// Restorelet restored = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: blob)
Secure Enclave key agreement
let seKey = try SecureEnclave.P256.KeyAgreement.PrivateKey()let peerPublicKey: P256.KeyAgreement.PublicKey = // from peerlet sharedSecret = try seKey.sharedSecretFromKeyAgreement(with: peerPublicKey)
Common Mistakes
1. Using the shared secret directly as a key
// DON'Tlet badKey = sharedSecret.withUnsafeBytes { bytes inSymmetricKey(data: Data(bytes))}// DO -- derive with HKDFlet goodKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self,salt: salt,sharedInfo: info,outputByteCount: 32)
2. Reusing nonces
// DON'T -- hardcoded noncelet nonce = try AES.GCM.Nonce(data: Data(repeating: 0, count: 12))let box = try AES.GCM.seal(data, using: key, nonce: nonce)// DO -- let CryptoKit generate a random nonce (default behavior)let box = try AES.GCM.seal(data, using: key)
3. Ignoring authentication tag verification
// DON'T -- manually strip tag and decrypt// DO -- always use AES.GCM.open() or ChaChaPoly.open()// which verifies the tag automatically
4. Using Insecure hashes for security
// DON'T -- MD5/SHA1 for integrity or securityimport CryptoKitlet bad = Insecure.MD5.hash(data: data)// DO -- use SHA256 or strongerlet good = SHA256.hash(data: data)
Insecure.MD5 and Insecure.SHA1 exist only for legacy compatibility (checksum verification, protocol interop). Never use them for new security-sensitive operations.
5. Storing symmetric keys in UserDefaults
// DON'TUserDefaults.standard.set(rawKeyData, forKey: "encryptionKey")// DO -- store in Keychain// See references/cryptokit-patterns.md for Keychain storage patterns
6. Not checking Secure Enclave availability
// DON'T -- crash on simulator or unsupported hardwarelet key = try SecureEnclave.P256.Signing.PrivateKey()// DOguard SecureEnclave.isAvailable else { /* fallback */ }let key = try SecureEnclave.P256.Signing.PrivateKey()
Review Checklist
- [ ] Using CryptoKit, not CommonCrypto or raw Security framework
- [ ] SHA256+ for hashing; no MD5/SHA1 for security purposes
- [ ] HMAC verification uses
isValidAuthenticationCode(constant-time) - [ ] AES-GCM or ChaChaPoly for symmetric encryption; 256-bit keys
- [ ] Nonces are random (default) -- not hardcoded or reused
- [ ] Authenticated data (AAD) used where metadata needs integrity
- [ ] SharedSecret derived via HKDF, not used directly
- [ ] sharedInfo parameter is non-empty and context-specific
- [ ] HPKE used instead of custom ECDH+HKDF+AEAD for recipient public-key encryption on iOS 17+
- [ ] SHA-3 and post-quantum APIs guarded with iOS 26+ availability
- [ ] Secure Enclave availability checked before use
- [ ] Secure Enclave key
dataRepresentationstored in Keychain - [ ] Private keys not logged, printed, or serialized unnecessarily
- [ ] Symmetric keys stored in Keychain, not UserDefaults or files
- [ ] Encryption export compliance considered (
ITSAppUsesNonExemptEncryption)
References
- Extended patterns (key serialization, Insecure module, Keychain integration, AES key wrapping, HPKE): references/cryptokit-patterns.md
- Apple documentation: CryptoKit
- Apple documentation: HPKE
- Apple documentation: Quantum-secure workflows
- Apple sample: Performing Common Cryptographic Operations
- Apple sample: Storing CryptoKit Keys in the Keychain