> For the complete documentation index, see [llms.txt](https://adatrack.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://adatrack.gitbook.io/docs/documentation/iot-ingestion/hmac.md).

# HMAC Authentication

To ensure the integrity and authenticity of your telemetry data, AdaTrack uses **HMAC-SHA256 (Hash-Based Message Authentication Code)** for all UDP packets. This cryptographic method ensures that only your authorized devices can send data to your account and that the data has not been tampered with in transit.

### Why HMAC?

In a connectionless UDP environment, traditional session-based security (like TLS/TCP) is too heavy for constrained IoT devices. HMAC provides a stateless, low-overhead alternative that:

* **Verifies Identity:** Confirms the sender possesses the unique shared secret.
* **Ensures Integrity:** Detects if any part of the packet was modified.
* **Prevents Replays:** Using a timestamp prevents attackers from recording and re-sending old packets.

### The Authentication Flow

1. **Shared Secret:** Registering a device generates a unique 32-byte secret key, shown **once** as a 64-character hex string. Store it on the device; AdaTrack keeps its own copy.
2. **Signing (Device):** The device builds `DeviceID (16 B) + Timestamp (8 B) + Payload` and computes HMAC-SHA256 over those bytes using the secret key.
3. **Transmission:** The device appends the 32-byte signature and sends the datagram via UDP.
4. **Verification (Backend):**
   * AdaTrack extracts the Device ID and Timestamp and rejects the packet if the timestamp differs from server time by more than the replay window (default 5 seconds, past or future).
   * It fetches the shared secret for the device (from a high-speed in-memory cache, falling back to the database).
   * It recomputes the HMAC over the received bytes and performs a **constant-time comparison** with the received signature.
   * If they match, the packet is accepted; otherwise it is dropped without a reply.

### Packet Format with HMAC

| Section       | Size     | Description                                                                        |
| ------------- | -------- | ---------------------------------------------------------------------------------- |
| **Header**    | 24 Bytes | `DeviceID` (16 B, raw UUID bytes) and `Timestamp` (8 B, UNIX seconds, big-endian). |
| **Payload**   | N Bytes  | Your actual sensor telemetry.                                                      |
| **Signature** | 32 Bytes | The result of `HMAC_SHA256(SecretKey, Header + Payload)`, as raw bytes.            |

### Getting the Details Right

Most "invalid signature" problems come from one of these:

| Mistake                                                    | Correct                                                 |
| ---------------------------------------------------------- | ------------------------------------------------------- |
| Using the 64-character hex **text** as the key             | Hex-decode it and use the resulting **32 bytes**        |
| Sending the signature as hex text                          | Append the raw 32-byte digest                           |
| Little-endian timestamp (the default on ESP32, STM32, AVR) | Write the timestamp **big-endian**                      |
| Timestamp in milliseconds                                  | Use **seconds**                                         |
| Device ID as the UUID text                                 | Use the **16 raw bytes** (strip the dashes, hex-decode) |
| Signing only the payload                                   | Sign `DeviceID + Timestamp + Payload`                   |

### Implementation Example (Python)

```python
import hashlib
import hmac
import struct
import time
import uuid

device_id = uuid.UUID("YOUR_DEVICE_UUID").bytes
secret_key = bytes.fromhex("YOUR_64_HEX_CHARACTER_KEY")

# Payload: temperature (int16, 0.01 C), battery (uint8), lat/lon (int32, 1e-6 deg), rssi (int8)
payload = struct.pack(">hBiib", 2550, 87, 48148598, 17107748, -71)

timestamp = struct.pack(">Q", int(time.time()))        # seconds, big-endian
message = device_id + timestamp + payload
signature = hmac.new(secret_key, message, hashlib.sha256).digest()

packet = message + signature                           # send this datagram
```

For complete, tested clients in Python, Node.js, Go, Arduino/ESP32 and STM32, including a known-answer test vector for validating your own signing code, see [Firmware & Client Examples](https://github.com/AdaTrack/adatrack_platform/tree/main/docs/gitbook/documentation/iot-ingestion/firmware/README.md).

### Best Practices

* **Never Share Keys:** Treat your HMAC keys as passwords. Never include them in your source code repository or share them over unencrypted channels.
* **Unique Keys per Device:** Do not use the same key for multiple devices. If one device is compromised, you only need to rotate its specific key.
* **Synchronize Clocks:** AdaTrack rejects packets whose timestamp differs from server time by more than the replay window (**5 seconds** by default; self-hosted operators can change `REPLAY_WINDOW_SECONDS`). Use NTP, a GPS receiver or the cellular network time, and do not send until the clock is valid.
* **Replay Scope:** The replay window limits how old a captured packet can be, but a packet replayed inside the window is not rejected as a duplicate. Do not rely on UDP packets for actions where a duplicate would be harmful.
* **Constant-Time Comparison:** When writing your own verification logic, always use constant-time comparison functions to prevent timing attacks.
