CryptoSuite is your all-in-one Swift library for integrating elliptic curve cryptography into your apps with ease. It provides a simple yet robust API that seamlessly combines cryptographic operations, secure key persistence via keychain and biometric protection, all built on top of Apple's CryptoKit.
CryptoSuite is written entirely in Swift 6, leveraging structured concurrency and the latest Swift APIs. It provides your project with a comprehensive and secure solution for EC-based cryptographic operations, including key persistence and message integrity checks using MAC:
- Elliptic Curve Cryptography (ECC): Support for key agreement (ECDH) and digital signature (ECDSA) operations using the following elliptic curves:
- NIST P-256 (+ Secure Enclave managed P-256)
- NIST P-384
- NIST P-521
- Message Authentication Code (MAC): Generation and verification using HMAC.
- Keychain Integration: Secure key persistence leveraging Apple's keychain through the Keyrmes library.
- Biometric Protection: Optional biometry authentication for key access and key usage (the later available only for Secure Enclave keys only).
- Biometric State Awareness: Monitoring device biometric state changes (for example when new fingerprint / face / eye has been registered)
- Authentication Context Workflows: Manage biometric authentication contexts, including initialization, preauthentication, and invalidation workflows
CryptoSuite supports the following platforms and OS versions:
- iOS: 16.0 and later
- macOS: 13.0 and later
- watchOS: 9.0 and later
- tvOS: 16.0 and later
- visionOS: 1.0 and later
To integrate using Apple's Swift Package Manager, add the following line to the dependencies in your Package.swift
file:
.package(url: "https://github.com/bpluta/CryptoSuite.git", from: "1.0.0")
Then include CryptoSuite
as a dependency for your target:
.target(name: "<YOUR_TARGET_NAME>", dependencies: [
.product(name: "CryptoSuite", package: "CryptoSuite"),
]),
Finally, add import CryptoSuite
to your source code.
CryptoSuite offers a clear and easy-to-use API for performing common cryptographic tasks. Below are examples demonstrating essential features:
let keyGenerator = SecureKeyGenerator()
// Generates standard NIST P-256 EC Private Key
let privateKey: P256.KeyAgreement.PrivateKey? = try keyGenerator.generateKey(with: nil)
// Obtaining public key
let publicKey = privateKey?.publicKey
// Defining enum with keychain entity identifier
enum KeychainIdentifiers: String, KeychainIdentifiable {
case agreementKey
var keychainLabel: String { rawValue }
}
// Setting up keychain-backed secure storage
let keychainStore = KeychainStore()
let keyStorage = SecureKeyStorage(keychainStore: keychainStore, accessibility: .whenUnlockedThisDeviceOnly)
// Storing given private key under agreementKey identifier
try await keyStorage.storeKey(
privateKey,
identifier: KeychainIdentifiers.agreementKey,
isAuthenticationRequired: false
)
// Reading P256.KeyAgreement.PrivateKey under agreementKey identifier from keychain
let privateKey: P256.KeyAgreement.PrivateKey? = try await keyStorage.readKey(
identifier: KeychainIdentifiers.agreementKey,
authenticationContext: nil
)
// Deleting key of agreementKey from keychain
try await keyStorage.deleteKey(identifier: KeychainIdentifiers.agreementKey)
// Generating ECDSA signature of given message using provided private key
let signature = try signatureManager.sign(data: messageData, with: privateKey)
// Verifying if ECDSA signature of given message is correct and has been produced by the owner of the public key
let isValid = signatureManager.verify(signature: signature, of: messageData, for: publicKey)
// Initializing CryptoKit.SymmetricKey instance from raw key data
let symmetricKey = SymmetricKey(data: keyData)
// Generating HMAC-SHA256 authentication code of given data using provided symmetric key
let authenticationCode = signatureManager.create(HMAC<SHA256>.self, from: data, with: symmetricKey)
// Initializing CryptoKit.SymmetricKey instance from raw key data
let symmetricKey = SymmetricKey(data: keyData)
// Validating if provided HMAC-SHA256 authentication code is correct for given data and symmetric key
let isValid = signatureManager.verify(HMAC<SHA256>.self, authenticationCode: authenticationCode, authenticating: data, using: symmetricKey)
Important
In order to provide biometry into your app you need to first include the NSFaceIDUsageDescription
key into your Info.plist
file in your project
// Defining enum with keychain entity identifiers
enum KeychainIdentifiers: String, KeychainIdentifiable {
case biometryDomainState
var keychainLabel: String { rawValue }
}
// Setting up BiometryProvider instance
let keychainStore = KeychainStore()
let biometryProvider = BiometryProvider(
keychainStore: keychainStore,
domainStateKey: KeychainIdentifiers.biometryDomainState,
accessibility: .whenUnlockedThisDeviceOnly
)
// Initializing authentication context with custom prompt message
let biometryContext = biometryProvider.initializeContext(prompt: "Biometry prompt message")
// Verifying if biometry can be evaluated and its state has not been changed
// (for example new face has been enrolled to FaceID)
try await biometryProvider.biometryState.verify(biometryContext, with: .biometry)
// Evaluating a biometry authentication on given context
try await biometryProvider.preauthBiometry(context: biometryContext, policy: .biometry)
// Stopping pending evaluation and preventing the context from being used again
await biometryProvider.invalidate(context: biometryContext)
// Deleting biometry state domain from keychain
try await biometryProvider.biometryState.reset()
You can use your custom LAContext setup with CryptoSuite by using the following initializer:
let biometryContext = BiometryContext(context: laContext)
CryptoSuite provides an easy way to migrate SecKey instances from Security.framework
to supported CryptoKit
key types that can be used in CryptoSuite. Here are few examples:
// NIST P-256
let p256AgreementKey = try P256.KeyAgreement.PrivateKey(secKey: secKey)
let p256SigningKey = try P256.Signing.PrivateKey(secKey: secKey)
// NIST P-384
let p384AgreementKey = try P384.KeyAgreement.PrivateKey(secKey: secKey)
let p384SigningKey = try P384.Signing.PrivateKey(secKey: secKey)
// NIST P-521
let p521AgreementKey = try P521.KeyAgreement.PrivateKey(secKey: secKey)
let p521SigningKey = try P521.Signing.PrivateKey(secKey: secKey)
// Secure Enclave NIST P-256
let p256SecureEnclaveAgreementKey = try SecureEnclave.P256.KeyAgreement.PrivateKey(secKey: secKey, authenticationContext: laContext)
let p256SecureEnclaveSigningKey = try SecureEnclave.P256.Signing.PrivateKey(secKey: secKey, authenticationContext: laContext)
CryptoSuite has been designed to work with Swift Concurrency providng safe and easy to use way to integrate it to your project using async / await API.
Note
While CryptoSuite has been built with Swift concurrency support in mind, it is based upon Apple’s CryptoKit types that are not necessarily marked as Sendable
. Therefore if you choose to use strict Swift Concurrency settings in your project you will likely run into concurrency warning issues when handling CryptoKit types. If you wish to silence these kind of warnings you can override an implicit import of CryptoKit that is provided by the library by adding an explicit import of CryptoKit marked with @preconcurrency
attribute.
Contributions are very welcome 🙌
CryptoSuite is available under the Zero-Clause BSD license. See LICENSE.txt for details.