HIGH api key exposuredjangohmac signatures

Api Key Exposure in Django with Hmac Signatures

Api Key Exposure in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability

HMAC-based authentication in Django is often used to sign requests and verify integrity. When API keys are embedded in request headers or query parameters and signed with HMAC, exposure can occur if the signing flow or transport controls are weak. A common pattern is to include an api_key identifier in the signed payload or in a custom header such as X-API-Key, while the HMAC signature is computed over a canonical string that may include the method, path, timestamp, and body.

If the server-side verification does not properly scope the signed string to the exact combination of method, path, and key material, an attacker may be able to replay or manipulate requests. For example, including only the path and body in the signature while omitting the X-API-Key header means that swapping the key does not invalidate the signature if the path and body remain the same, enabling horizontal privilege issues across services that share a signing key or algorithm.

Another exposure vector is logging and error handling. If the full signed request, including the API key or signature, is logged in plaintext or exposed in error messages, an attacker who gains read access to logs can harvest keys. In Django, middleware that logs request headers without redaction can inadvertently persist API keys alongside signatures, making it easier to correlate and reuse them.

Timing attacks also matter. If signature verification short-circuits on the first mismatching byte, an attacker can use network timing differences to iteratively guess the signature. This is especially relevant when HMAC verification is implemented manually instead of using constant-time comparison utilities. Using Django’s hmac.compare_digest ensures that comparisons take time proportional to the length of the digest and do not leak information through timing channels.

Finally, if the key rotation or discovery mechanisms are weak — for example, exposing an endpoint that echoes the public part of the key or returning the same key identifier across environments — the effective secrecy of the HMAC diminishes. Proper scoping of the signed string, strict transport protections, and avoiding logging of sensitive headers reduce the likelihood of inadvertent exposure when using HMAC signatures in Django.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To reduce exposure risk, scope the signed string to include the HTTP method, the request path, a timestamp, and the API key identifier, and verify all components on the server. Use Django’s built-in hmac.compare_digest for constant-time comparison, and ensure middleware redacts sensitive headers from logs.

import base64
import hashlib
import hmac
import time
from django.http import HttpRequest, HttpResponse
from django.utils.crypto import constant_time_compare

def build_signature(api_key: str, api_secret: bytes, method: str, path: str, timestamp: str, body: str) -> str:
    message = f'{method}\n{path}\n{timestamp}\n{api_key}\n{body}'
    mac = hmac.new(api_secret, message.encode('utf-8'), hashlib.sha256)
    return base64.b64encode(mac.digest()).decode('utf-8')

def verify_request(request: HttpRequest, api_key: str, api_secret: bytes, received_signature: str, timestamp: str, skew_seconds: int = 300) -> bool:
    # Prevent replay by limiting timestamp window
    now = str(int(time.time()))
    if abs(int(now) - int(timestamp)) > skew_seconds:
        return False
    expected = build_signature(api_key, api_secret, request.method, request.path, timestamp, request.body.decode('utf-8') if request.body else '')
    return constant_time_compare(expected, received_signature)

class HmacAuthMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        api_key = request.META.get('HTTP_X_API_KEY')
        signature = request.META.get('HTTP_X_SIGNATURE')
        timestamp = request.META.get('HTTP_X_TIMESTAMP')
        if api_key and signature and timestamp:
            # Use a secure lookup for api_secret tied to api_key
            api_secret = self.get_secret_for_key(api_key)
            if api_secret and verify_request(request, api_key, api_secret, signature, timestamp):
                # Proceed only after verification
                response = self.get_response(request)
                # Ensure sensitive headers are not logged
                return response
        return HttpResponse(status=401)

    def get_secret_for_key(self, api_key: str) -> bytes:
        # Retrieve secret from a secure store, e.g., environment or vault
        # Example mapping; in practice use a secure lookup
        mapping = {'service-a': b'super-secret-key-1'}
        return mapping.get(api_key, b'')

On the client, construct the same canonical string and include the timestamp, key identifier, and signature in headers:

import base64
import hashlib
import hmac
import time
import requests

api_key = 'service-a'
api_secret = b'super-secret-key-1'
method = 'POST'
path = '/api/v1/orders'
timestamp = str(int(time.time()))
body = '{"item": "widget", "qty": 3}'
message = f'{method}\n{path}\n{timestamp}\n{api_key}\n{body}'
signature = base64.b64encode(hmac.new(api_secret, message.encode('utf-8'), hashlib.sha256).digest()).decode('utf-8')

headers = {
    'X-API-Key': api_key,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
    'Content-Type': 'application/json',
}
requests.post('https://api.example.com/api/v1/orders', json=body, headers=headers)

Additional hardening steps include redacting sensitive headers in Django logging configuration and rotating secrets via a secure workflow. These practices reduce the chance that an exposed key or log entry leads to further compromise.

Frequently Asked Questions

Should the API key be included in the HMAC signature string?
Yes, include the API key identifier in the signed string so that a different key produces a different signature, preventing key substitution across services.
How can I prevent replay attacks when using Hmac Signatures in Django?
Include a timestamp or nonce in the signed string and verify it server-side within an acceptable time window; reject requests with stale timestamps to mitigate replay.