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