Key Management
The IDK provides a unified key management system through the KeyManagerService. This service abstracts the details of different key storage backends, allowing you to generate, store, and use cryptographic keys consistently across platforms and providers.
Key management in the IDK covers the full lifecycle of cryptographic keys: generating new key pairs, persisting them in a key store, retrieving them by alias or key ID, and using them for signing, encryption, and key agreement. Because multiple KMS backends can be registered at once (software, hardware, cloud), the KeyManagerService acts as a router that delegates each operation to the correct provider.
KeyManagerService Overview
The KeyManagerService is the central entry point for all cryptographic key operations. It delegates actual key operations to registered KMS providers while presenting a consistent interface.
Key capabilities include:
- Key generation with configurable algorithms
- Key storage and retrieval
- Signing and verification operations
- Encryption and decryption
- Key wrapping and unwrapping
- Key agreement for establishing shared secrets
- Provider capability querying
Accessing the Service
The KeyManagerService is available from the session graph:
- Android/Kotlin
- iOS/Swift
val keyManager = session.graph.keyManagerService
let keyManager = session.graph.keyManagerService
Generating Keys
When generating a key, you choose the signature algorithm (which determines the curve or key size) and optionally target a specific KMS provider. If you pass null for providerId, the service picks the default provider. The alias is a human-readable identifier you can later use to look up the key.
Generate cryptographic key pairs using the generateKeyAsync method:
- Android/Kotlin
- iOS/Swift
import com.sphereon.crypto.core.SignatureAlgorithm
import com.sphereon.crypto.jose.JwkUse
import com.sphereon.crypto.core.KeyOperations
// Generate an ECDSA P-256 signing key
val signingKeyPair = keyManager.generateKeyAsync(
providerId = null, // Use default provider
alias = "my-signing-key",
use = JwkUse.sig,
keyOperations = arrayOf(KeyOperations.SIGN, KeyOperations.VERIFY),
alg = SignatureAlgorithm.ECDSA_SHA256,
keyVisibility = KeyVisibility.PRIVATE
)
// Generate with a specific provider
val awsKeyPair = keyManager.generateKeyAsync(
providerId = "aws",
alias = "my-aws-key",
use = JwkUse.sig,
alg = SignatureAlgorithm.ECDSA_SHA256
)
import SphereonCrypto
// Generate an ECDSA P-256 signing key
let signingKeyPair = try await keyManager.generateKeyAsync(
providerId: nil, // Use default provider
alias: "my-signing-key",
use: .sig,
keyOperations: [.sign, .verify],
alg: .ecdsaSha256,
keyVisibility: .private_
)
// Generate with a specific provider
let awsKeyPair = try await keyManager.generateKeyAsync(
providerId: "aws",
alias: "my-aws-key",
use: .sig,
alg: .ecdsaSha256
)
Supported Algorithms
The IDK supports the following signature algorithms:
| Algorithm | Description | Curve/Size |
|---|---|---|
ECDSA_SHA256 | ECDSA with SHA-256 | P-256 |
ECDSA_SHA384 | ECDSA with SHA-384 | P-384 |
ECDSA_SHA512 | ECDSA with SHA-512 | P-521 |
RSA_SHA256 | RSA PKCS#1 v1.5 with SHA-256 | 2048+ bits |
RSA_SHA384 | RSA PKCS#1 v1.5 with SHA-384 | 2048+ bits |
RSA_SHA512 | RSA PKCS#1 v1.5 with SHA-512 | 2048+ bits |
RSA_SSA_PSS_SHA256_MGF1 | RSA-PSS with SHA-256 | 2048+ bits |
RSA_SSA_PSS_SHA384_MGF1 | RSA-PSS with SHA-384 | 2048+ bits |
RSA_SSA_PSS_SHA512_MGF1 | RSA-PSS with SHA-512 | 2048+ bits |
ManagedKeyPair
After generating a key, you receive a ManagedKeyPair containing both COSE and JOSE representations. This dual format means you can use the same key material with CBOR-based protocols (like mDL/mdoc) and JSON-based protocols (like JWS) without converting between formats yourself. For hardware-backed keys, the private key fields will be null because the key material never leaves the secure element.
- Android/Kotlin
- iOS/Swift
// ManagedKeyPair contains both formats
val keyPair: ManagedKeyPair = keyManager.generateKeyAsync(...)
// Access COSE format
val cosePublicKey = keyPair.cose.publicCoseKey
val cosePrivateKey = keyPair.cose.privateCoseKey // May be null for hardware keys
// Access JOSE/JWK format
val publicJwk = keyPair.jose.publicJwk
val privateJwk = keyPair.jose.privateJwk // May be null for hardware keys
// Key identifiers
val kid = keyPair.kid
val alias = keyPair.alias
val providerId = keyPair.providerId
// ManagedKeyPair contains both formats
let keyPair: ManagedKeyPair = try await keyManager.generateKeyAsync(...)
// Access COSE format
let cosePublicKey = keyPair.cose.publicCoseKey
let cosePrivateKey = keyPair.cose.privateCoseKey // May be nil for hardware keys
// Access JOSE/JWK format
let publicJwk = keyPair.jose.publicJwk
let privateJwk = keyPair.jose.privateJwk // May be nil for hardware keys
// Key identifiers
let kid = keyPair.kid
let alias = keyPair.alias
let providerId = keyPair.providerId
Key Info Types
Different operations require key info in different encodings. The ManagedKeyInfoType wrapper lets you convert a ManagedKeyPair into a typed key info object scoped to either COSE or JOSE, and with either public or private visibility. Use cborToManagedKeyInfo when working with COSE/CBOR protocols and joseToManagedKeyInfo for JWS/JWE workflows.
- Android/Kotlin
- iOS/Swift
// Convert to ManagedKeyInfo for COSE operations
val coseKeyInfo: ManagedKeyInfoType<CoseKeyType> = keyPair.cborToManagedKeyInfo(
visibility = KeyVisibility.PUBLIC
)
// Convert to ManagedKeyInfo for JOSE operations
val joseKeyInfo: ManagedKeyInfoType<JwkType> = keyPair.joseToManagedKeyInfo(
visibility = KeyVisibility.PUBLIC
)
// Generic conversion with encoding specification
val keyInfo = keyPair.toManagedKeyInfo<CoseKeyType>(
visibility = KeyVisibility.PRIVATE,
keyEncoding = KeyEncoding.COSE
)
// Access key info properties
val providerId = keyInfo.providerId
val alias = keyInfo.alias
val signatureAlgorithm = keyInfo.signatureAlgorithm
val x5c = keyInfo.x5c // X.509 certificate chain if available
// Convert to ManagedKeyInfo for COSE operations
let coseKeyInfo: ManagedKeyInfoType = keyPair.cborToManagedKeyInfo(
visibility: .public_
)
// Convert to ManagedKeyInfo for JOSE operations
let joseKeyInfo: ManagedKeyInfoType = keyPair.joseToManagedKeyInfo(
visibility: .public_
)
// Access key info properties
let providerId = keyInfo.providerId
let alias = keyInfo.alias
let signatureAlgorithm = keyInfo.signatureAlgorithm
let x5c = keyInfo.x5c // X.509 certificate chain if available
Signing Data
Create signatures using stored keys:
- Android/Kotlin
- iOS/Swift
// Get key info for signing (needs private key access)
val keyInfo = keyPair.cborToManagedKeyInfo(visibility = KeyVisibility.PRIVATE)
// Sign raw bytes
val data = "Hello, World!".encodeToByteArray()
val signature = keyManager.createRawSignature(
keyInfo = keyInfo,
input = data,
requireX5Chain = false
)
// Sign with X.509 certificate chain requirement
val signatureWithCert = keyManager.createRawSignature(
keyInfo = keyInfo,
input = data,
requireX5Chain = true // Requires associated certificate
)
// Get key info for signing (needs private key access)
let keyInfo = keyPair.cborToManagedKeyInfo(visibility: .private_)
// Sign raw bytes
let data = "Hello, World!".data(using: .utf8)!
let signature = try await keyManager.createRawSignature(
keyInfo: keyInfo,
input: data,
requireX5Chain: false
)
// Sign with X.509 certificate chain requirement
let signatureWithCert = try await keyManager.createRawSignature(
keyInfo: keyInfo,
input: data,
requireX5Chain: true // Requires associated certificate
)
Verifying Signatures
Verify signatures against public keys:
- Android/Kotlin
- iOS/Swift
// Get public key info for verification
val publicKeyInfo = keyPair.cborToManagedKeyInfo(visibility = KeyVisibility.PUBLIC)
// Verify the signature
val isValid = keyManager.isValidRawSignature(
keyInfo = publicKeyInfo,
input = data,
signature = signature
)
if (isValid) {
println("Signature is valid")
} else {
println("Signature verification failed")
}
// Get public key info for verification
let publicKeyInfo = keyPair.cborToManagedKeyInfo(visibility: .public_)
// Verify the signature
let isValid = try await keyManager.isValidRawSignature(
keyInfo: publicKeyInfo,
input: data,
signature: signature
)
if isValid {
print("Signature is valid")
} else {
print("Signature verification failed")
}
Key Retrieval
You can look up keys by alias (the human-readable name you assigned at generation time) or enumerate all keys in the store. When retrieving a key, you need to supply the provider ID so the key store knows which backend to query. If you only ever use the default provider, keyManager.defaultProviderId() gives you the right value.
Retrieve previously generated keys using the key store:
- Android/Kotlin
- iOS/Swift
// List all keys in the key store
val allKeys = keyManager.keyStore.listKeys()
for (keyInfo in allKeys) {
println("Key: ${keyInfo.alias}, Provider: ${keyInfo.providerId}")
}
// Get a specific key
val keyInfo = KeyInfo<CoseKeyType>(
alias = "my-signing-key",
providerId = keyManager.defaultProviderId()
)
val existingKey = keyManager.keyStore.getKey(keyInfo)
// List all keys in the key store
let allKeys = try await keyManager.keyStore.listKeys()
for keyInfo in allKeys {
print("Key: \(keyInfo.alias), Provider: \(keyInfo.providerId)")
}
// Get a specific key
let keyInfo = KeyInfo(
alias: "my-signing-key",
providerId: keyManager.defaultProviderId()
)
let existingKey = try await keyManager.keyStore.getKey(keyInfo: keyInfo)
Key Deletion
Remove keys that are no longer needed:
- Android/Kotlin
- iOS/Swift
// Delete a specific key
val keyInfo = KeyInfo<CoseKeyType>(
alias = "my-signing-key",
providerId = keyManager.defaultProviderId()
)
val deleted = keyManager.keyStore.deleteKey(keyInfo)
if (deleted) {
println("Key deleted successfully")
}
// Delete a specific key
let keyInfo = KeyInfo(
alias: "my-signing-key",
providerId: keyManager.defaultProviderId()
)
let deleted = try await keyManager.keyStore.deleteKey(keyInfo: keyInfo)
if deleted {
print("Key deleted successfully")
}
Provider Management
The KeyManagerService can have multiple KMS providers registered simultaneously. For example, you might have a software provider for development and a hardware-backed provider for production. You can look up providers by ID, find one that supports a specific algorithm, or query by capability requirements like hardware backing.
Query and manage KMS providers:
- Android/Kotlin
- iOS/Swift
// Get default provider ID
val defaultProvider = keyManager.defaultProviderId()
// List all registered providers
val providerIds = keyManager.getProviderIds()
// Get a specific provider
val provider = keyManager.getProviderById("software")
// Find provider by algorithm support
val ecdsaProvider = keyManager.getKmsBySignatureAlgorithm(SignatureAlgorithm.ECDSA_SHA256)
// Query providers by capabilities
val queryResult = keyManager.queryProvider(
KmsProviderQuery(
signatureAlgorithm = SignatureAlgorithm.ECDSA_SHA256,
requireHardwareBacking = true
)
)
if (queryResult.isOk) {
val provider = queryResult.value.provider
println("Found provider: ${provider.id}")
} else {
println("No matching provider found")
}
// Get default provider ID
let defaultProvider = keyManager.defaultProviderId()
// List all registered providers
let providerIds = keyManager.getProviderIds()
// Get a specific provider
let provider = keyManager.getProviderById(id: "software")
// Find provider by algorithm support
let ecdsaProvider = keyManager.getKmsBySignatureAlgorithm(
alg: .ecdsaSha256
)
// Query providers by capabilities
let queryResult = try await keyManager.queryProvider(
query: KmsProviderQuery(
signatureAlgorithm: .ecdsaSha256,
requireHardwareBacking: true
)
)
if queryResult.isOk {
let provider = queryResult.value.provider
print("Found provider: \(provider.id)")
} else {
print("No matching provider found")
}
Provider Capabilities
Before generating keys or performing operations, you can inspect what each provider actually supports. This is useful when your application runs on multiple platforms where different providers may be available, or when you need to verify that a provider supports a specific curve or key operation before attempting it.
Query what operations a provider supports:
- Android/Kotlin
- iOS/Swift
// Get all capabilities from all providers
val capsResult = keyManager.getAllCapabilities(includeDisabled = false)
if (capsResult.isOk) {
for (cap in capsResult.value.capabilities) {
println("Provider: ${cap.providerId}")
println(" Storage types: ${cap.storageTypes.joinToString()}")
println(" Supports import: ${cap.supportsKeyImport}")
println(" Supports export: ${cap.supportsKeyExport}")
println(" Hardware backing: ${cap.supportsHardwareBacking}")
println(" Supported curves: ${cap.supportedCurves.joinToString()}")
println(" Supports signing: ${cap.supportsSigning()}")
println(" Supports encryption: ${cap.supportsEncryption()}")
}
} else {
println("Failed to get capabilities")
}
// Get all capabilities from all providers
let capsResult = try await keyManager.getAllCapabilities(includeDisabled: false)
if capsResult.isOk {
for cap in capsResult.value.capabilities {
print("Provider: \(cap.providerId)")
print(" Storage types: \(cap.storageTypes)")
print(" Supports import: \(cap.supportsKeyImport)")
print(" Supports export: \(cap.supportsKeyExport)")
print(" Hardware backing: \(cap.supportsHardwareBacking)")
print(" Supported curves: \(cap.supportedCurves)")
print(" Supports signing: \(cap.supportsSigning())")
print(" Supports encryption: \(cap.supportsEncryption())")
}
} else {
print("Failed to get capabilities")
}
Key Lifecycle Best Practices
When managing cryptographic keys:
Generate keys with appropriate algorithms. ECDSA_SHA256 (ES256) is a good default for signing, offering a balance of security and performance.
Use meaningful aliases. Include context like purpose and creation date to simplify key management.
Scope keys appropriately. Keys are tied to their provider and cannot be transferred between providers.
Consider key rotation. For long-lived applications, implement key rotation where new keys are generated periodically and old keys retained for verification until they expire.
Use hardware backing when available. The mobile provider uses platform secure storage (iOS Secure Enclave, Android Keystore) which cannot export private keys.