HMAC Auth SDK for Kotlin
SafeSky HMAC Authentication SDK for Kotlin
Kotlin SDK for authenticating requests to the SafeSky API using HMAC-SHA256 signatures.
Click here to download the SDK
API Endpoint: All requests use
https://uav-api.safesky.app. Each API key is associated with either a sandbox or production environment. The gateway automatically routes your request to the correct backend based on the environment configured for your API key.
Installation
Gradle (Kotlin DSL)
Add to your build.gradle.kts:
dependencies {
implementation("com.safesky:hmac-auth:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
Maven
Add to your pom.xml:
<dependency>
<groupId>com.safesky</groupId>
<artifactId>hmac-auth</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-jvm</artifactId>
<version>1.6.2</version>
</dependency>
Quick Start
import com.safesky.authentication.SafeSkyHmacAuth
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun main() {
// Your SafeSky API key
val apiKey = "YOUR_SAFESKY_API_KEY_HEREHere"
// Generate authentication headers for a GET request
val headers = SafeSkyHmacAuth.generateAuthHeaders(
apiKey,
"GET",
"https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908"
)
// Make authenticated request
val client = HttpClient.newHttpClient()
val requestBuilder = HttpRequest.newBuilder()
.uri(URI("https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908"))
.GET()
headers.toMap().forEach { (key, value) ->
requestBuilder.header(key, value)
}
val response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
println(response.body())
}
Usage Examples
The SDK includes 4 complete examples in Example.kt:
- GET nearby aircraft - Retrieve traffic in a specific area
- POST UAV position - Submit UAV telemetry data
- POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
- Manual step-by-step - Detailed authentication flow
Example 1: GET Request
import com.safesky.authentication.SafeSkyHmacAuth
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun getNearbyAircraft() {
val apiKey = "YOUR_SAFESKY_API_KEY_HERE"
val url = "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000"
// Generate auth headers
val authHeaders = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url)
// Make request
val client = HttpClient.newHttpClient()
val requestBuilder = HttpRequest.newBuilder()
.uri(URI(url))
.GET()
.header("Content-Type", "application/json")
authHeaders.toMap().forEach { (key, value) ->
requestBuilder.header(key, value)
}
val response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
println("Status: ${response.statusCode()}")
println(response.body())
}
Example 2: POST Request
import com.safesky.authentication.SafeSkyHmacAuth
import kotlinx.serialization.json.*
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun postUavPosition() {
val apiKey = "YOUR_SAFESKY_API_KEY_HERE"
val url = "https://uav-api.safesky.app/v1/uav"
// Build request body
val bodyData = buildJsonArray {
addJsonObject {
put("id", "my_uav_001")
put("altitude", 110)
put("latitude", 50.69378)
put("longitude", 4.39201)
put("last_update", System.currentTimeMillis() / 1000)
put("status", "AIRBORNE")
put("call_sign", "Test UAV")
put("ground_speed", 10)
put("course", 250)
put("vertical_rate", 5)
}
}
val body = bodyData.toString()
// Generate auth headers (pass body for POST)
val authHeaders = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body)
// Make request
val client = HttpClient.newHttpClient()
val requestBuilder = HttpRequest.newBuilder()
.uri(URI(url))
.POST(HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/json")
authHeaders.toMap().forEach { (key, value) ->
requestBuilder.header(key, value)
}
val response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
println("Status: ${response.statusCode()}")
}
Example 3: POST Advisory (GeoJSON)
import com.safesky.authentication.SafeSkyHmacAuth
import kotlinx.serialization.json.*
fun postAdvisory() {
val apiKey = "YOUR_SAFESKY_API_KEY_HERE"
val url = "https://uav-api.safesky.app/v1/advisory"
val timestamp = System.currentTimeMillis() / 1000
val bodyData = buildJsonObject {
put("type", "FeatureCollection")
putJsonArray("features") {
addJsonObject {
put("type", "Feature")
putJsonObject("properties") {
put("id", "my_advisory_id1")
put("max_altitude", 111)
put("last_update", timestamp)
put("call_sign", "Advisory test with polygon")
put("remarks", "Inspection powerlines")
}
putJsonObject("geometry") {
put("type", "Polygon")
putJsonArray("coordinates") {
addJsonArray {
add(buildJsonArray { add(4.3948); add(50.6831) })
// ... more coordinates ...
}
}
}
}
addJsonObject {
put("type", "Feature")
putJsonObject("properties") {
put("id", "my_advisory_id2")
put("max_altitude", 150)
put("max_distance", 500)
put("last_update", timestamp)
put("call_sign", "Advisory test with a point")
put("remarks", "Inspection rails")
}
putJsonObject("geometry") {
put("type", "Point")
putJsonArray("coordinates") {
add(4.4)
add(50.7)
}
}
}
}
}
val body = bodyData.toString()
val authHeaders = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body)
// Make request (similar to Example 2)
}
Example 4: Manual Step-by-Step
import com.safesky.authentication.SafeSkyHmacAuth
import java.net.URI
fun manualAuthentication() {
val apiKey = "YOUR_SAFESKY_API_KEY_HERE"
// Step 1: Derive KID
val kid = SafeSkyHmacAuth.deriveKid(apiKey)
println("KID: $kid")
// Step 2: Derive HMAC key
val hmacKey = SafeSkyHmacAuth.deriveHmacKey(apiKey)
// Step 3: Generate timestamp and nonce
val timestamp = SafeSkyHmacAuth.generateTimestamp()
val nonce = SafeSkyHmacAuth.generateNonce()
// Step 4: Build canonical request
val canonicalRequest = SafeSkyHmacAuth.buildCanonicalRequest(
"GET",
"/v1/uav",
"lat=50.6970&lng=4.3908",
"api.safesky.app",
timestamp,
nonce,
""
)
// Step 5: Generate signature
val signature = SafeSkyHmacAuth.generateSignature(canonicalRequest, hmacKey)
println("Signature: $signature")
}
API Reference
Main Object: SafeSkyHmacAuth
Singleton object providing HMAC authentication functions.
generateAuthHeaders(apiKey, method, url, body = "")
Generates all HMAC authentication headers for a SafeSky API request.
Parameters:
apiKey(String): Your SafeSky API key (e.g.,ssk_live_...)method(String): HTTP method (GET,POST, etc.)url(String): Full request URL including protocol, host, path, and query stringbody(String, optional): Request body for POST/PUT requests (default: empty string)
Returns: SafeSkyAuthHeaders data class containing all required headers
Example:
val headers = SafeSkyHmacAuth.generateAuthHeaders(
"YOUR_SAFESKY_API_KEY_HERE",
"POST",
"https://uav-api.safesky.app/v1/uav",
Json.encodeToString(data)
)
Data Class: SafeSkyAuthHeaders
Container for authentication headers.
Properties:
Methods:
toMap()→Map<String, String>: Convert headers to map for HTTP requests
Helper Functions
deriveKid(apiKey) → String
Derives the KID (Key Identifier) from an API key.
Formula: KID = base64url(SHA256("kid:" + api_key)[0:16])
deriveHmacKey(apiKey) → ByteArray
Derives the HMAC signing key from an API key using HKDF-SHA256.
generateNonce() → String
Generates a cryptographically secure nonce (UUID format).
generateTimestamp() → String
Generates the current timestamp in ISO8601 format with milliseconds.
Format: YYYY-MM-DDTHH:MM:SS.sssZ
buildCanonicalRequest(...) → String
Builds the canonical request string for HMAC signature.
Parameters:
method(String): HTTP methodpath(String): Request pathqueryString(String): Query string without leading?host(String): Host header valuetimestamp(String): ISO8601 timestampnonce(String): Unique noncebody(String): Request body
generateSignature(canonicalRequest, hmacKey) → String
Generates HMAC-SHA256 signature for the canonical request.
Returns: Base64-encoded signature
Authentication Flow
- Derive KID:
KID = base64url(SHA256("kid:" + api_key)[0:16]) - Derive HMAC Key: HKDF-SHA256 with salt and info strings
- Generate Nonce: UUID v4 for replay protection
- Generate Timestamp: ISO8601 format with milliseconds
- Build Canonical Request:
METHOD /path query_string host:hostname:port x-ss-date:timestamp x-ss-nonce:nonce body_hash_sha256_hex - Sign:
HMAC-SHA256(hmac_key, canonical_request)→ base64 - Build Headers: Authorization header with KID, signed headers, and signature
Running Examples
## Using Gradle
./gradlew run
## Or with Gradle wrapper
gradle run
## Compile only
./gradlew build
Kotlin/JVM Support
- Kotlin 1.9+ (recommended)
- JVM 17+ (required)
- Kotlin 1.8+ (compatible)
Android Integration
This SDK works seamlessly with Android:
import com.safesky.authentication.SafeSkyHmacAuth
import okhttp3.OkHttpClient
import okhttp3.Request
class SafeSkyRepository(private val apiKey: String) {
private val client = OkHttpClient()
suspend fun getNearbyAircraft(lat: Double, lng: Double): List<Aircraft> {
val url = "https://uav-api.safesky.app/v1/uav?lat=$lat&lng=$lng&rad=20000"
val headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url)
val requestBuilder = Request.Builder().url(url)
headers.toMap().forEach { (key, value) ->
requestBuilder.addHeader(key, value)
}
val response = client.newCall(requestBuilder.build()).execute()
return if (response.isSuccessful) {
Json.decodeFromString(response.body?.string() ?: "[]")
} else {
emptyList()
}
}
}
Ktor Integration
import com.safesky.authentication.SafeSkyHmacAuth
import io.ktor.client.*
import io.ktor.client.request.*
suspend fun getNearbyAircraft(client: HttpClient, apiKey: String) {
val url = "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908"
val headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url)
val response = client.get(url) {
headers.toMap().forEach { (key, value) ->
header(key, value)
}
}
}
Security Notes
- Keep API keys secret: Never commit them to version control
- Use BuildConfig or resources: Store keys securely in Android
- HTTPS only: Always use HTTPS in production (api.safesky.app)
- Replay protection: Server validates nonces within 15-minute window
- Clock skew: Server allows ±5 minutes timestamp tolerance
Troubleshooting
Invalid Signature
- Check body is identical (no extra whitespace)
- Verify URL includes all query parameters
- Ensure host header matches URL (include port for non-standard ports)
Replay Attack Detected
- Nonce was already used within 15-minute window
- Generate fresh nonce for each request
Timestamp Out of Range
- Server time differs by more than ±5 minutes
- Synchronize system clock with NTP
Dependencies
- Kotlin Standard Library - Built-in
- kotlinx-serialization-json - JSON serialization (for examples)
- java.security - Built-in (SHA-256, HMAC)
- java.net.http - Built-in JDK 11+ (HTTP client)
Support
For questions or issues:
- Email: support@safesky.app
- Documentation: https://docs.safesky.app