HMAC Auth SDK for Java
Java SDK for authenticating requests to the SafeSky API using HMAC-SHA256 signatures.
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://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908",
// "https://public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908", // Production
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://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://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:
- GET nearby aircraft - Retrieve traffic in a specific area
- POST UAV position - Submit UAV telemetry data
- POST UAV with query parameters - Submit UAV and get nearby traffic
- POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
- Manual step-by-step - Detailed authentication flow
Example 3: POST with Query Parameters
// URL includes query parameters
String url = "https://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://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://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://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://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 stringbody(String): Request body for POST/PUT requests,nullfor 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 keyderiveHmacKey(String apiKey)→byte[]- Derives HMAC signing key using HKDFbuildCanonicalRequest(...)→String- Builds canonical request stringgenerateSignature(String canonicalRequest, byte[] hmacKey)→String- Generates HMAC-SHA256 signaturegenerateNonce()→String- Generates UUID v4 noncegenerateTimestamp()→String- Generates ISO8601 timestamp with milliseconds
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
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_orssk_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://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