Skip to main content

Authentication

Getting Started

You should have received a single API key from SafeSky (e.g., ssk_live_... or ssk_test_...).

From this single key, both the credential identifier (KID) and the HMAC signing key are automatically derived using cryptographic functions. You don't need to manage separate components - just store and protect your API key.

Keep your API key secure and never expose it in client-side code or public repositories.

⚠️ Important Notice: The legacy X-API-Key header authentication method is deprecated and will be removed in an upcoming release. All integrations must migrate to HMAC authentication as described below. Please update your implementation as soon as possible to avoid service interruption.

HMAC Authentication

SafeSky API uses HMAC (Hash-based Message Authentication Code) for request authentication. This scheme provides several key benefits:

  • Security: Credentials are never sent over the network; only a cryptographic signature is transmitted
  • Integrity: Ensures requests haven't been tampered with during transit
  • Replay Protection: Timestamps prevent replay attacks
  • Stateless: No session management required on the server side

HMAC uses the SHA-256 hashing algorithm to create a unique signature for each request based on the request content and your API secret.

SDKs and Libraries

To simplify integration, SafeSky provides official SDKs for multiple platforms:

These SDKs handle HMAC signature generation, request signing, and error handling automatically. We recommend using an SDK whenever possible to reduce implementation complexity and potential security issues.

Manual HMAC Implementation

If an SDK is not available for your platform, you can implement HMAC authentication manually.

API Key Derivation

SafeSky uses a single API key from which both the credential identifier (KID) and the HMAC signing key are derived:

  1. Derive KID (Credential Identifier)

    kid = base64url(SHA256("kid:" + api_key)[0:16])
    
    • Take first 16 bytes of SHA-256 hash
    • Encode as base64url (using - instead of +, _ instead of /, no padding =)
  2. Derive HMAC Signing Key

    hmac_key = HKDF-SHA256(api_key, salt="safesky-hmac-salt-v1", info="auth-v1", len=32)
    
    • Uses HKDF (HMAC-based Key Derivation Function) with SHA-256
    • Produces a 32-byte signing key
Required Headers

Each authenticated request must include the following headers:

Authorization: SS-HMAC Credential=<kid>/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=<base64-signature>
X-SS-Date: <ISO8601-timestamp>
X-SS-Nonce: <uuid-v4>
X-SS-Alg: SS-HMAC-SHA256-V1
Signature Generation Process
  1. Generate Request Metadata

    • timestamp: ISO8601 format (e.g., 2025-11-12T12:00:00.000Z)
    • nonce: UUID v4 (e.g., 123e4567-e89b-12d3-a456-426614174000)
  2. Create the Canonical Request String

    Build the canonical string with newline separators:

    <HTTP-METHOD>\n
    <REQUEST-PATH>\n
    <QUERY-STRING>\n
    host:<host-header>\n
    x-ss-date:<timestamp>\n
    x-ss-nonce:<nonce>\n
    \n
    <hex-sha256-of-body>
    
    • HTTP-METHOD: Uppercase HTTP method (GET, POST, PUT, DELETE)
    • REQUEST-PATH: URI path only (e.g., /v1/uav)
    • QUERY-STRING: Query parameters sorted alphabetically (e.g., lat=50.6970&lng=4.3908) or empty string
    • host: Host header value (e.g., api.safesky.app)
    • x-ss-date: ISO8601 timestamp
    • x-ss-nonce: UUID v4 nonce
    • Empty line before body hash
    • Body hash: SHA-256 of request body in lowercase hex (empty string hash for GET/DELETE)
  3. Generate HMAC Signature

    Compute the HMAC-SHA256 signature:

    signature_bytes = HMAC-SHA256(hmac_key, canonical_request)
    signature = base64(signature_bytes)
    
  4. Build Authorization Header

    Construct the Authorization header:

    Authorization: SS-HMAC Credential=<kid>/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=<signature>
    
Example Implementation
Python
import hmac
import hashlib
import base64
import uuid
from datetime import datetime, timezone

def derive_kid(api_key):
    """Derive KID from API key"""
    kid_string = f"kid:{api_key}"
    hash_bytes = hashlib.sha256(kid_string.encode()).digest()
    first_16 = hash_bytes[:16]
    kid = base64.b64encode(first_16).decode()
    return kid.replace('+', '-').replace('/', '_').replace('=', '')

