PluginBench
Skill
Pass
Audit score 90

cryptokit

dpearson2699/swift-ios-skills

Apple CryptoKit for Swift cryptographic primitives: hashing, HMAC, AES-GCM, signing, key agreement, and post-quantum operations.

What is cryptokit?

CryptoKit provides a Swift-native API for cryptographic operations including hashing (SHA-2/SHA-3), message authentication (HMAC), symmetric encryption (AES-GCM, ChaChaPoly), public-key signing (ECDSA, Ed25519), key agreement (ECDH), HPKE, post-quantum key encapsulation/signing, and Secure Enclave-backed keys. Use it for new cryptographic code targeting iOS 13+ (most primitives) or iOS 17+/26+ for HPKE and post-quantum algorithms.

  • Hash data with SHA-256/384/512 or SHA-3 (incremental or one-shot)
  • Generate and verify HMACs for keyed message authentication
  • Encrypt and decrypt with AES-GCM or ChaChaPoly, with optional authenticated data
  • Sign and verify with ECDSA (P256/P384/P521) or Ed25519 (Curve25519)
  • Perform ECDH key agreement and derive symmetric keys via HKDF or X9.63
  • Encrypt to recipient public keys using HPKE (iOS 17+)

How to install cryptokit

npx skills add https://github.com/dpearson2699/swift-ios-skills --skill cryptokit
Prerequisites
  • iOS 13+ for core primitives (hashing, HMAC, AES-GCM, ChaChaPoly, ECDSA, Ed25519, ECDH)
  • iOS 17+ for HPKE
  • iOS 26+ for SHA-3 and post-quantum algorithms (ML-DSA, ML-KEM, X-Wing)
  • Swift 6.3+ recommended for full CryptoKit support
Claude Code
Cursor
Windsurf
Cline

How to use cryptokit

  1. 1.Import CryptoKit at the top of your Swift file
  2. 2.For hashing: call SHA256.hash(data:) or use incremental hashing with var hasher = SHA256()
  3. 3.For HMAC: create a SymmetricKey, then call HMAC<SHA256>.authenticationCode(for:using:) and verify with isValidAuthenticationCode()
  4. 4.For symmetric encryption: create a SymmetricKey, seal plaintext with AES.GCM.seal() or ChaChaPoly.seal(), and decrypt with .open()
  5. 5.For signing: create a private key (P256.Signing.PrivateKey() or Curve25519.Signing.PrivateKey()), sign with .signature(for:), and verify with publicKey.isValidSignature()
  6. 6.For key agreement: create two private keys, compute sharedSecret via sharedSecretFromKeyAgreement(), and derive a SymmetricKey using hkdfDerivedSymmetricKey()
  7. 7.For HPKE (iOS 17+): create a sender with HPKE.Sender(recipientKey:ciphersuite:info:) and seal plaintext
  8. 8.For Secure Enclave: use SecureEnclave.P256.Signing.PrivateKey() to create hardware-backed keys

Use cases

Good for
  • Verifying file integrity or API response authenticity with SHA-256 hashing and HMAC
  • Encrypting sensitive user data at rest with AES-256-GCM
  • Signing and verifying JWTs or API requests with P256 or Ed25519 keys
  • Establishing a shared encryption key between two devices via ECDH key agreement
  • Migrating legacy CommonCrypto code to modern CryptoKit APIs
Who it's for
  • iOS/macOS app developers implementing cryptographic features
  • Security engineers migrating from CommonCrypto or raw Security framework APIs
  • Backend developers building Swift server applications with cryptographic requirements
  • Anyone building protocols requiring ECDH, HPKE, or post-quantum cryptography

cryptokit FAQ

When should I use AES-GCM vs. ChaChaPoly?

Use AES-GCM by default; it is hardware-accelerated on Apple silicon. Use ChaChaPoly when AES acceleration is unavailable or when interoperating with protocols like TLS or WireGuard that require ChaCha20-Poly1305.

How do I compare cryptographic digests securely?

Compare CryptoKit digest values directly using == (e.g., SHA256.hash(data: a) == SHA256.hash(data: b)). Do not convert digests to strings or arrays for security-sensitive comparisons.

What is the difference between ECDH and HPKE?

ECDH is a key agreement primitive that requires both parties to exchange public keys beforehand. HPKE is a complete public-key encryption scheme that encapsulates a shared secret and encrypts data in one operation, useful when you only have the recipient's public key.

Can I use CryptoKit keys with the Secure Enclave?

Yes. Use SecureEnclave.P256.Signing.PrivateKey() or SecureEnclave.P256.KeyAgreement.PrivateKey() to create hardware-backed keys that never leave the Secure Enclave. Curve25519 keys do not support Secure Enclave.

Do I need to check availability for SHA-3 and post-quantum algorithms?

Yes. Use #available(iOS 26.0, *) guards around SHA-3 (SHA3_256/384/512) and post-quantum APIs (ML-DSA, ML-KEM, X-Wing) unless your deployment target is iOS 26+.

Full instructions (SKILL.md)

Source of truth, from dpearson2699/swift-ios-skills.


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

Use SHA256/SHA384/SHA512 on iOS 13+; SHA3_256/SHA3_384/SHA3_512 require iOS 26+. All conform to HashFunction.

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()

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

Use HMAC when a protocol requires keyed message authentication; verify with isValidAuthenticationCode rather than comparing serialized values yourself.

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
)

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

CurveSignature SchemeKey SizeTypical Use
P256ECDSA256-bitGeneral purpose; Secure Enclave support
P384ECDSA384-bitHigher security requirements
P521ECDSA521-bitMaximum NIST security level
Curve25519Ed25519256-bitFast; 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:

MethodStandardUse
hkdfDerivedSymmetricKeyHKDF (RFC 5869)Recommended default
x963DerivedSymmetricKeyANSI X9.63Interop 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: XWingMLKEM768X25519 with .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 automatically

4. 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 patterns

6. 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 dataRepresentation stored 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