> axiom-cryptokit-ref
Use when needing CryptoKit API details — hash functions (SHA2/SHA3), HMAC, AES-GCM/ChaChaPoly encryption, ECDSA/EdDSA signatures, ECDH key agreement, ML-KEM/ML-DSA post-quantum algorithms, HPKE encryption, Secure Enclave key types, key representations (raw/DER/PEM/x963), or Swift Crypto cross-platform parity. Covers complete CryptoKit API surface.
curl "https://skillshub.wtf/CharlesWiltgen/Axiom/axiom-cryptokit-ref?format=md"CryptoKit API Reference
Complete API reference for Apple CryptoKit: hashing, HMAC, symmetric encryption, key agreement, digital signatures, post-quantum cryptography, HPKE, Secure Enclave, key derivation, and Swift Crypto cross-platform parity.
Quick Reference
import CryptoKit
// Generate a symmetric key
let key = SymmetricKey(size: .bits256)
// AES-GCM encrypt
let sealed = try AES.GCM.seal(plaintext, using: key)
let combined = sealed.combined! // nonce + ciphertext + tag
// AES-GCM decrypt
let sealedBox = try AES.GCM.SealedBox(combined: combined)
let decrypted = try AES.GCM.open(sealedBox, using: key)
// ECDSA sign (P256)
let signingKey = P256.Signing.PrivateKey()
let signature = try signingKey.signature(for: data)
let valid = signingKey.publicKey.isValidSignature(signature, for: data)
// Secure Enclave key
let seKey = try SecureEnclave.P256.Signing.PrivateKey()
let seSignature = try seKey.signature(for: data)
Hashing
Hash Functions
| Algorithm | Type | Output Size | Use |
|---|---|---|---|
| SHA256 | SHA256 | 32 bytes | General purpose, most common |
| SHA384 | SHA384 | 48 bytes | TLS, certificate chains |
| SHA512 | SHA512 | 64 bytes | High-security contexts |
| SHA3_256 | SHA3_256 | 32 bytes | NIST post-quantum companion |
| SHA3_384 | SHA3_384 | 48 bytes | Post-quantum companion |
| SHA3_512 | SHA3_512 | 64 bytes | Post-quantum companion |
| Insecure.MD5 | Insecure.MD5 | 16 bytes | Legacy interop only |
| Insecure.SHA1 | Insecure.SHA1 | 20 bytes | Legacy interop only |
Single-Call Hashing
let digest = SHA256.hash(data: data)
// digest conforms to Sequence of UInt8
let hex = digest.map { String(format: "%02x", $0) }.joined()
Streaming (Incremental) Hashing
var hasher = SHA256()
hasher.update(data: chunk1)
hasher.update(data: chunk2)
hasher.update(bufferPointer: unsafePointer)
let digest = hasher.finalize() // SHA256Digest
HashFunction Protocol
All hash types conform to HashFunction with: byteCount, blockByteCount, init(), update(data:), update(bufferPointer:), finalize(), and hash(data:).
Digest conforms to Sequence (of UInt8), supports constant-time ==, and converts to Data(digest) or Array(digest). description returns hex string.
Message Authentication (HMAC)
SymmetricKey
let key = SymmetricKey(size: .bits128) // .bits128, .bits192, .bits256
let key = SymmetricKey(size: SymmetricKeySize(bitCount: 512)) // Custom size
let key = SymmetricKey(data: existingKeyData) // From existing material
key.bitCount // Key size in bits
key.withUnsafeBytes { bytes in /* ... */ } // Only way to access raw bytes
HMAC Generation and Verification
// HMAC is generic over HashFunction
let authCode = HMAC<SHA256>.authenticationCode(for: data, using: key)
// authCode: HMAC<SHA256>.MAC
let valid = HMAC<SHA256>.isValidAuthenticationCode(authCode, authenticating: data, using: key)
// Data representation
let macData = Data(authCode)
Iterative HMAC
var hmac = HMAC<SHA256>(key: key)
hmac.update(data: chunk1)
hmac.update(data: chunk2)
let authCode = hmac.finalize()
Symmetric Encryption
AES-GCM
// Seal (encrypt + authenticate)
let sealed = try AES.GCM.seal(plaintext, using: key)
let sealed = try AES.GCM.seal(plaintext, using: key, nonce: customNonce)
let sealed = try AES.GCM.seal(
plaintext,
using: key,
nonce: customNonce,
authenticating: associatedData // AAD — authenticated but not encrypted
)
// SealedBox properties
sealed.nonce // AES.GCM.Nonce (12 bytes)
sealed.ciphertext // Data
sealed.tag // Data (16 bytes)
sealed.combined // Data? (nonce + ciphertext + tag)
// Open (decrypt + verify)
let plaintext = try AES.GCM.open(sealedBox, using: key)
let plaintext = try AES.GCM.open(sealedBox, using: key, authenticating: associatedData)
AES-GCM SealedBox Construction
// From combined representation (nonce + ciphertext + tag)
let box = try AES.GCM.SealedBox(combined: combinedData)
// From components
let box = try AES.GCM.SealedBox(
nonce: AES.GCM.Nonce(data: nonceData),
ciphertext: ciphertextData,
tag: tagData
)
AES-GCM Nonce
let nonce = AES.GCM.Nonce() // Random 12 bytes (recommended)
let nonce = try AES.GCM.Nonce(data: nonceData) // Custom (MUST be unique per key)
ChaChaPoly
Identical interface to AES-GCM. Preferred for software-only environments without AES-NI.
let sealed = try ChaChaPoly.seal(plaintext, using: key)
let sealed = try ChaChaPoly.seal(plaintext, using: key, authenticating: aad)
let plaintext = try ChaChaPoly.open(sealed, using: key)
let plaintext = try ChaChaPoly.open(sealed, using: key, authenticating: aad)
// SealedBox, Nonce — same pattern as AES.GCM
let box = try ChaChaPoly.SealedBox(combined: combined)
let nonce = ChaChaPoly.Nonce()
AES Key Wrapping
// Wrap a key with another key (RFC 3394)
let wrapped = try AES.KeyWrap.wrap(keyToWrap, using: wrappingKey)
// wrapped: Data
// Unwrap
let unwrapped = try AES.KeyWrap.unwrap(wrapped, using: wrappingKey)
// unwrapped: SymmetricKey
Key Agreement (ECDH)
Supported Curves
| Curve | Type Prefix | Key Size | Use |
|---|---|---|---|
| Curve25519 | Curve25519.KeyAgreement | 32 bytes | Modern, fast, safe defaults |
| P-256 | P256.KeyAgreement | 32 bytes | NIST standard, Secure Enclave |
| P-384 | P384.KeyAgreement | 48 bytes | Higher security NIST |
| P-521 | P521.KeyAgreement | 66 bytes | Maximum NIST security |
Private Key Creation
let privateKey = Curve25519.KeyAgreement.PrivateKey() // Random
let privateKey = P256.KeyAgreement.PrivateKey() // Random
let privateKey = P256.KeyAgreement.PrivateKey(compactRepresentable: true)
// From serialized representations
let privateKey = try P256.KeyAgreement.PrivateKey(rawRepresentation: rawData)
let privateKey = try P256.KeyAgreement.PrivateKey(derRepresentation: derData)
let privateKey = try P256.KeyAgreement.PrivateKey(pemRepresentation: pemString)
let privateKey = try P256.KeyAgreement.PrivateKey(x963Representation: x963Data) // NIST only
Public Key Representations
let publicKey = privateKey.publicKey
publicKey.rawRepresentation // Data (all curves)
publicKey.derRepresentation // Data — SubjectPublicKeyInfo (all curves)
publicKey.pemRepresentation // String (all curves)
publicKey.x963Representation // Data — uncompressed point (NIST only)
publicKey.compactRepresentation // Data? (NIST only)
publicKey.compressedRepresentation // Data (NIST only)
Shared Secret Derivation
let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: peerPublicKey)
// sharedSecret: SharedSecret — NOT directly usable as a key
// Derive symmetric key with HKDF
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: saltData, // Can be empty Data()
sharedInfo: infoData, // Context/label data
outputByteCount: 32 // Key size
)
// Derive with X9.63 KDF
let symmetricKey = sharedSecret.x963DerivedSymmetricKey(
using: SHA256.self,
sharedInfo: infoData,
outputByteCount: 32
)
Signatures (ECDSA/EdDSA)
Supported Algorithms
| Curve | Algorithm | Type Prefix |
|---|---|---|
| Curve25519 | Ed25519 (EdDSA) | Curve25519.Signing |
| P-256 | ECDSA | P256.Signing |
| P-384 | ECDSA | P384.Signing |
| P-521 | ECDSA | P521.Signing |
Key Creation
let privateKey = P256.Signing.PrivateKey()
let privateKey = Curve25519.Signing.PrivateKey()
// Same representation constructors as KeyAgreement keys:
// init(rawRepresentation:), init(derRepresentation:),
// init(pemRepresentation:), init(x963Representation:) for NIST curves
Sign and Verify
// Sign raw data
let signature = try privateKey.signature(for: data)
// Sign a digest (skip re-hashing already-hashed data)
let digest = SHA256.hash(data: data)
let signature = try privateKey.signature(for: digest) // NIST curves only
// Verify
let valid = privateKey.publicKey.isValidSignature(signature, for: data)
let valid = privateKey.publicKey.isValidSignature(signature, for: digest)
Signature Representations
// NIST curves (P256/P384/P521)
signature.derRepresentation // Data — use for cross-platform interop
signature.rawRepresentation // Data — r || s concatenated
// Reconstruct from DER
let sig = try P256.Signing.ECDSASignature(derRepresentation: derData)
let sig = try P256.Signing.ECDSASignature(rawRepresentation: rawData)
// Curve25519 — raw bytes only (64 bytes, no DER)
signature.rawRepresentation
Cross-Platform Encoding
Use derRepresentation when exchanging signatures with non-CryptoKit systems (OpenSSL, Java, Go). Use rawRepresentation for CryptoKit-to-CryptoKit or when wire size matters (DER adds 6-8 bytes overhead).
Post-Quantum Cryptography: ML-KEM
Key Encapsulation Mechanism based on Module-Lattice (FIPS 203). iOS 26+.
Parameter Sets
| Type | Security Level | Public Key | Ciphertext | Shared Secret |
|---|---|---|---|---|
| MLKEM768 | 128-bit (AES-128 equivalent) | 1,184 bytes | 1,088 bytes | 32 bytes |
| MLKEM1024 | 256-bit (AES-256 equivalent) | 1,568 bytes | 1,568 bytes | 32 bytes |
Key Generation
let privateKey = MLKEM768.PrivateKey()
let publicKey = privateKey.publicKey
let privateKey = MLKEM1024.PrivateKey()
Encapsulation and Decapsulation
// Sender: encapsulate with recipient's public key
let result = try recipientPublicKey.encapsulate()
// result.sharedSecret: SymmetricKey (32 bytes)
// result.encapsulated: Data (ciphertext to send)
// Recipient: decapsulate with private key
let sharedSecret = try privateKey.decapsulate(result.encapsulated)
// sharedSecret: SymmetricKey — matches sender's sharedSecret
Key Representations
// Public key
publicKey.rawRepresentation // Data
// Private key
privateKey.seedRepresentation // Data (compact seed)
privateKey.integrityCheckedRepresentation // Data (seed + SHA3-256 hash)
// Reconstruct
let pk = try MLKEM768.PrivateKey(seedRepresentation: seedData, publicKey: publicKey)
let pk = try MLKEM768.PrivateKey(integrityCheckedRepresentation: data)
Post-Quantum Cryptography: ML-DSA
Digital Signature Algorithm based on Module-Lattice (FIPS 204). iOS 26+.
Parameter Sets
| Type | Security Level | Public Key | Signature |
|---|---|---|---|
| MLDSA65 | 128-bit | 1,952 bytes | 3,309 bytes |
| MLDSA87 | 256-bit | 2,592 bytes | 4,627 bytes |
Key Generation
let privateKey = MLDSA65.PrivateKey()
let publicKey = privateKey.publicKey
let privateKey = MLDSA87.PrivateKey()
Sign and Verify
// Sign — returns Data (not a typed Signature struct)
let signatureData = try privateKey.signature(for: data)
// Sign with context (domain separation)
let signatureData = try privateKey.signature(for: data, context: contextData)
// Verify — takes DataProtocol for signature parameter
let valid = publicKey.isValidSignature(signatureData, for: data)
let valid = publicKey.isValidSignature(signatureData, for: data, context: contextData)
Key and Signature Representations
// Public key
publicKey.rawRepresentation
// Private key
privateKey.seedRepresentation
privateKey.integrityCheckedRepresentation
// Reconstruct
let pk = try MLDSA65.PrivateKey(seedRepresentation: seedData, publicKey: publicKey)
let pk = try MLDSA65.PrivateKey(integrityCheckedRepresentation: data)
// Signature is raw Data — no typed Signature struct
// Store/transmit signatureData directly
Hybrid Post-Quantum: X-Wing KEM
Combines ML-KEM768 + Curve25519 ECDH for hybrid post-quantum key exchange. If either algorithm holds, the combined scheme holds. iOS 26+.
let privateKey = XWingMLKEM768X25519.PrivateKey()
let publicKey = privateKey.publicKey
// Encapsulate
let result = try publicKey.encapsulate()
// result.sharedSecret, result.encapsulated
// Decapsulate
let sharedSecret = try privateKey.decapsulate(result.encapsulated)
// Representations
publicKey.rawRepresentation
privateKey.seedRepresentation
privateKey.integrityCheckedRepresentation
HPKE (Hybrid Public Key Encryption)
Hybrid Public Key Encryption (RFC 9180). Combines KEM + KDF + AEAD into a single encryption scheme. iOS 17+ (classical ciphersuites). Post-quantum ciphersuites (XWing) require iOS 26+.
Predefined Ciphersuites
| Ciphersuite | KEM | KDF | AEAD |
|---|---|---|---|
.XWingMLKEM768X25519_SHA256_AES_GCM_256 | X-Wing | HKDF-SHA256 | AES-256-GCM |
.Curve25519_SHA256_ChachaPoly | Curve25519 | HKDF-SHA256 | ChaCha20Poly1305 |
.P256_SHA256_AES_GCM_256 | P-256 | HKDF-SHA256 | AES-256-GCM |
.P384_SHA384_AES_GCM_256 | P-384 | HKDF-SHA384 | AES-256-GCM |
.P521_SHA512_AES_GCM_256 | P-521 | HKDF-SHA512 | AES-256-GCM |
Custom Ciphersuite Composition
let ciphersuite = HPKE.Ciphersuite(
kem: .Curve25519_HKDF_SHA256,
kdf: .HKDF_SHA256,
aead: .AES_GCM_128
)
KEM Options
.Curve25519_HKDF_SHA256, .P256_HKDF_SHA256, .P384_HKDF_SHA384, .P521_HKDF_SHA512, .XWingMLKEM768X25519 (iOS 26+)
KDF Options
.HKDF_SHA256, .HKDF_SHA384, .HKDF_SHA512
AEAD Options
.AES_GCM_128, .AES_GCM_256, .chaChaPoly, .exportOnly
Sender (Encrypt)
var sender = try HPKE.Sender(
recipientKey: recipientPublicKey,
ciphersuite: .Curve25519_SHA256_ChachaPoly,
info: infoData // Binding context (can be empty)
)
let ciphertext = try sender.seal(plaintext)
let ciphertext = try sender.seal(plaintext, authenticating: aad)
let encapsulatedKey = sender.encapsulatedKey // Send alongside ciphertext
// Export secret (for key derivation without encryption)
let exported = try sender.exportSecret(context: ctx, outputByteCount: 32)
Recipient (Decrypt)
var recipient = try HPKE.Recipient(
privateKey: recipientPrivateKey,
ciphersuite: .Curve25519_SHA256_ChachaPoly,
info: infoData,
encapsulatedKey: encapsulatedKey // From sender
)
let plaintext = try recipient.open(ciphertext)
let plaintext = try recipient.open(ciphertext, authenticating: aad)
let exported = try recipient.exportSecret(context: ctx, outputByteCount: 32)
Additional Modes
Both Sender and Recipient accept optional authentication and PSK parameters:
// Authenticated mode — proves sender identity
var sender = try HPKE.Sender(
recipientKey: recipientPublicKey, ciphersuite: ciphersuite, info: infoData,
authenticatedBy: senderPrivateKey
)
var recipient = try HPKE.Recipient(
privateKey: recipientPrivateKey, ciphersuite: ciphersuite, info: infoData,
encapsulatedKey: encapsulatedKey, authenticatedBy: senderPublicKey
)
// PSK mode — adds pre-shared key binding
// Add to either Sender or Recipient init:
// presharedKey: psk, // SymmetricKey
// presharedKeyIdentifier: pskID // Data
HPKE Error Types
HPKE.Errors.inconsistentParameters // Ciphersuite/key mismatch
HPKE.Errors.inconsistentCiphersuiteAndKey // Key type doesn't match KEM
HPKE.Errors.exportOnlyMode // Seal/open called in export-only mode
HPKE.Errors.inconsistentPSKInputs // PSK and PSK ID must both be provided or neither
HPKE.Errors.expectedPSK // PSK mode requires PSK
HPKE.Errors.unexpectedPSK // Non-PSK mode given PSK
HPKE.Errors.outOfRangeSequenceNumber // Sequence number overflow
HPKE.Errors.ciphertextTooShort // Ciphertext shorter than tag size
Secure Enclave
Hardware-backed key storage. Keys never leave the Secure Enclave chip. Device-bound and non-exportable.
Availability Check
SecureEnclave.isAvailable // false on Simulator, true on devices with SE
Supported Key Types
| Type | Use |
|---|---|
SecureEnclave.P256.Signing.PrivateKey | ECDSA signatures |
SecureEnclave.P256.KeyAgreement.PrivateKey | ECDH key agreement |
SecureEnclave.MLKEM768.PrivateKey | Post-quantum KEM (iOS 26+) |
SecureEnclave.MLKEM1024.PrivateKey | Post-quantum KEM (iOS 26+) |
SecureEnclave.MLDSA65.PrivateKey | Post-quantum signatures (iOS 26+) |
SecureEnclave.MLDSA87.PrivateKey | Post-quantum signatures (iOS 26+) |
Key Creation
let key = try SecureEnclave.P256.Signing.PrivateKey() // Default access control
// With biometric access control
let accessControl = SecAccessControlCreateWithFlags(
nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet], nil
)!
let key = try SecureEnclave.P256.Signing.PrivateKey(accessControl: accessControl)
// With pre-prompted biometric context
let context = LAContext()
context.localizedReason = "Sign transaction"
let key = try SecureEnclave.P256.Signing.PrivateKey(
accessControl: accessControl, authenticationContext: context
)
Persistence and Usage
// dataRepresentation is an opaque device-bound blob — store in Keychain
let wrapped = key.dataRepresentation
let restored = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: wrapped)
let restored = try SecureEnclave.P256.Signing.PrivateKey(
dataRepresentation: wrapped, authenticationContext: context
)
// SE keys use the same sign/verify/agree API as software keys
let signature = try seKey.signature(for: data)
let valid = seKey.publicKey.isValidSignature(signature, for: data)
let publicKeyData = seKey.publicKey.derRepresentation // Public key IS exportable
Key Derivation (HKDF)
HMAC-based Key Derivation Function (RFC 5869).
One-Step Derivation
let derivedKey = HKDF<SHA256>.deriveKey(
inputKeyMaterial: SymmetricKey(data: ikm),
salt: saltData, // Optional, can be empty
info: infoData, // Context/label
outputByteCount: 32
)
// derivedKey: SymmetricKey
Two-Step (Extract + Expand)
Use two-step when deriving multiple keys from the same input: extract once, expand with different info values.
let prk = HKDF<SHA256>.extract(inputKeyMaterial: SymmetricKey(data: ikm), salt: saltData)
let encKey = HKDF<SHA256>.expand(pseudoRandomKey: prk, info: Data("enc".utf8), outputByteCount: 32)
let macKey = HKDF<SHA256>.expand(pseudoRandomKey: prk, info: Data("mac".utf8), outputByteCount: 32)
Error Types
CryptoKitError
CryptoKitError.incorrectKeySize // Key size doesn't match algorithm
CryptoKitError.incorrectParameterSize // Parameter size invalid
CryptoKitError.authenticationFailure // GCM/ChaCha tag verification failed, HMAC mismatch
CryptoKitError.underlyingCoreCryptoError(error:) // Low-level failure
CryptoKitError.wrapFailure // AES key wrap failed
CryptoKitError.unwrapFailure // AES key unwrap failed
CryptoKitASN1Error
CryptoKitASN1Error.invalidASN1Object // Malformed ASN.1 structure
CryptoKitASN1Error.invalidASN1IntegerEncoding // Bad integer encoding
CryptoKitASN1Error.truncatedASN1Field // Data ends prematurely
CryptoKitASN1Error.invalidFieldIdentifier // Unknown ASN.1 tag
CryptoKitASN1Error.unexpectedFieldType // Wrong ASN.1 type
CryptoKitASN1Error.invalidObjectIdentifier // Bad OID
CryptoKitASN1Error.invalidPEMDocument // PEM header/footer or Base64 invalid
HPKE and KEM Errors
// HPKE.Errors — see HPKE section for full list of 8 cases
HPKE.Errors.inconsistentParameters
HPKE.Errors.ciphertextTooShort
// ... (6 more)
// KEM.Errors (iOS 26+)
KEM.Errors.publicKeyMismatchDuringInitialization
KEM.Errors.invalidSeed
Swift Crypto Cross-Platform Parity
Apple's open-source swift-crypto provides CryptoKit APIs on Linux, Windows, and other platforms.
Import Difference
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto // swift-crypto package
#endif
API Parity
Everything maps 1:1 except SecureEnclave.* (requires Apple hardware). Hashing, HMAC, AES-GCM, ChaChaPoly, ECDH, ECDSA/EdDSA, ML-KEM, ML-DSA, X-Wing, HPKE, HKDF, and AES Key Wrap are all available cross-platform.
// Package.swift
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0")
// Target: .product(name: "Crypto", package: "swift-crypto")
Resources
WWDC: 2019-709, 2024-10120
Docs: /cryptokit, /cryptokit/performing-common-cryptographic-operations, /security/certificate-key-and-trust-services/keys/storing-keys-in-the-secure-enclave
Skills: axiom-cryptokit
> related_skills --same-repo
> axiom-eventkit
Use when working with ANY calendar event, reminder, EventKit permission, or EventKitUI controller. Covers access tiers (no-access, write-only, full), permission migration from pre-iOS 17, store lifecycle, reminder patterns, EventKitUI controller selection, Siri Event Suggestions, virtual conference extensions.
> axiom-eventkit-ref
Use when needing EventKit API details — EKEventStore, EKEvent, EKReminder, EventKitUI view controllers, EKCalendarChooser, authorization methods, predicate-based fetching, recurrence rules, Siri Event Suggestions donation, EKVirtualConferenceProvider, location-based reminders, and EKErrorDomain codes
> axiom-contacts
Use when accessing ANY contact data, requesting Contacts permissions, choosing between picker and store access, implementing Contact Access Button, or migrating to iOS 18 limited access. Covers authorization levels, CNContactStore, ContactProvider, key fetching, incremental sync.
> axiom-contacts-ref
Use when needing Contacts API details — CNContactStore, CNMutableContact, CNSaveRequest, CNContactFormatter, CNContactVCardSerialization, CNContactPickerViewController, ContactAccessButton, contactAccessPicker, ContactProvider extension, CNChangeHistoryFetchRequest, contact key descriptors, and CNError codes