Back to Documentation
iOS Integration

iOS Integration

Swift integration guide for iOS applications

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)