Skip to main content

HMAC Auth SDK for Java

SafeSky HMAC Authentication SDK for Java

Java 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

No external dependencies required for the core library! Uses only Java standard library.

Requirements:

  • Java 17+ (for text blocks and modern APIs)
<!-- For the example file only (optional), add Gson for JSON handling -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

Quick Start

import java.util.Map;

public class QuickStart {
    public static void main(String[] args) {
        // Your SafeSky API key
        String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
        
        // Generate authentication headers
        Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(
            apiKey,
            "GET",
            "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908",
            null  // body is null for GET requests
        );
        
        // Use with your HTTP client
        // headers contains: Authorization, X-SS-Date, X-SS-Nonce, X-SS-Alg
    }
}

Usage Examples

Example 1: GET Request (Nearby Aircraft) with HttpURLConnection
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000";

// Generate headers
Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

// Make request
HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
conn.setRequestMethod("GET");

// Add auth headers
for (Map.Entry<String, String> header : headers.entrySet()) {
    conn.setRequestProperty(header.getKey(), header.getValue());
}

int statusCode = conn.getResponseCode();
// Read response...
conn.disconnect();
Example 2: POST Request (Publish UAV Position)
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.Gson;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://uav-api.safesky.app/v1/uav";

// Build JSON body
JsonArray bodyArray = new JsonArray();
JsonObject uav = new JsonObject();
uav.addProperty("id", "my_uav_001");
uav.addProperty("altitude", 110);
uav.addProperty("latitude", 50.69378);
uav.addProperty("longitude", 4.39201);
uav.addProperty("last_update", System.currentTimeMillis() / 1000);
uav.addProperty("status", "AIRBORNE");
uav.addProperty("call_sign", "Test UAV");
uav.addProperty("ground_speed", 10);
uav.addProperty("course", 250);
uav.addProperty("vertical_rate", 5);
bodyArray.add(uav);

String body = new Gson().toJson(bodyArray);

// Generate headers (pass body for POST)
Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body);

// Make request
HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");

for (Map.Entry<String, String> header : headers.entrySet()) {
    conn.setRequestProperty(header.getKey(), header.getValue());
}

// Write body
try (OutputStream os = conn.getOutputStream()) {
    byte[] input = body.getBytes(StandardCharsets.UTF_8);
    os.write(input, 0, input.length);
}

int statusCode = conn.getResponseCode();
conn.disconnect();

Complete Examples

The SDK includes 5 complete examples in SafeSkyHmacExample.java:

  1. GET nearby aircraft - Retrieve traffic in a specific area
  2. POST UAV position - Submit UAV telemetry data
  3. POST UAV with query parameters - Submit UAV and get nearby traffic
  4. POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
  5. Manual step-by-step - Detailed authentication flow
Example 3: POST with Query Parameters
// URL includes query parameters
String url = "https://uav-api.safesky.app/v1/uav?return_nearby_traffic=true";
String body = /* JSON body */;

// Query params are automatically included in signature
Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body);
Example 4: POST Advisory (GeoJSON)
String url = "https://uav-api.safesky.app/v1/advisory";
long timestamp = System.currentTimeMillis() / 1000;

String body = """
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "id": "advisory_001",
        "max_altitude": 111,
        "last_update": %d,
        "call_sign": "Test Advisory",
        "remarks": "Testing"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[
          [4.38, 50.69],
          [4.40, 50.69],
          [4.40, 50.70],
          [4.38, 50.70],
          [4.38, 50.69]
        ]]
      }
    }
  ]
}
""".formatted(timestamp);

Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body);
Example 5: Using with Apache HttpClient
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908";

Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
    HttpGet request = new HttpGet(url);
    
    // Add auth headers
    for (Map.Entry<String, String> header : headers.entrySet()) {
        request.addHeader(header.getKey(), header.getValue());
    }
    
    httpClient.execute(request);
}
Example 6: Using with OkHttp
import okhttp3.*;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908";

Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

