iOS SDK Guide (Swift)
Setup
Add to your Package.swift or use CocoaPods:
swift
// Package.swift
dependencies: [
.package(url: "https://github.com/apple/swift-crypto.git", from: "2.0.0")
]Or CocoaPods:
ruby
pod 'CryptoSwift'Basic Usage
swift
import Foundation
import CryptoKit
class KeyClaimClient {
private let apiKey: String
private let baseUrl: String
init(apiKey: String, baseUrl: String = "https://keyclaim.org/api") {
self.apiKey = apiKey
self.baseUrl = baseUrl
}
// Step 1: Create a challenge
func createChallenge(ttl: Int = 30) async throws -> String {
let url = URL(string: "\(baseUrl)/challenge/create")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
let body: [String: Any] = [
"ttl": ttl
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
return json["challenge"] as! String
}
// Step 2: Generate HMAC response
func generateResponseHMAC(challenge: String) -> String {
let key = SymmetricKey(data: apiKey.data(using: .utf8)!)
let challengeData = challenge.data(using: .utf8)!
let signature = HMAC<SHA256>.authenticationCode(for: challengeData, using: key)
return Data(signature).map { String(format: "%02x", $0) }.joined()
}
// Step 3: Decrypt challenge with private key (RSA)
func decryptChallenge(encryptedChallenge: String, privateKey: SecKey) throws -> String {
guard let encryptedData = Data(base64Encoded: encryptedChallenge) else {
throw NSError(domain: "KeyClaim", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid base64"])
}
var error: Unmanaged<CFError>?
guard let decryptedData = SecKeyCreateDecryptedData(
privateKey,
.rsaEncryptionOAEPSHA256,
encryptedData as CFData,
&error
) as Data? else {
throw error?.takeRetainedValue() as Error? ?? NSError(domain: "KeyClaim", code: -1)
}
return String(data: decryptedData, encoding: .utf8)!
}
// Step 4: Validate response
func validate(
challenge: String,
response: String,
decryptedChallenge: String? = nil
) async throws -> [String: Any] {
let url = URL(string: "\(baseUrl)/challenge/validate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var body: [String: Any] = [
"key": apiKey,
"challenge": challenge,
"response": response
]
if let decrypted = decryptedChallenge {
body["decryptedChallenge"] = decrypted
}
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONSerialization.jsonObject(with: data) as! [String: Any]
}
}
// Usage
let client = KeyClaimClient(apiKey: "kc_your_api_key")
Task {
do {
// HMAC method
let challenge = try await client.createChallenge()
let response = client.generateResponseHMAC(challenge: challenge)
let result = try await client.validate(challenge: challenge, response: response)
if result["valid"] as? Bool == true {
print("✓ Validated successfully!")
}
} catch {
print("Error: \(error)")
}
}RSA Key Pair Generation
swift
import Security
func generateRSAKeyPair(keySize: Int = 2048) throws -> (privateKey: SecKey, publicKey: SecKey) {
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: keySize,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: false
]
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw NSError(domain: "KeyClaim", code: -1)
}
return (privateKey, publicKey)
}
func exportPrivateKey(_ privateKey: SecKey) throws -> String {
var error: Unmanaged<CFError>?
guard let keyData = SecKeyCopyExternalRepresentation(privateKey, &error) as Data? else {
throw error!.takeRetainedValue() as Error
}
let base64 = keyData.base64EncodedString()
return "-----BEGIN PRIVATE KEY-----\n\(base64)\n-----END PRIVATE KEY-----"
}
func exportPublicKey(_ publicKey: SecKey) throws -> String {
var error: Unmanaged<CFError>?
guard let keyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
throw error!.takeRetainedValue() as Error
}
let base64 = keyData.base64EncodedString()
return "-----BEGIN PUBLIC KEY-----\n\(base64)\n-----END PUBLIC KEY-----"
}
// Generate
let (privateKey, publicKey) = try generateRSAKeyPair()
let privatePEM = try exportPrivateKey(privateKey)
let publicPEM = try exportPublicKey(publicKey)