Back to Documentation
Android Integration

Android Integration

Kotlin and Java integration guide for Android applications

Android SDK Guide - Kotlin

Setup

Add to your build.gradle (Module: app):

gradle
dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation 'com.google.code.gson:gson:2.10.1'
}

Basic Usage

kotlin
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import com.google.gson.Gson
import java.security.*
import javax.crypto.Cipher
import javax.crypto.spec.OAEPParameterSpec
import javax.crypto.spec.PSource
import java.util.Base64

class KeyClaimClient(
    private val apiKey: String,
    private val baseUrl: String = "https://keyclaim.org/api"
) {
    private val client = OkHttpClient()
    private val gson = Gson()
    
    // Step 1: Create a challenge
    fun createChallenge(ttl: Int = 30): String {
        val json = """
        {
            "ttl": $ttl
        }
        """.trimIndent()
        
        val requestBody = json.toRequestBody("application/json".toMediaType())
        val request = Request.Builder()
            .url("$baseUrl/challenge/create")
            .addHeader("Authorization", "Bearer $apiKey")
            .post(requestBody)
            .build()
        
        val response = client.newCall(request).execute()
        val responseBody = response.body?.string() ?: throw Exception("Empty response")
        val data = gson.fromJson(responseBody, Map::class.java)
        
        return data["challenge"] as String
    }
    
    // Step 2: Generate HMAC response
    fun generateResponseHMAC(challenge: String): String {
        val mac = javax.crypto.Mac.getInstance("HmacSHA256")
        val secretKey = javax.crypto.spec.SecretKeySpec(
            apiKey.toByteArray(),
            "HmacSHA256"
        )
        mac.init(secretKey)
        val hash = mac.doFinal(challenge.toByteArray())
        return hash.joinToString("") { "%02x".format(it) }
    }
    
    // Step 3: Decrypt challenge with private key (RSA)
    fun decryptChallenge(encryptedChallenge: String, privateKey: PrivateKey): String {
        val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
        cipher.init(Cipher.DECRYPT_MODE, privateKey)
        
        val encryptedBytes = Base64.getDecoder().decode(encryptedChallenge)
        val decryptedBytes = cipher.doFinal(encryptedBytes)
        
        return String(decryptedBytes)
    }
    
    // Step 4: Validate response
    fun validate(
        challenge: String,
        response: String,
        decryptedChallenge: String? = null
    ): Map<String, Any> {
        val jsonMap = mutableMapOf<String, Any>(
            "key" to apiKey,
            "challenge" to challenge,
            "response" to response
        )
        if (decryptedChallenge != null) {
            jsonMap["decryptedChallenge"] = decryptedChallenge
        }
        val json = gson.toJson(jsonMap)
        
        val requestBody = json.toRequestBody("application/json".toMediaType())
        val request = Request.Builder()
            .url("$baseUrl/challenge/validate")
            .post(requestBody)
            .build()
        
        val response = client.newCall(request).execute()
        val responseBody = response.body?.string() ?: throw Exception("Empty response")
        
        return gson.fromJson(responseBody, Map::class.java) as Map<String, Any>
    }
}

// Usage
val client = KeyClaimClient("kc_your_api_key")

// HMAC method
val challenge = client.createChallenge()
val response = client.generateResponseHMAC(challenge)
val result = client.validate(challenge, response)

if (result["valid"] == true) {
    println("✓ Validated successfully!")
}

// RSA method (with private key)
// val privateKey = loadPrivateKeyFromPEM("private_key.pem")
// If API key has a key pair assigned, challenge will be automatically encrypted
// val challenge = client.createChallenge()
// val decrypted = client.decryptChallenge(challenge, privateKey) // Only if encrypted
// val result = client.validate(challenge, decrypted, decrypted)

RSA Key Pair Generation

kotlin
import java.security.*
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.Base64

fun generateRSAKeyPair(keySize: Int = 2048): Pair<PrivateKey, PublicKey> {
    val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
    keyPairGenerator.initialize(keySize)
    val keyPair = keyPairGenerator.generateKeyPair()
    return Pair(keyPair.private, keyPair.public)
}

fun privateKeyToPEM(privateKey: PrivateKey): String {
    val keyFactory = KeyFactory.getInstance("RSA")
    val keySpec = keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec::class.java)
    val encoded = Base64.getEncoder().encode(keySpec.encoded)
    
    return "-----BEGIN PRIVATE KEY-----\n" +
           encoded.chunked(64).joinToString("\n") +
           "\n-----END PRIVATE KEY-----"
}

fun publicKeyToPEM(publicKey: PublicKey): String {
    val keyFactory = KeyFactory.getInstance("RSA")
    val keySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec::class.java)
    val encoded = Base64.getEncoder().encode(keySpec.encoded)
    
    return "-----BEGIN PUBLIC KEY-----\n" +
           encoded.chunked(64).joinToString("\n") +
           "\n-----END PUBLIC KEY-----"
}

// Generate and save
val (privateKey, publicKey) = generateRSAKeyPair(2048)
val privatePEM = privateKeyToPEM(privateKey)
val publicPEM = publicKeyToPEM(publicKey)

// Save to files or secure storage