HMAC-SHA256-Signaturen von SMS-Webhooks prüfen
So stellen Sie sicher, dass die Anfrage wirklich von DroidSender kommt und nicht von jemand anderem.
Zuletzt aktualisiert
| Header | Inhalt |
|---|---|
| X-DroidSender-Event | der Name des Ereignisses, derselbe wie im Body |
| X-DroidSender-Delivery | kennzeichnet einen Zustellversuch; zum Deduplizieren verwenden Sie event plus data.id (plus data.status bei sent, delivered und failed) |
| X-DroidSender-Signature | die Signatur, unten beschrieben |
| User-Agent | DroidSender-Webhooks/2.0 |
Jede Anfrage enthält den Header X-DroidSender-Signature in der Form t=<Unix-Sekunden>,v1=<hex>, wobei v1 der HMAC-SHA256 von "<t>.<roher Body>" mit dem Secret Ihres Webhooks ist.
const crypto = require("crypto");
function signatureIsValid(rawBody, header, secret) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
if (!match) return false;
const [, t, received] = match;
// der Zeitstempel ist Teil der signierten Daten: Eine abgefangene Zustellung
// lässt sich nicht morgen erneut einspielen, und t ist ohne das Secret nicht änderbar
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(t));
if (age > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
// Vergleich in konstanter Zeit: Ein einfaches === macht das Secret messbar
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex"),
);
}import hmac, hashlib, re, time
def signature_is_valid(raw_body: bytes, header: str, secret: str) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
if not m:
return False
t, received = m.group(1), m.group(2)
if abs(int(time.time()) - int(t)) > 300:
return False
expected = hmac.new(
secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)<?php
function signature_is_valid(string $rawBody, ?string $header, string $secret): bool {
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header ?? '', $m)) {
return false;
}
[, $t, $received] = $m;
if (abs(time() - (int) $t) > 300) {
return false;
}
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
// hash_equals, nie ==: Ein normaler Vergleich verrät Zeitunterschiede
return hash_equals($expected, $received);
}
// der rohe Body kommt aus php://input; $_POST ist schon geparst und hier nutzlos
$rawBody = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_DROIDSENDER_SIGNATURE'] ?? null;