Sicherung der Server‑zu‑Server‑Integrität mit HMAC‑Signaturen in Django/DRF

Wenn Server miteinander kommunizieren, stellt sich die Frage: „Ist die Anfrage wirklich von mir?“ und „Wurde sie unterwegs verändert?“ In diesem Beitrag zeigen wir, wie man in einer Django/DRF‑Umgebung HMAC‑basierte Authentifizierung einsetzt, um die Integrität und Vertrauenswürdigkeit von Server‑zu‑Server‑Anfragen zu gewährleisten.

Insbesondere werden wir erläutern:

  • Was ist das Ziel dieser Technik?
  • Welche Angriffe kann sie verhindern?
  • Warum sie für Client‑Server‑Kommunikation ungeeignet ist (Grenzen)
  • Wie man hmac_mixin.py + CBV in einem realen Django/DRF‑Projekt einsetzt

Was ist HMAC‑Signatur‑Authentifizierung?



HMAC (Hash‑based Message Authentication Code) ist ein Verfahren, bei dem ein geheimer Schlüssel + Nachricht verwendet wird, um einen Signaturwert (signature) zu erzeugen.

  • Zwei Server (A, B) teilen sich einen secret key.
  • Server A sendet eine POST‑Anfrage an Server B:
  • Der Body und ein Timestamp werden zu einer HMAC‑Signatur zusammengefasst.
  • Diese Signatur wird in einem HTTP‑Header (z. B. X-HMAC-Signature) mitgeschickt.
  • Server B berechnet mit demselben secret key die Signatur erneut und vergleicht sie:
  • Gleich → „Dies ist eine legitime Anfrage von A“
  • Ungleich → „Die Anfrage wurde verändert oder gefälscht“

Damit prüft HMAC gleichzeitig:

  • Integrität – Der Inhalt wurde nicht verändert.
  • Authentifizierung – Nur derjenige, der den geheimen Schlüssel kennt, kann die Signatur erzeugen.

※ Voraussetzung: Der geheime Schlüssel darf ausschließlich zwischen den Servern geteilt werden.


Zweck der Technik und zu verhindende Angriffe

1. Verhinderung von Body‑Manipulation

Angenommen, ein Angreifer fängt den Datenverkehr ab. Er könnte:

  • Den Body leicht verändern, um z. B. Geld auf ein anderes Konto zu überweisen.
  • Parameter ändern, um höhere Beträge oder andere Optionen zu nutzen.

Der empfangende Server berechnet erneut die HMAC‑Signatur. Sobald sich der Body um einen einzigen Buchstaben ändert, verschwindet die Signatur, und die Anfrage wird abgelehnt.

2. Verhinderung von Server‑Spoofing

Ein Angreifer, der vorgibt, ein legitimer Server zu sein, kann ohne den geheimen Schlüssel keine gültige Signatur erzeugen. Der empfangende Server erkennt dies sofort.

3. Schutz vor Replay‑Angriffen (Timestamp/Nonce)

Ein Angreifer könnte eine gültige Anfrage einfach erneut senden. Deshalb fügen wir häufig einen Timestamp (X-HMAC-Timestamp) oder einen Nonce hinzu und prüfen:

  • Ist die Anfrage zu alt? → Ablehnen.
  • Wurde der Nonce bereits verwendet? → Ablehnen.

Damit wird Replay‑Angriff reduziert.

※ HMAC allein löst nicht alle Probleme – HTTPS/TLS bleibt die Basis.


Warum HMAC nicht für Client‑Server‑Kommunikation geeignet ist



„Für Client‑Server‑Kommunikation ist HMAC ungeeignet, weil der Schlüssel im Client-Code exponiert werden kann.“

1. Schlüssel kann nicht verborgen werden

  • Mobile Apps, SPA‑Frontends (JS), Desktop‑Apps enthalten den Schlüssel im Bundle/JavaScript.
  • Ein Angreifer kann die App dekompilieren oder den JS‑Code inspizieren und den Schlüssel extrahieren.

Einmal exponiert, kann der Angreifer beliebige HMAC‑Signaturen erzeugen, und der Server kann nicht mehr unterscheiden, ob die Anfrage vom echten Client oder vom Angreifer stammt.

2. HMAC ist keine Verschlüsselung

HMAC schützt nur die Integrität und Authentifizierung, verschlüsselt jedoch den Body nicht. Sensible Daten müssen weiterhin über TLS geschützt werden.


Wann sollte man HMAC‑basierte Server‑zu‑Server‑Authentifizierung einsetzen?

1. Django‑App → separater DRF‑Auth‑Server

  • Mehrere Services nutzen einen gemeinsamen Auth‑/User‑Server (DRF).
  • Die Haupt‑Django‑App sendet POST‑Anfragen an den Auth‑Server.
  • Durch HMAC‑Signatur kann der Auth‑Server sicherstellen, dass die Anfrage von der vertrauenswürdigen Django‑App stammt.

2. Django‑App → AI‑Inference‑Server (DRF, FastAPI, etc.)

  • Die Django‑App fungiert als Front‑/Back‑End.
  • Schwergewichtige AI‑Inference‑Aufgaben werden an einen separaten Server delegiert.
  • Durch HMAC‑Signatur wird die interne Kommunikation gesichert und gefälschte Anfragen verhindert.

Praktisches Pattern in Django/DRF: hmac_mixin.py + CBV

Ein sauberer Ansatz in Projekten:

  1. Lege hmac_mixin.py im Projekt‑/App‑Verzeichnis an.
  2. Extrahiere die gemeinsame HMAC‑Signatur‑Logik als Mixin.
  3. Erstelle CBVs, die den Mixin erben und post_with_hmac aufrufen.

1. hmac_mixin.py – Gemeinsame Signatur + POST‑Methode

# hmac_mixin.py
import hmac
import hashlib
import json
import time

import requests
from django.conf import settings


class HMACRequestMixin:
    # settings.py: HMAC_SECRET_KEY = "super-secret-key-from-env"
    HMAC_SECRET_KEY = settings.HMAC_SECRET_KEY.encode("utf-8")

    HMAC_HEADER_NAME = "X-HMAC-Signature"
    HMAC_TIMESTAMP_HEADER = "X-HMAC-Timestamp"
    HMAC_ALGORITHM = hashlib.sha256

    def build_hmac_signature(self, body: bytes, timestamp: str) -> str:
        """Erzeugt die HMAC‑Signatur aus Body und Timestamp."""
        message = timestamp.encode("utf-8") + b"." + body
        digest = hmac.new(self.HMAC_SECRET_KEY, message, self.HMAC_ALGORITHM).hexdigest()
        return digest

    def post_with_hmac(self, url: str, payload: dict, timeout: int = 5):
        """Sendet eine HMAC‑signierte POST‑Anfrage an die gegebene URL."""
        body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
        timestamp = str(int(time.time()))
        signature = self.build_hmac_signature(body, timestamp)

        headers = {
            "Content-Type": "application/json",
            self.HMAC_HEADER_NAME: signature,
            self.HMAC_TIMESTAMP_HEADER: timestamp,
        }

        response = requests.post(url, data=body, headers=headers, timeout=timeout)
        response.raise_for_status()
        return response

2. Beispiel‑CBV für den Sender

# views.py
from django.conf import settings
from django.http import JsonResponse
from django.views import View

from .hmac_mixin import HMACRequestMixin


class AIInferenceRequestView(HMACRequestMixin, View):
    """Empfängt Client‑Anfrage, sendet HMAC‑signierte Anfrage an AI‑Server."""

    def post(self, request, *args, **kwargs):
        data = json.loads(request.body.decode("utf-8"))
        payload = {
            "text": data.get("text", ""),
            "user_id": request.user.id if request.user.is_authenticated else None,
        }

        url = settings.AI_SERVER_URL  # z. B. "https://ai-service.internal/v1/infer"

        try:
            ai_response = self.post_with_hmac(url, payload)
        except requests.RequestException as e:
            return JsonResponse({"detail": "AI server error", "error": str(e)}, status=502)

        return JsonResponse(ai_response.json(), status=ai_response.status_code)

Empfänger‑Seite: HMAC‑Authentifizierungs‑Klasse in DRF

# authentication.py
import hmac
import hashlib
import time

from django.conf import settings
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class HMACSignatureAuthentication(BaseAuthentication):
    SECRET_KEY = settings.HMAC_SECRET_KEY.encode("utf-8")
    HMAC_HEADER_NAME = "X-HMAC-Signature"
    HMAC_TIMESTAMP_HEADER = "X-HMAC-Timestamp"
    ALGORITHM = hashlib.sha256
    MAX_SKEW_SECONDS = 60

    def _build_signature(self, body: bytes, timestamp: str) -> str:
        message = timestamp.encode("utf-8") + b"." + body
        return hmac.new(self.SECRET_KEY, message, self.ALGORITHM).hexdigest()

    def authenticate(self, request):
        signature = request.headers.get(self.HMAC_HEADER_NAME)
        timestamp = request.headers.get(self.HMAC_TIMESTAMP_HEADER)

        if not signature or not timestamp:
            raise AuthenticationFailed("Missing HMAC headers")

        try:
            ts = int(timestamp)
        except ValueError:
            raise AuthenticationFailed("Invalid timestamp")

        now = int(time.time())
        if abs(now - ts) > self.MAX_SKEW_SECONDS:
            raise AuthenticationFailed("Request timestamp too old")

        expected_signature = self._build_signature(request.body, timestamp)

        if not hmac.compare_digest(signature, expected_signature):
            raise AuthenticationFailed("Invalid HMAC signature")

        return (None, None)

Anwendung in einer DRF‑View

# views.py (DRF‑Server)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny

from .authentication import HMACSignatureAuthentication


class AIInferenceView(APIView):
    authentication_classes = [HMACSignatureAuthentication]
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        input_text = request.data.get("text", "")
        result = {"answer": f"AI result for: {input_text}"}
        return Response(result)

Damit haben Sie ein klares Muster:

  • Sender: HMACRequestMixinpost_with_hmac
  • Empfänger: HMACSignatureAuthentication → Validierung

Fazit

  • HMAC‑Signaturen nutzen einen gemeinsamen geheimen Schlüssel, um Integrität und Authentifizierung von Server‑zu‑Server‑Anfragen zu prüfen.
  • Sie verhindern Body‑Manipulation, Server‑Spoofing und Replay‑Angriffe (mit Timestamp/Nonce).
  • Für Client‑Server‑Kommunikation ist HMAC ungeeignet, da der Schlüssel im Client-Code exponiert werden kann.
  • In Django/DRF‑Projekten empfiehlt sich die Verwendung eines Mixins (hmac_mixin.py) für das Senden und einer eigenen Authentifizierungs‑Klasse für das Empfangen.

image