OkHttpClient client = new OkHttpClient();

Request.Builder requestBuilder = new Request.Builder()
    .url(url)
    .get();

// Add auth headers
for (Map.Entry<String, String> header : headers.entrySet()) {
    requestBuilder.addHeader(header.getKey(), header.getValue());
}

Response response = client.newCall(requestBuilder.build()).execute();
Example 7: Using with Spring RestTemplate
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://uav-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908";

Map<String, String> authHeaders = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();

// Add auth headers
authHeaders.forEach(headers::add);

HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);

API Reference

generateAuthHeaders(apiKey, method, url, body)Map<String, String>

Main function - Generates all HMAC authentication headers.

Parameters:

  • apiKey (String): Your SafeSky API key (e.g., ssk_live_...)
  • method (String): HTTP method (GET, POST, etc.)
  • url (String): Full URL including protocol, host, path, and query string
  • body (String): Request body for POST/PUT requests, null for GET requests

Returns: Map with authentication headers:

{
    "Authorization": "SS-HMAC Credential=...",
    "X-SS-Date": "2025-11-12T14:30:00.123Z",
    "X-SS-Nonce": "uuid-v4",
    "X-SS-Alg": "SS-HMAC-SHA256-V1"
}
Lower-Level Functions

For advanced use cases, you can use individual static methods:

  • deriveKid(String apiKey)String - Derives Key Identifier from API key
  • deriveHmacKey(String apiKey)byte[] - Derives HMAC signing key using HKDF
  • buildCanonicalRequest(...)String - Builds canonical request string
  • generateSignature(String canonicalRequest, byte[] hmacKey)String - Generates HMAC-SHA256 signature
  • generateNonce()String - Generates UUID v4 nonce
  • generateTimestamp()String - Generates ISO8601 timestamp with milliseconds

Authentication Flow

  1. Derive KID: KID = base64url(SHA256("kid:" + api_key)[0:16])
  2. Derive HMAC Key: HKDF-SHA256 with salt and info strings
  3. Generate Nonce: UUID v4 for replay protection
  4. Generate Timestamp: ISO8601 format with milliseconds
  5. Build Canonical Request:
    METHOD
    /path
    query_string
    host:hostname:port
    x-ss-date:timestamp
    x-ss-nonce:nonce
    
    body_hash_sha256_hex
    
  6. Sign: HMAC-SHA256(hmac_key, canonical_request) → base64
  7. Build Headers: Authorization header with KID, signed headers, and signature

Compiling and Running

Compile the library:
javac SafeSkyHmacAuth.java
Compile and run the example (requires Gson):
## Download Gson jar
wget https://repo1.maven.org/maven2/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar

## Compile
javac -cp gson-2.10.1.jar SafeSkyHmacAuth.java SafeSkyHmacExample.java

## Run
java -cp .:gson-2.10.1.jar SafeSkyHmacExample
Using with Maven:

Add to your pom.xml:

<dependencies>
    <!-- Optional: Only needed if using Gson for JSON handling -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>

Security Notes

  • Keep API keys secret: Never commit them to version control
  • Use environment variables: Store keys in secure configuration
  • 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, encoding issues)
  • URL includes all query parameters
  • Host header matches URL (include port for non-standard ports)
Replay Attack Detected
  • Nonce was already used within 15-minute window
  • Generate a fresh nonce for each request
Timestamp Out of Range
  • Server time differs by more than ±5 minutes
  • Synchronize your system clock with NTP
Authentication Failed
  • Verify API key is correct (starts with ssk_live_ or ssk_test_)
  • Check API key has required permissions for the endpoint
  • Ensure all headers are present

Running Examples

## Make sure server is running on localhost:8080 for local testing
## or update API_ENDPOINT to https://uav-api.safesky.app for production

java -cp .:gson-2.10.1.jar SafeSkyHmacExample

Support

For questions or issues:

  • Email: support@safesky.app
  • Documentation: https://docs.safesky.app