> 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 18.4+.
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.encapsulatedKey: Data (ciphertext to send)
// Recipient: decapsulate with private key
let sharedSecret = try privateKey.decapsulate(result.encapsulatedKey)
// sharedSecret: SymmetricKey — matches sender's sharedSecret
Key Representations
// Public key
publicKey.rawRepresentation // Data
// Private key
privateKey.rawRepresentation // Data (expanded form)
privateKey.seedRepresentation // Data (compact seed, 64 bytes)
// Reconstruct
let pk = try MLKEM768.PrivateKey(rawRepresentation: rawData)
let pk = try MLKEM768.PrivateKey(seedRepresentation: seedData)
// Integrity-checked (validates key consistency)
let pk = try MLKEM768.PrivateKey(integrityCheckedRepresentation: data)
Post-Quantum Cryptography: ML-DSA
Digital Signature Algorithm based on Module-Lattice (FIPS 204). iOS 18.4+.
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
let signature = try privateKey.signature(for: data)
// Sign with context (domain separation)
let signature = try privateKey.signature(for: data, context: contextData)
// Verify
let valid = publicKey.isValidSignature(signature, for: data)
let valid = publicKey.isValidSignature(signature, for: data, context: contextData)
Key and Signature Representations
// Public key
publicKey.rawRepresentation
// Private key
privateKey.rawRepresentation
privateKey.seedRepresentation
// Reconstruct
let pk = try MLDSA65.PrivateKey(rawRepresentation: rawData)
let pk = try MLDSA65.PrivateKey(seedRepresentation: seedData)
// Signature
signature.rawRepresentation
let sig = try MLDSA65.Signature(rawRepresentation: rawData)
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 18.4+.
let privateKey = XWingMLKEM768X25519.PrivateKey()
let publicKey = privateKey.publicKey
// Encapsulate
let result = try publicKey.encapsulate()
// result.sharedSecret, result.encapsulatedKey
// Decapsulate
let sharedSecret = try privateKey.decapsulate(result.encapsulatedKey)
// Representations
publicKey.rawRepresentation
privateKey.rawRepresentation
privateKey.seedRepresentation
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 |
.Curve25519_SHA256_AES_GCM_128 | Curve25519 | HKDF-SHA256 | AES-128-GCM |
.Curve25519_SHA256_AES_GCM_256 | Curve25519 | HKDF-SHA256 | AES-256-GCM |
.P256_SHA256_AES_GCM_128 | P-256 | HKDF-SHA256 | AES-128-GCM |
.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_SHA256
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.sealFailure // Encryption failed
HPKE.Errors.openFailure // Decryption/authentication failed
HPKE.Errors.encapsulationFailure // KEM encapsulation failed
HPKE.Errors.decapsulationFailure // KEM decapsulation failed
HPKE.Errors.exportFailure // Secret export failed
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 18.4+) |
SecureEnclave.MLKEM1024.PrivateKey | Post-quantum KEM (iOS 18.4+) |
SecureEnclave.MLDSA65.PrivateKey | Post-quantum signatures (iOS 18.4+) |
SecureEnclave.MLDSA87.PrivateKey | Post-quantum signatures (iOS 18.4+) |
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.sealFailure
HPKE.Errors.openFailure
HPKE.Errors.encapsulationFailure
HPKE.Errors.decapsulationFailure
HPKE.Errors.exportFailure
KEM.Errors.decapsulationFailed
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-passkeys
Use when implementing passkey sign-in, replacing passwords with WebAuthn, configuring ASAuthorizationController, setting up AutoFill-assisted requests, adding automatic passkey upgrades, or migrating from password-based authentication. Covers passkey creation, assertion, cross-device sign-in, credential managers, and the Passwords app.
> axiom-keychain
Use when storing credentials, tokens, or secrets securely, debugging SecItem errors (errSecDuplicateItem, errSecItemNotFound, errSecInteractionNotAllowed), managing keychain access groups, or choosing accessibility classes. Covers SecItem API mental model, uniqueness constraints, data protection, biometric access control, sharing between apps, and Mac keychain differences.
> axiom-keychain-ref
Use when needing SecItem function signatures, keychain attribute constants, item class uniqueness constraints, accessibility level details, SecAccessControlCreateFlags, kSecReturn behavior per class, LAContext keychain integration, or OSStatus error codes. Covers complete keychain API surface.
> axiom-keychain-diag
Use when SecItem calls fail — errSecDuplicateItem from unexpected uniqueness, errSecItemNotFound despite item existing, errSecInteractionNotAllowed in background, keychain items disappearing after app update, access group entitlement errors, or Mac keychain shim issues. Covers systematic error diagnosis with decision trees.