def derive_hmac_key(api_key):
    """Derive HMAC key using HKDF-SHA256"""
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.kdf.hkdf import HKDF
    
    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=b"safesky-hmac-salt-v1",
        info=b"auth-v1"
    )
    return hkdf.derive(api_key.encode())

def build_canonical_request(method, path, query_string, host, timestamp, nonce, body):
    """Build canonical request string"""
    # Hash the body
    body_hash = hashlib.sha256((body or "").encode()).hexdigest()
    
    # Build canonical request
    canonical = (
        f"{method.upper()}\n"
        f"{path}\n"
        f"{query_string or ''}\n"
        f"host:{host}\n"
        f"x-ss-date:{timestamp}\n"
        f"x-ss-nonce:{nonce}\n"
        f"\n"
        f"{body_hash}"
    )
    return canonical

def generate_signature(canonical_request, hmac_key):
    """Generate HMAC-SHA256 signature"""
    signature = hmac.new(hmac_key, canonical_request.encode(), hashlib.sha256).digest()
    return base64.b64encode(signature).decode()

# Example usage
API_KEY = "your_api_key_here"
METHOD = "GET"
URL = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908"
BODY = ""

# Parse URL
from urllib.parse import urlparse
parsed = urlparse(URL)
host = parsed.netloc
path = parsed.path
query = parsed.query

# Generate metadata
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
nonce = str(uuid.uuid4())

# Derive credentials
kid = derive_kid(API_KEY)
hmac_key = derive_hmac_key(API_KEY)

# Build and sign request
canonical = build_canonical_request(METHOD, path, query, host, timestamp, nonce, BODY)
signature = generate_signature(canonical, hmac_key)

# Headers
headers = {
    "Authorization": f"SS-HMAC Credential={kid}/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature={signature}",
    "X-SS-Date": timestamp,
    "X-SS-Nonce": nonce,
    "X-SS-Alg": "SS-HMAC-SHA256-V1"
}
Java

See the complete SDK implementation at wiki/hmac/java-sdk/SafeSkyHmacAuth.java

Important Considerations
  • Clock Synchronization: Ensure your system clock is synchronized with NTP. The API will reject requests with timestamps outside ±5 minutes clock skew tolerance.
  • Character Encoding: Always use UTF-8 encoding for all string operations.
  • Body Serialization: For POST/PUT requests, use the exact body bytes that will be sent. Don't modify or pretty-print the JSON after signing.
  • Query String: Must be in the canonical request as a separate line (sorted alphabetically if multiple parameters).
  • Signature Format: Base64-encoded (not hexadecimal).
  • Nonce Uniqueness: Each nonce can only be used once within a 15-minute window to prevent replay attacks.
  • Header Names: All custom headers use X-SS- prefix (not X-SafeSky-).
  • Algorithm: Must be SS-HMAC-SHA256-V1 in the X-SS-Alg header.
Error Responses

Authentication failures will return HTTP 401 with error messages such as:

  • Missing or invalid HMAC headers: Required headers are missing or malformed
  • Invalid signature: The signature doesn't match the expected value
  • Timestamp outside acceptable range: The timestamp is more than ±5 minutes from server time
  • Replay attack detected - nonce already used: The nonce has been used in a previous request
  • Invalid credential - key not found: The KID is unknown or the API key is inactive
Testing Your Implementation

To verify your implementation:

  1. Key Derivation: Verify your KID and HMAC key derivation matches the SDK

    • Test KID generation with a known API key
    • Ensure base64url encoding is correct (use - and _, no padding)
    • Verify HKDF implementation produces correct 32-byte key
  2. Canonical Request: Build a canonical request and compare with SDK

    • Verify newline characters are correct (\n)
    • Check body hash is lowercase hexadecimal SHA-256
    • Ensure empty line before body hash
  3. Signature Generation: Compare your HMAC-SHA256 output

    • Use base64 encoding (not hexadecimal)
    • Verify the signature in Authorization header format
  4. Clock Sync: Ensure your system clock is accurate

    • Use date -u +"%Y-%m-%dT%H:%M:%S.000Z" to check ISO8601 format
    • Server allows ±5 minutes tolerance
  5. Test Request: Start with a simple GET request

    • Use a test endpoint like /v1/uav?lat=50&lng=4
    • Verify all four required headers are present

For additional support, contact SafeSky technical support or refer to: