Zum Inhalt

hmac-secure-link: Alternatives NGINX HMAC Secure Link Modul mit Unterstützung für OpenSSL-Hashes

Installation

Sie können dieses Modul in jeder RHEL-basierten Distribution installieren, einschließlich, aber nicht beschränkt auf:

  • RedHat Enterprise Linux 7, 8, 9 und 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 und Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-hmac-secure-link
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-hmac-secure-link

Aktivieren Sie das Modul, indem Sie Folgendes am Anfang von /etc/nginx/nginx.conf hinzufügen:

load_module modules/ngx_http_hmac_secure_link_module.so;

Dieses Dokument beschreibt nginx-module-hmac-secure-link v2.0.0 veröffentlicht am 02. April 2026.


Beschreibung

Das Nginx HMAC Secure Link Modul verbessert die Sicherheit und Funktionalität des Standardmoduls secure_link. Sichere Tokens werden unter Verwendung einer ordnungsgemäßen HMAC-Konstruktion (RFC 2104) mit jedem von OpenSSL 3.x unterstützten Hash-Algorithmus erstellt. Die verfügbaren Algorithmen hängen von den in Ihrer OpenSSL-Konfiguration geladenen Anbietern ab.

Standardanbieter (verfügbar out of the box in jeder OpenSSL 3.x-Installation): md5, sha1, sha224, sha256, sha384, sha512, sha512-224, sha512-256, sha3-224, sha3-256, sha3-384, sha3-512, shake128, shake256, blake2b512, blake2s256, sm3.

Legacy-Anbieter (erfordert, dass der OpenSSL Legacy-Anbieter explizit in openssl.cnf geladen wird; standardmäßig nicht in OpenSSL 3.x verfügbar): md4, mdc2, rmd160, gost.

Der empfohlene Algorithmus ist sha256 oder stärker. md5 und sha1 werden akzeptiert, sollten jedoch nicht in neuen Bereitstellungen verwendet werden.

Das HMAC wird als H(secret ⊕ opad, H(secret ⊕ ipad, message)) berechnet, anstatt das unsichere MD5(secret, message, expire) zu verwenden, das vom integrierten Modul verwendet wird.

Vorgefertigte Pakete (Ubuntu / Debian)

Vorgefertigte Pakete für dieses Modul sind kostenlos im GetPageSpeed-Repository erhältlich:

## Fügen Sie das Repository hinzu (Ubuntu-Beispiel — ersetzen Sie 'jammy' durch Ihre Version)
echo "deb [signed-by=/etc/apt/keyrings/getpagespeed.gpg] \
  https://extras.getpagespeed.com/ubuntu jammy main" \
  | sudo tee /etc/apt/sources.list.d/getpagespeed-extras.list

## Konfigurationsdirektiven

Alle Direktiven akzeptieren NGINX-Variablen und komplexe Werte.

### `secure_link_hmac`

**Kontext:** `http`, `server`, `location`

Gibt den Variablenausdruck an, dessen ausgewerteter Wert dem Format `<token>,<timestamp>[,<expires>]` folgen muss. **Der Feldtrennzeichen ist immer ein Komma und ist zwischen jedem Feld erforderlich.** Das Komma ist im Modulparser fest codiert; kein anderer Trenner wird hier unterstützt.

| Feld        | Beschreibung                                               |
|-------------|-----------------------------------------------------------|
| `token`     | Base64url-kodiertes HMAC (kein Padding `=`)               |
| `timestamp` | Anfrageerstellungszeit (siehe [Timestamp-Formate](#timestamp-formate)) |
| `expires`   | Optionale Lebensdauer in Sekunden; weglassen oder `0` für unbegrenzt |

```nginx
secure_link_hmac "$arg_st,$arg_ts,$arg_e";

Wichtig: Wenn secure_link_hmac aus Abfrageparametern zusammengesetzt wird ("$arg_st,$arg_ts,$arg_e"), dürfen die Zeitstempel- und Ablaufwerte selbst keine unescaped Kommas enthalten. ISO 8601- und Unix-Zeitstempel sind kommafrei und funktionieren ohne spezielle Behandlung. RFC 7231-Daten enthalten ein eingebettetes Komma (z. B. Sun, 06 Nov …); das Modul behandelt dies korrekt für das zweite Feld, aber Sie müssen das Komma URL-encodieren, wenn Sie ein RFC 7231-Datum in einem Abfrageparameter platzieren, damit $arg_ts auf die vollständige dekodierte Datumszeichenfolge aufgelöst wird (siehe Timestamp-Formate).

Kontext: http, server, location

Die Nachricht, deren HMAC verifiziert werden soll. Muss genau dem entsprechen, was der Client beim Berechnen des Tokens verwendet hat. Beinhaltet typischerweise die URI und den Zeitstempel, sodass Tokens URI-spezifisch und zeitgebunden sind.

Der Trenner zwischen den Feldern in der Nachricht wird vom Betreiber frei gewählt und kann jedes Byte oder jede Bytefolge sein — Pipe (|), Doppelpunkt (:), Schrägstrich (/), Bindestrich (-) oder sogar gar nichts. Das Modul behandelt secure_link_hmac_message als eine undurchsichtige Bytezeichenfolge und analysiert deren Inhalt niemals; der Trenner ist einfach Teil des HMAC-Vorabbildes.

Die einzige Anforderung ist, dass der auf der Serverseite gewählte Trenner identisch mit dem Trenner ist, den der Client beim Berechnen des HMAC verwendet hat. Die Verwendung eines Trenners, der in keinem der Feldwerte natürlich erscheinen kann (wie | für URIs und Unix-Zeitstempel), verringert das Risiko von Längenverlängerungsambiguitäten.

## Pipe-Trenner (empfohlen — kann nicht in einem URI-Pfad oder Unix-Zeitstempel erscheinen)
secure_link_hmac_message "$uri|$arg_ts|$arg_e";

## Doppelpunkt-Trenner
secure_link_hmac_message "$uri:$arg_ts:$arg_e";

## Kein Trenner (gültig, aber mehrdeutig, wenn Felder einen Zeichensatz teilen)
secure_link_hmac_message "$uri$arg_ts$arg_e";

Kontext: http, server, location

Der HMAC-Geheimschlüssel. Halten Sie dies aus der Versionskontrolle heraus.

secure_link_hmac_secret "my_very_secret_key";

Kontext: http, server, location
Standard: sha256

Der OpenSSL-Digest-Name, der für das HMAC verwendet wird.

secure_link_hmac_algorithm sha256;

Eingebettete Variablen

Wird nach der Verarbeitung der Direktive secure_link_hmac gesetzt. Mögliche Werte:

Wert Bedeutung
"1" Token ist kryptografisch gültig und der Link ist nicht abgelaufen
"0" Token ist gültig, aber der Link ist abgelaufen
(leer) Token fehlt, ist fehlerhaft, HMAC-Fehlanpassung oder Zeitstempel ungültig

Verwenden Sie diese Variable, um den Zugriff zu steuern. In der Produktion geben Sie denselben Fehlercode für alle fehlerhaften Fälle zurück, damit ein Angreifer nicht zwischen einem abgelaufenen Token und einem gefälschten unterscheiden kann:

if ($secure_link_hmac != "1") {
    return 403;
}

Hinweis: "1" und "0" sind literale einstellige Zeichenfolgen, keine Zahlen. Der leere / nicht gefundene Fall bedeutet, dass die Variable nicht gesetzt ist, nicht dass sie "" entspricht.

Der rohe Ablaufzeitraum-String (in Sekunden), wie er in der Anfrage empfangen wurde. Diese Variable wird nur gesetzt, wenn ein Ablauf in secure_link_hmac vorhanden war. Sie kann für Protokollierung oder bedingte Logik verwendet werden:

add_header X-Link-Expires $secure_link_hmac_expires;
  • Wenn der eingehende Wert "3600" war, enthält diese Variable "3600".
  • Wenn kein Ablauffeld vorhanden war, ist die Variable nicht gesetzt (not_found).
  • Diese Variable wird als Nebeneffekt der Auswertung von $secure_link_hmac befüllt; bewerten Sie zuerst $secure_link_hmac.

Ein frisch berechnetes base64url-kodiertes HMAC-Token (kein nachgestelltes =-Padding). Verwenden Sie diese Variable, wenn NGINX als Proxy fungiert, der authentifizierte Anfragen an ein Backend weiterleiten muss:

location ^~ /backend/ {
    set $expire 60;
    secure_link_hmac_message "$uri|$time_iso8601|$expire";
    secure_link_hmac_secret  "my_very_secret_key";
    secure_link_hmac_algorithm sha256;

    proxy_pass "http://backend$uri?st=$secure_link_hmac_token&ts=$time_iso8601&e=$expire";
}

Das Token ist base64url-kodiert ohne Padding, kompatibel mit URL-Abfrageparametern ohne weitere Escape-Zeichen.

Timestamp-Formate

Ein Zeitstempel sollte immer in der signierten Nachricht enthalten sein, um Replay-Angriffe zu verhindern. Drei Formate werden vom serverseitigen Parser akzeptiert. Clients können das verwenden, was am bequemsten ist.

ISO 8601 mit numerischem UTC-Offset (empfohlen)

YYYY-MM-DDThh:mm:ss+HH:MM
YYYY-MM-DDThh:mm:ss-HH:MM

Beispiele:

2025-06-01T14:30:00+00:00   # UTC
2025-06-01T17:30:00+03:00   # UTC+3 (Kiew/Istanbul)
2025-06-01T08:30:00-06:00   # UTC-6 (Chicago CDT)

Der Server konvertiert in UTC, bevor er vergleicht, sodass jeder gültige Offset akzeptiert wird.

ISO 8601 UTC (Z-Suffix)

YYYY-MM-DDThh:mm:ssZ

Beispiel: 2025-06-01T14:30:00Z

Entspricht +00:00, ist aber kürzer. Nginx's integrierte $time_iso8601-Variable gibt das Format +00:00 aus; für Z müssen Sie den Zeitstempel auf Anwendungsebene formatieren.

RFC 7231 / IMF-fixdate (HTTP-Datum)

Wie in RFC 7231 §7.1.1.1 angegeben. Alle RFC 7231-Daten sind implizit UTC; kein Offset wird angewendet.

Tag, DD Mon YYYY hh:mm:ss GMT

Beispiele:

Sun, 01 Jun 2025 14:30:00 GMT
Mon, 23 Mar 2026 08:00:00 GMT

Dabei ist Tag eine dreibuchstabige Abkürzung für den Wochentag (MonSun) und Mon (Monat) ist einer von Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec. Der Parser ist für beide Abkürzungen nicht groß-/kleinschreibungsempfindlich.

Hinweis: RFC 7231 definiert auch zwei veraltete Formate (RFC 850 und ANSI C asctime). Diese werden nicht unterstützt; nur das bevorzugte IMF-fixdate-Format wird akzeptiert.

Unix-Zeitstempel (einfache Ganzzahl)

Eine Zeichenfolge aus Dezimalziffern, die Sekunden seit der Unix-Epoche (1970-01-01T00:00:00Z) darstellt.

Beispiel: 1748785800

Dies ist das einfachste Format und funktioniert gut in Bash und Node.js. Der Parser ist streng: Das Zeitstempelfeld muss nur Dezimalziffern enthalten; jedes andere Zeichen führt zur Ablehnung.

Sicherheitsnotiz: Unix-Zeitstempel haben nur eine Auflösung von einer Sekunde. Verwenden Sie ISO 8601, wenn die Untersekundenpräzision wichtig ist oder wenn Sie eine bestimmte Zeitzone ausdrücken müssen.

Anwendungsbeispiel — Serverseite

location ^~ /files/ {
    # Die drei durch Kommas getrennten Felder: Token, Zeitstempel, Ablauf (Sekunden)
    secure_link_hmac "$arg_st,$arg_ts,$arg_e";

    # HMAC-Geheimschlüssel
    secure_link_hmac_secret "my_secret_key";

    # Die signierte Nachricht: URI + Zeitstempel + Ablauf
    secure_link_hmac_message "$uri|$arg_ts|$arg_e";

    # Hash-Algorithmus
    secure_link_hmac_algorithm sha256;

    # In der Produktion geben Sie nicht preis, ob das Token falsch oder abgelaufen war.
    # $secure_link_hmac == "1" → gültig und nicht abgelaufen
    # $secure_link_hmac == "0" → gültig, aber abgelaufen
    # $secure_link_hmac unset  → ungültig / fehlerhaft
    if ($secure_link_hmac != "1") {
        return 403;
    }

    rewrite ^/files/(.*)$ /files/$1 break;
}

Client-Seitenbeispiele

Perl — ISO 8601-Zeitstempel

perl_set $secure_token '
    sub {
        use Digest::SHA qw(hmac_sha256_base64);
        use POSIX qw(strftime);

        my $r       = shift;
        my $key     = "my_very_secret_key";
        my $expire  = 60;
        my $now     = time();

        # ISO 8601 mit numerischem UTC-Offset
        my $tz = strftime("%z", localtime($now));
        $tz =~ s/(\d{2})(\d{2})/$1:$2/;
        my $timestamp = strftime("%Y-%m-%dT%H:%M:%S", localtime($now)) . $tz;

        my $message = $r->uri . "|" . $timestamp . "|" . $expire;
        my $digest  = hmac_sha256_base64($message, $key);
        $digest     =~ tr(+/)(-_);           # base64 → base64url
        $digest     =~ s/=+$//;             # Padding entfernen

        return "st=$digest&ts=$timestamp&e=$expire";
    }
';

PHP — Unix-Zeitstempel

<?php
$secret    = 'my_very_secret_key';
$expire    = 60;
$algo      = 'sha256';
$timestamp = time();                       // Unix-Zeitstempel
$uri       = '/files/top_secret.pdf';
$message   = "{$uri}|{$timestamp}|{$expire}";

$token = base64_encode(hash_hmac($algo, $message, $secret, true));
$token = strtr($token, '+/', '-_');        // base64 → base64url
$token = rtrim($token, '=');              // Padding entfernen

$host = $_SERVER['HTTP_HOST'];
$url  = "https://{$host}{$uri}?st={$token}&ts={$timestamp}&e={$expire}";

PHP — ISO 8601-Zeitstempel

<?php
$secret    = 'my_very_secret_key';
$expire    = 60;
$algo      = 'sha256';
$timestamp = (new DateTimeImmutable('now', new DateTimeZone('UTC')))
               ->format(DateTimeInterface::RFC3339);  // "2025-06-01T14:30:00+00:00"
$uri       = '/files/top_secret.pdf';
$message   = "{$uri}|{$timestamp}|{$expire}";

$token = base64_encode(hash_hmac($algo, $message, $secret, true));
$token = strtr($token, '+/', '-_');
$token = rtrim($token, '=');

$url = "https://example.com{$uri}?st={$token}&ts=" . urlencode($timestamp) . "&e={$expire}";

PHP — RFC 7231 / IMF-fixdate-Zeitstempel

<?php
$secret    = 'my_very_secret_key';
$expire    = 60;
$algo      = 'sha256';
// RFC 7231 IMF-fixdate — immer UTC, immer "GMT"-Suffix
$timestamp = gmdate('D, d M Y H:i:s') . ' GMT';  // "Sun, 01 Jun 2025 14:30:00 GMT"
$uri       = '/files/top_secret.pdf';
$message   = "{$uri}|{$timestamp}|{$expire}";

$token = base64_encode(hash_hmac($algo, $message, $secret, true));
$token = strtr($token, '+/', '-_');
$token = rtrim($token, '=');

// URL-encode das RFC 7231-Datum (enthält Leerzeichen und Kommas)
$url = "https://example.com{$uri}?st={$token}&ts=" . rawurlencode($timestamp) . "&e={$expire}";

Node.js — Unix-Zeitstempel

const crypto = require('crypto');

const secret    = 'my_very_secret_key';
const expire    = 60;
const timestamp = Math.floor(Date.now() / 1000);   // Unix-Zeitstempel
const uri       = '/files/top_secret.pdf';
const message   = `${uri}|${timestamp}|${expire}`;

const token = crypto.createHmac('sha256', secret)
                    .update(message)
                    .digest('base64')
                    .replace(/=/g,  '')
                    .replace(/\+/g, '-')
                    .replace(/\//g, '_');

const url = `https://example.com${uri}?st=${token}&ts=${timestamp}&e=${expire}`;

Node.js — RFC 7231 / IMF-fixdate-Zeitstempel

const crypto = require('crypto');

const secret    = 'my_very_secret_key';
const expire    = 60;
// toUTCString() erzeugt das RFC 7231 IMF-fixdate-Format in allen modernen Laufzeiten
const timestamp = new Date().toUTCString();        // "Sun, 01 Jun 2025 14:30:00 GMT"
const uri       = '/files/top_secret.pdf';
const message   = `${uri}|${timestamp}|${expire}`;

const token = crypto.createHmac('sha256', secret)
                    .update(message)
                    .digest('base64')
                    .replace(/=/g,  '')
                    .replace(/\+/g, '-')
                    .replace(/\//g, '_');

const url = `https://example.com${uri}?st=${token}&ts=${encodeURIComponent(timestamp)}&e=${expire}`;

Python — ISO 8601-Zeitstempel (UTC Z-Suffix)

import hmac, hashlib, base64, urllib.parse
from datetime import datetime, timezone

secret    = b'my_very_secret_key'
expire    = 60
timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
uri       = '/files/top_secret.pdf'
message   = f'{uri}|{timestamp}|{expire}'.encode()

token = base64.urlsafe_b64encode(
            hmac.new(secret, message, hashlib.sha256).digest()
        ).rstrip(b'=').decode()

url = f'https://example.com{uri}?st={token}&ts={urllib.parse.quote(timestamp)}&e={expire}'

Bash — Unix-Zeitstempel

#!/bin/bash
SECRET="my_super_secret"
URI="/file/my_secret_file.txt"
TIMESTAMP="$(date +%s)"
EXPIRES=3600

MESSAGE="${URI}|${TIMESTAMP}|${EXPIRES}"
TOKEN="$(printf '%s' "$MESSAGE" \
         | openssl dgst -sha256 -hmac "$SECRET" -binary \
         | openssl base64 \
         | tr '+/' '-_' \
         | tr -d '=')"

echo "http://127.0.0.1${URI}?st=${TOKEN}&ts=${TIMESTAMP}&e=${EXPIRES}"

Proxy-Nutzung

Wenn NGINX als Proxy fungiert, der ein HMAC-Token zu ausgehenden Anfragen hinzufügen muss, verwenden Sie die Variable $secure_link_hmac_token:

location ^~ /backend_location/ {
    set $expire 60;

    secure_link_hmac_message "$uri|$time_iso8601|$expire";
    secure_link_hmac_secret  "my_very_secret_key";
    secure_link_hmac_algorithm sha256;

    proxy_pass "http://backend_server$uri?st=$secure_link_hmac_token&ts=$time_iso8601&e=$expire";
}

Hinweis: $time_iso8601 gibt einen ISO 8601-Zeitstempel mit einem numerischen UTC-Offset aus (z. B. 2025-06-01T14:30:00+00:00), den dieses Modul akzeptiert.

Sicherheitsnotizen

Trenner in secure_link_hmac Der Feldtrennzeichen innerhalb des Wertes der Direktive secure_link_hmac ist immer ein Komma. Die Zeitstempel- und Ablauffelder dürfen keine nackten Kommas enthalten (ISO 8601- und Unix-Zeitstempel sind sicher; RFC 7231-Zeitstempel werden durch die interne Komma-Überspring-Logik des Moduls behandelt, aber das eingebettete Komma muss URL-encoded/decoded intakt überstehen — siehe Timestamp-Formate).

Trenner in secure_link_hmac_message Wählen Sie einen Trenner, der in keinem der zusammengefügten Felder erscheinen kann. Pipe (|) ist ein guter Standard für URI + Unix-Zeitstempel-Kombinationen. Die Verwendung von gar keinem Trenner ist gültig, kann jedoch einen Längenverlängerungsangriff ermöglichen, bei dem ein gültiges Set von Feldwerten als ein anderes Set neu interpretiert wird; ein Trenner verhindert dies.

Weitere Empfehlungen - Fügen Sie immer einen Zeitstempel in die signierte Nachricht ein, um Replay-Angriffe zu verhindern. - Wählen Sie einen kurzen expires-Wert für Ihren Anwendungsfall (60–3600 Sekunden sind typisch für Download-Links). - Geben Sie denselben HTTP-Fehlercode (z. B. 403) für alle Fehlerfälle zurück — sowohl "0" (abgelaufen) als auch nicht gefunden (ungültig) — damit Angreifer nicht zwischen einem abgelaufenen Token und einem gefälschten unterscheiden können. - Verwenden Sie einen geheimen Schlüssel von mindestens 32 Bytes zufälliger Entropie. - Bevorzugen Sie sha256 oder stärker; vermeiden Sie md5 und sha1 für neue Bereitstellungen. - URL-encodieren Sie Zeitstempelwerte, die Zeichen enthalten, die in Abfragezeichenfolgen speziell sind: - ISO 8601 UTC-Offset + muss als %2B gesendet werden (ansonsten als Leerzeichen dekodiert) - RFC 7231-Leerzeichen müssen als %20 gesendet werden und das eingebettete Komma als %2C

GitHub

Sie finden möglicherweise zusätzliche Konfigurationstipps und Dokumentationen für dieses Modul im GitHub-Repository für nginx-module-hmac-secure-link.