
Cryptokit
- 2.1k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
cryptokit is an agent skill that 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 w.
About
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. --- 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+).
- [Symmetric Encryption](#symmetric-encryption)
- [Public-Key Signing](#public-key-signing)
- [Key Agreement](#key-agreement)
- [Post-Quantum CryptoKit](#post-quantum-cryptokit)
- [Secure Enclave](#secure-enclave)
Cryptokit by the numbers
- 2,096 all-time installs (skills.sh)
- +113 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #278 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
cryptokit capabilities & compatibility
- Capabilities
- [symmetric encryption](#symmetric encryption) · [public key signing](#public key signing) · [key agreement](#key agreement) · [post quantum cryptokit](#post quantum cryptokit · [secure enclave](#secure enclave)
- Use cases
- documentation
What cryptokit says it does
--- name: cryptokit description: "Use Apple CryptoKit for Swift cryptographic primitives.
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+.
SHA3_256, SHA3_384, and SHA3_512 are available on iOS 26+.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill cryptokitAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.1k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
What problem does cryptokit solve for developers using this skill?
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
Who is it for?
Developers who need cryptokit patterns described in the cached skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill's documented scope.
When should I use this skill?
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
What you get
Actionable workflows and conventions from SKILL.md for cryptokit.
- Swift CryptoKit implementation
- HPKE encryption code
- assertion-passing crypto patterns
By the numbers
- Includes structured evals starting with hpke-recipient-encryption id 1
- Recommends HPKE on iOS 17+ over manual ECDH+HKDF+AEAD protocols
Files
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 CryptoKit
let 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)
// Encrypt
let sealedBox = try AES.GCM.seal(plaintext, using: key)
let ciphertext = sealedBox.combined! // nonce + ciphertext + tag
// Decrypt
let 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 ChaChaPoly
let 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
// Sign
let signature = try signingKey.signature(for: data)
// Verify
let 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
// Sign
let signature = try signingKey.signature(for: data)
// Verify
let 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
// Alice
let aliceKey = P256.KeyAgreement.PrivateKey()
// Bob
let bobKey = P256.KeyAgreement.PrivateKey()
// Alice computes shared secret
let sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(
with: bobKey.publicKey
)
// Derive a symmetric key using HKDF
let 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.encapsulatedKey
let 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 keys
return
}Creating a Secure Enclave signing key
let privateKey = try SecureEnclave.P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey // Standard P256.Signing.PublicKey
let 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.
// Export
let blob = privateKey.dataRepresentation
// Restore
let 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 peer
let sharedSecret = try seKey.sharedSecretFromKeyAgreement(
with: peerPublicKey
)Common Mistakes
1. Using the shared secret directly as a key
// DON'T
let badKey = sharedSecret.withUnsafeBytes { bytes in
SymmetricKey(data: Data(bytes))
}
// DO -- derive with HKDF
let goodKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: salt,
sharedInfo: info,
outputByteCount: 32
)2. Reusing nonces
// DON'T -- hardcoded nonce
let 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 automatically4. Using Insecure hashes for security
// DON'T -- MD5/SHA1 for integrity or security
import CryptoKit
let bad = Insecure.MD5.hash(data: data)
// DO -- use SHA256 or stronger
let 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'T
UserDefaults.standard.set(rawKeyData, forKey: "encryptionKey")
// DO -- store in Keychain
// See references/cryptokit-patterns.md for Keychain storage patterns6. Not checking Secure Enclave availability
// DON'T -- crash on simulator or unsupported hardware
let key = try SecureEnclave.P256.Signing.PrivateKey()
// DO
guard 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
{
"skill_name": "cryptokit",
"evals": [
{
"id": 1,
"name": "hpke-recipient-encryption",
"prompt": "I need to encrypt a payload in an iOS app for a recipient public key. Please outline the CryptoKit approach and include the Swift details that prevent the common ECDH/HKDF/AES-GCM mistakes.",
"expected_output": "Recommends HPKE on iOS 17+, shows stateful Sender/Recipient usage, sends encapsulatedKey with ciphertext, uses AAD correctly, and avoids a manual ECDH+HKDF+AEAD protocol unless HPKE is unavailable.",
"files": [],
"assertions": [
"Recommends HPKE instead of hand-rolled ECDH+HKDF+AEAD for recipient public-key encryption when iOS 17+ is available.",
"Shows or states that HPKE.Sender and HPKE.Recipient are stateful and must be var when sealing or opening.",
"States that sender.encapsulatedKey must be transmitted alongside the ciphertext.",
"Mentions AAD/metadata authentication and same-order open semantics for multi-message HPKE."
]
},
{
"id": 2,
"name": "post-quantum-workflow",
"prompt": "We are targeting iOS 26 and want quantum-secure CryptoKit for a document sharing flow. What APIs should we use for key exchange and signatures, and what availability or Secure Enclave caveats matter?",
"expected_output": "Covers X-Wing HPKE, ML-KEM, ML-DSA, iOS 26 availability, Secure Enclave ML-KEM/ML-DSA variants on supported hardware, and key/signature size tradeoffs.",
"files": [],
"assertions": [
"Names XWingMLKEM768X25519 or the .XWingMLKEM768X25519_SHA256_AES_GCM_256 HPKE ciphersuite for hybrid quantum-secure public-key encryption.",
"Names MLKEM768 or MLKEM1024 for key encapsulation and MLDSA65 or MLDSA87 for signatures.",
"States that SHA-3/post-quantum CryptoKit APIs require iOS 26+ availability checks unless the deployment target is iOS 26+.",
"Correctly distinguishes classical Secure Enclave P256 support from iOS 26 SecureEnclave.MLKEM and SecureEnclave.MLDSA variants."
]
},
{
"id": 3,
"name": "storage-boundary",
"prompt": "Please review this design: generate a CryptoKit SymmetricKey, save its bytes in UserDefaults, then use AES-GCM for local file encryption. Should the CryptoKit skill own the whole fix?",
"expected_output": "Flags UserDefaults key storage as wrong, keeps CryptoKit focused on AES-GCM/nonce/AAD/key material handling, and routes durable secret storage/access-control policy to Keychain or the swift-security domain.",
"files": [],
"assertions": [
"Rejects storing symmetric key material in UserDefaults or normal files.",
"Keeps CryptoKit guidance focused on AES-GCM, nonce reuse avoidance, AAD, and key material handling.",
"Routes durable key storage, Keychain queries, biometric/passcode access control, and broader credential lifecycle to Keychain/swift-security guidance.",
"Mentions encryption export compliance or App Store encryption declaration when app-level encryption is relevant."
]
}
]
}
CryptoKit Extended Patterns
Advanced patterns, key serialization, Keychain integration, legacy interop, and additional CryptoKit features beyond the core SKILL.md.
Contents
- Key Serialization
- Keychain Storage
- AES Key Wrapping
- HKDF Key Derivation
- HPKE (Hybrid Public Key Encryption)
- Post-Quantum APIs
- Insecure Module
- SealedBox Anatomy
- Signing with Digest
- Encryption Export Compliance
- Performance Considerations
- CommonCrypto Migration
Key Serialization
NIST curve keys (P256, P384, P521) support multiple serialization formats. Curve25519 keys use raw representation only.
NIST key export and import
let privateKey = P256.Signing.PrivateKey()
// DER (binary, compact)
let der = privateKey.derRepresentation
let fromDER = try P256.Signing.PrivateKey(derRepresentation: der)
// PEM (text, base64-encoded DER with header/footer)
let pem = privateKey.pemRepresentation
let fromPEM = try P256.Signing.PrivateKey(pemRepresentation: pem)
// X9.63 (used by SecKey / Keychain interop)
let x963 = privateKey.x963Representation
let fromX963 = try P256.Signing.PrivateKey(x963Representation: x963)
// Raw (scalar bytes only)
let raw = privateKey.rawRepresentation
let fromRaw = try P256.Signing.PrivateKey(rawRepresentation: raw)Public key serialization
Public keys support the same formats plus compact and compressed representations:
let publicKey = privateKey.publicKey
let der = publicKey.derRepresentation
let pem = publicKey.pemRepresentation
let x963 = publicKey.x963Representation
let raw = publicKey.rawRepresentation
let compact = publicKey.compactRepresentation // Optional; may be nil
let compressed = publicKey.compressedRepresentationCurve25519 key serialization
let key = Curve25519.Signing.PrivateKey()
// Only raw representation available
let raw = key.rawRepresentation
let restored = try Curve25519.Signing.PrivateKey(rawRepresentation: raw)
let pubRaw = key.publicKey.rawRepresentation
let restoredPub = try Curve25519.Signing.PublicKey(rawRepresentation: pubRaw)ECDSA signature serialization
let signature = try privateKey.signature(for: data)
// DER-encoded (standard interop format)
let derSig = signature.derRepresentation
// Raw (r || s concatenation)
let rawSig = signature.rawRepresentation
// Restore
let fromDER = try P256.Signing.ECDSASignature(derRepresentation: derSig)
let fromRaw = try P256.Signing.ECDSASignature(rawRepresentation: rawSig)Use DER for interoperability with non-Apple systems. Use raw for compact storage where both sides are CryptoKit.
Keychain Storage
CryptoKit key types divide into two storage strategies based on whether they have a SecKey-compatible representation.
NIST keys via SecKey
P256, P384, and P521 private keys can be stored as native Keychain elliptic-curve keys using their X9.63 representation.
protocol SecKeyConvertible: CustomStringConvertible {
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
var x963Representation: Data { get }
}
extension P256.Signing.PrivateKey: SecKeyConvertible {}
extension P256.KeyAgreement.PrivateKey: SecKeyConvertible {}
extension P384.Signing.PrivateKey: SecKeyConvertible {}
extension P384.KeyAgreement.PrivateKey: SecKeyConvertible {}
extension P521.Signing.PrivateKey: SecKeyConvertible {}
extension P521.KeyAgreement.PrivateKey: SecKeyConvertible {}Store:
func storeKey<T: SecKeyConvertible>(_ key: T, label: String) throws {
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate
]
guard let secKey = SecKeyCreateWithData(
key.x963Representation as CFData,
attributes as CFDictionary,
nil
) else {
throw KeyStoreError.unableToCreateSecKey
}
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationLabel as String: label,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecUseDataProtectionKeychain as String: true,
kSecValueRef as String: secKey
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeyStoreError.saveFailed(status)
}
}Retrieve:
func readKey<T: SecKeyConvertible>(label: String) throws -> T? {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationLabel as String: label,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecUseDataProtectionKeychain as String: true,
kSecReturnRef as String: true
]
var item: CFTypeRef?
switch SecItemCopyMatching(query as CFDictionary, &item) {
case errSecSuccess:
let secKey = item as! SecKey
var error: Unmanaged<CFError>?
guard let data = SecKeyCopyExternalRepresentation(secKey, &error) as Data? else {
throw KeyStoreError.exportFailed
}
return try T(x963Representation: data)
case errSecItemNotFound:
return nil
case let status:
throw KeyStoreError.readFailed(status)
}
}Non-NIST keys via generic password
Curve25519 keys and SymmetricKey lack X9.63 representations. Store them as generic password Keychain items using their raw data.
protocol GenericPasswordConvertible: CustomStringConvertible {
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes
var genericKeyRepresentation: SymmetricKey { get }
}
extension Curve25519.Signing.PrivateKey: GenericPasswordConvertible {
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
try self.init(rawRepresentation: data)
}
var genericKeyRepresentation: SymmetricKey {
rawRepresentation.withUnsafeBytes { SymmetricKey(data: $0) }
}
}
extension Curve25519.KeyAgreement.PrivateKey: GenericPasswordConvertible {
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
try self.init(rawRepresentation: data)
}
var genericKeyRepresentation: SymmetricKey {
rawRepresentation.withUnsafeBytes { SymmetricKey(data: $0) }
}
}
extension SymmetricKey: GenericPasswordConvertible {
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
self.init(data: data)
}
var genericKeyRepresentation: SymmetricKey { self }
}Store:
func storeKey<T: GenericPasswordConvertible>(
_ key: T, account: String
) throws {
try key.genericKeyRepresentation.withUnsafeBytes { keyBytes in
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecUseDataProtectionKeychain as String: true,
kSecValueData as String: Data(keyBytes)
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeyStoreError.saveFailed(status)
}
}
}Secure Enclave keys in Keychain
Secure Enclave keys export an encrypted dataRepresentation that only the same device's Secure Enclave can restore. Store this blob as a generic password:
extension SecureEnclave.P256.Signing.PrivateKey: GenericPasswordConvertible {
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
}
var genericKeyRepresentation: SymmetricKey {
SymmetricKey(data: dataRepresentation)
}
}AES Key Wrapping
CryptoKit supports AES Key Wrap (RFC 3394) for securely wrapping one symmetric key with another.
let kek = SymmetricKey(size: .bits256) // Key Encryption Key
let dek = SymmetricKey(size: .bits256) // Data Encryption Key
// Wrap
let wrappedData = try AES.KeyWrap.wrap(dek, using: kek)
// Unwrap
let unwrapped = try AES.KeyWrap.unwrap(wrappedData, using: kek)Use key wrapping when transmitting or storing keys encrypted under a master key.
HKDF Key Derivation
HKDF (RFC 5869) derives cryptographic keys from input key material. Available as a standalone operation outside of SharedSecret.
let inputKey = SymmetricKey(size: .bits256)
// Derive with salt and info
let derived = HKDF<SHA256>.deriveKey(
inputKeyMaterial: inputKey,
salt: Data("salt".utf8),
info: Data("my-app-encryption-v1".utf8),
outputByteCount: 32
)Extract-then-expand (two-step)
For protocols that need explicit control:
// Extract: produce a pseudorandom key
let prk = HKDF<SHA256>.extract(
inputKeyMaterial: inputKey,
salt: Data("salt".utf8)
)
// Expand: derive output key material
let okm = HKDF<SHA256>.expand(
pseudoRandomKey: prk,
info: Data("context".utf8),
outputByteCount: 32
)HPKE (Hybrid Public Key Encryption)
HPKE (RFC 9180) combines key encapsulation with authenticated encryption for public-key encryption workflows. It is available on iOS 17+; the X-Wing post-quantum hybrid ciphersuite requires iOS 26+.
Sending an encrypted message
let recipientKey = P256.KeyAgreement.PrivateKey()
var sender = try HPKE.Sender(
recipientKey: recipientKey.publicKey,
ciphersuite: .P256_SHA256_AES_GCM_256,
info: Data("my-protocol-v1".utf8)
)
let ciphertext = try sender.seal(Data("secret message".utf8))
let encapsulatedKey = sender.encapsulatedKey
// Send ciphertext + encapsulatedKey to recipientReceiving
var recipient = try HPKE.Recipient(
privateKey: recipientKey,
ciphersuite: .P256_SHA256_AES_GCM_256,
info: Data("my-protocol-v1".utf8),
encapsulatedKey: encapsulatedKey
)
let plaintext = try recipient.open(ciphertext)Available ciphersuites
| Ciphersuite | KEM | KDF | AEAD | Availability |
|---|---|---|---|---|
.P256_SHA256_AES_GCM_256 | P256 | HKDF-SHA256 | AES-GCM-256 | iOS 17+ |
.P384_SHA384_AES_GCM_256 | P384 | HKDF-SHA384 | AES-GCM-256 | iOS 17+ |
.P521_SHA512_AES_GCM_256 | P521 | HKDF-SHA512 | AES-GCM-256 | iOS 17+ |
.Curve25519_SHA256_ChachaPoly | X25519 | HKDF-SHA256 | ChaCha20Poly1305 | iOS 17+ |
.XWingMLKEM768X25519_SHA256_AES_GCM_256 | X-Wing hybrid | HKDF-SHA256 | AES-GCM-256 | iOS 26+ |
Post-Quantum APIs
iOS 26+ adds ML-KEM key encapsulation, ML-DSA signatures, and the X-Wing hybrid HPKE KEM. Guard these APIs with availability checks unless the deployment target is iOS 26+.
ML-KEM encapsulation
if #available(iOS 26.0, *) {
let privateKey = try MLKEM768.PrivateKey()
let result = try privateKey.publicKey.encapsulate()
let sharedKey = result.sharedSecret
let encapsulated = result.encapsulated
let recovered = try privateKey.decapsulate(encapsulated)
}encapsulated is what the sender transmits. sharedSecret and the decapsulated result are SymmetricKey values.
ML-DSA signatures
if #available(iOS 26.0, *) {
let privateKey = try MLDSA65.PrivateKey()
let signature = try privateKey.signature(for: message)
let isValid = privateKey.publicKey.isValidSignature(signature, for: message)
}Secure Enclave variants exist for SecureEnclave.MLKEM768, SecureEnclave.MLKEM1024, SecureEnclave.MLDSA65, and SecureEnclave.MLDSA87 on supported hardware.
Sources: CryptoKit, HPKE, and quantum-secure workflows.
Insecure Module
The Insecure enum provides MD5 and SHA1 for legacy compatibility ONLY.
import CryptoKit
// Legacy checksum verification
let md5 = Insecure.MD5.hash(data: fileData)
let sha1 = Insecure.SHA1.hash(data: fileData)Valid uses:
- Verifying checksums from legacy systems
- Computing ETags or content hashes for caching
- Protocol interop requiring MD5/SHA1
Invalid uses:
- Password hashing
- Data integrity for security
- Digital signatures
- HMAC for authentication
The Insecure namespace makes insecure usage explicit at the call site.
SealedBox Anatomy
Both AES-GCM and ChaChaPoly produce a sealed box with three components:
| Component | AES-GCM | ChaChaPoly |
|---|---|---|
| Nonce | 12 bytes | 12 bytes |
| Ciphertext | Same length as plaintext | Same length as plaintext |
| Tag | 16 bytes | 16 bytes |
Combined representation
let sealedBox = try AES.GCM.seal(plaintext, using: key)
// Combined: nonce (12) + ciphertext (N) + tag (16)
let combined = sealedBox.combined // Optional for AES-GCM, non-optional for ChaChaPoly
// Individual components
let nonce = sealedBox.nonce
let ciphertext = sealedBox.ciphertext
let tag = sealedBox.tagReconstructing from components
When receiving nonce, ciphertext, and tag separately:
let box = try AES.GCM.SealedBox(
nonce: AES.GCM.Nonce(data: nonceData),
ciphertext: ciphertextData,
tag: tagData
)
let plaintext = try AES.GCM.open(box, using: key)Reconstructing from combined
let box = try AES.GCM.SealedBox(combined: combinedData)
let plaintext = try AES.GCM.open(box, using: key)Signing with Digest
For P256/P384/P521, sign a pre-computed digest instead of raw data:
let digest = SHA256.hash(data: data)
let signature = try privateKey.signature(for: digest)
let isValid = publicKey.isValidSignature(signature, for: digest)This avoids hashing the data twice when the digest is already available.
Encryption Export Compliance
Apps that use encryption must declare compliance in App Store Connect.
ITSAppUsesNonExemptEncryption
Set in Info.plist:
<key>ITSAppUsesNonExemptEncryption</key>
<false/>Set to false if the app uses ONLY:
- Apple-provided encryption (HTTPS via URLSession, CryptoKit for
data protection on-device only)
- Standard authentication (OAuth, SAML, biometrics)
Set to true if the app:
- Implements custom encryption protocols
- Communicates with non-standard encrypted services
- Encrypts data sent to third-party servers
When true, an export compliance review or proper classification is required. See Apple's Complying with Encryption Export Regulations documentation.
Performance Considerations
AES-GCM vs ChaChaPoly
On Apple silicon devices, AES-GCM is hardware-accelerated and generally faster. ChaChaPoly performs better on devices without AES hardware acceleration (rare on modern Apple hardware). For most iOS apps, prefer AES-GCM.
Hashing large data
Use incremental hashing for large files to avoid loading everything into memory:
func hashFile(at url: URL) throws -> SHA256.Digest {
let handle = try FileHandle(forReadingFrom: url)
var hasher = SHA256()
while autoreleasepool(invoking: {
let chunk = handle.readData(ofLength: 1024 * 1024) // 1 MB
guard !chunk.isEmpty else { return false }
hasher.update(data: chunk)
return true
}) {}
return hasher.finalize()
}Key generation costs
| Operation | Relative Cost |
|---|---|
SymmetricKey(size:) | Very fast (CSPRNG) |
P256.Signing.PrivateKey() | Fast |
P384.Signing.PrivateKey() | Moderate |
P521.Signing.PrivateKey() | Slower |
SecureEnclave.P256.*.PrivateKey() | Slowest (hardware round-trip) |
Generate keys once and store them. Do not regenerate per-operation.
CommonCrypto Migration
Hashing
// CommonCrypto (old)
import CommonCrypto
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes { CC_SHA256($0.baseAddress, CC_LONG(data.count), &digest) }
// CryptoKit (new)
import CryptoKit
let digest = SHA256.hash(data: data)HMAC
// CommonCrypto (old)
var hmac = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
keyData.withUnsafeBytes { keyPtr in
data.withUnsafeBytes { dataPtr in
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256),
keyPtr.baseAddress, keyData.count,
dataPtr.baseAddress, data.count,
&hmac)
}
}
// CryptoKit (new)
let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)AES encryption
// CommonCrypto (old) -- error-prone, manual IV/padding management
// ~30 lines of CCCrypt with buffer allocation
// CryptoKit (new) -- authenticated encryption in one call
let sealedBox = try AES.GCM.seal(data, using: key)
let decrypted = try AES.GCM.open(sealedBox, using: key)CryptoKit advantages over CommonCrypto:
- Authenticated encryption by default (no unauthenticated CBC mode)
- Type-safe keys and nonces
- Automatic nonce generation
- No manual buffer management
- Constant-time comparisons built in
- Sendable types for concurrency safety
Related skills
How it compares
Pick cryptokit for Apple CryptoKit-specific iOS patterns with eval validation, not generic OpenSSL or Node.js crypto guidance.
FAQ
What does cryptokit do?
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 ECD
When should I use cryptokit?
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 ECD
Is cryptokit safe to install?
Review the Security Audits panel on this page before installing in production.