Mit dem Web Bot Auth (WBA)-Protokoll können Sie die Identität Ihres Agenten oder Bots mithilfe von HTTP-Nachrichtensignaturen (RFC 9421) kryptografisch nachweisen.
Wenn Sie ausgehende Anfragen signieren, können Dienste wie Google Cloud Fraud Defense (die Weiterentwicklung von reCAPTCHA) Ihren Agenten direkt verifizieren, ohne sich nur auf IP-Adressen oder User-Agent-Strings zu verlassen.
Führen Sie die folgenden Aufgaben aus, um Ihren Agenten bei WBA zu authentifizieren:
- Generieren Sie ein Ed25519-Schlüsselpaar und formatieren Sie den öffentlichen Schlüssel als JSON Web Key (JWK)-Set.
- Hosten Sie Ihr Verzeichnis mit öffentlichen Schlüsseln an einem bekannten HTTPS-Endpunkt.
- Signieren Sie jede ausgehende HTTP-Anfrage mit Ihrem privaten Schlüssel.
- Optional: Signieren Sie den experimentellen
Agent-Context-Header, um die Reputation zwischen Mandanten zu isolieren, die einen Signaturschlüssel gemeinsam nutzen.
Ed25519-Schlüsselpaar generieren
Für die WBA ist ein asymmetrisches Ed25519-Schlüsselpaar (RFC 8032) erforderlich. Sie halten den privaten Schlüssel geheim, um ausgehende HTTP-Anfragen zu signieren, und veröffentlichen den öffentlichen Schlüssel in einem JSON Web Key (JWK) Set (RFC 7517).
Schlüsselpaar mit OpenSSL und jwker generieren
Sie können ein Ed25519-Schlüsselpaar mit OpenSSL generieren und den öffentlichen Schlüssel mit dem Open-Source-Tool jwker in einen JWK konvertieren:
Generieren Sie einen privaten Ed25519-Schlüssel im PEM-Format:
openssl genpkey -algorithm ed25519 -out private-key.pemExtrahieren Sie den öffentlichen Schlüssel im PEM-Format:
openssl pkey -in private-key.pem -pubout -out public-key.pemWenn Sie
jwkernoch nicht installiert haben, installieren Sie es gemäß der Installationsanleitung fürjwker.Konvertieren Sie den öffentlichen PEM-Schlüssel mit
jwkerin eine JWK-Datei:jwker public-key.pem public-key.jwkMit diesem Befehl wird ein einzelnes JWK-Objekt in
public-key.jwkgeschrieben:{ "kty": "OKP", "crv": "Ed25519", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" }Verpacken Sie das JWK-Objekt in ein JWK-Set-
keys-Array (jwks.json) und fügen Sie die erforderlichen Felderalg,useundkidhinzu:{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "alg": "EdDSA", "use": "sig", "kid": "OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw", "x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs" } ] }
Feldreferenz für öffentliches JWK-Set
Jedes öffentliche Schlüsselobjekt im JWK-Satz-Array keys unterstützt die folgenden Felder:
| Feld | Wert | Beschreibung |
|---|---|---|
kty |
"OKP" |
Schlüsseltyp. Muss "OKP" (Oktett-Schlüsselpaar) sein, wie in RFC 8037 definiert. |
crv |
"Ed25519" |
Kryptografische Kurve. Muss "Ed25519" lauten. |
alg |
"EdDSA" |
Algorithmus, der für die Verwendung mit dem Schlüssel vorgesehen ist. Muss "EdDSA" lauten. |
use |
"sig" |
Verwendungszweck des öffentlichen Schlüssels. Legen Sie "sig" (Signatur) fest. |
x |
"BASE64URL_PUBLIC_KEY" |
Der 32-Byte-Ed25519-Rohschlüssel, codiert mit nicht aufgefülltem base64url (RFC 4648, Abschnitt 5). |
kid |
"KEY_ID" |
Eindeutige Schlüssel-ID, die mit dem Parameter keyid in Ihren signierten Anfragen übereinstimmt. Verwenden Sie den SHA-256-JWK-Fingerabdruck (nicht aufgefülltes base64url) gemäß RFC 7638, um die Kompatibilität mit anderen Anbietern zu gewährleisten. |
nbf |
UNIX_SECONDS |
Optional. Unix-Zeitstempel in Sekunden, vor dem der Schlüssel ungültig ist. |
exp |
UNIX_SECONDS |
Optional. Unix-Zeitstempel in Sekunden, nach denen der Schlüssel abläuft. Prüfer lehnen abgelaufene Schlüssel ab. |
Öffentliches Schlüsselverzeichnis hosten
Prüfer rufen Ihre öffentlichen Schlüssel von einem bekannten HTTPS-Endpunkt in der Domain Ihres Agents ab.
Anforderungen an Endpunkte und Header
Konfigurieren Sie Ihren Webserver so, dass er die folgenden Anforderungen erfüllt, um Ihr Schlüsselverzeichnis zu hosten:
- URL-Pfad: Stellen Sie die JWK-Set-JSON unter
https://YOUR_AGENT_DOMAIN/.well-known/http-message-signatures-directorybereit. - Transportsicherheit: Stellen Sie den Endpunkt über HTTPS auf dem Standardport
443bereit. Leiten Sie Anfragen nicht an einen anderen Host oder ein anderes Schema weiter. - Inhaltstyp: Legen Sie den Antwortheader
Content-Typeaufapplication/http-message-signatures-directory+json(oderapplication/json) fest. - Caching: Legen Sie den Antwortheader
Cache-Controlmit einermax-age-Anweisung fest (z. B.Cache-Control: public, max-age=3600). - Schlüsselrotation: Bevor Sie Anfragen mit einem neuen privaten Schlüssel signieren, fügen Sie den öffentlichen JWK dem
keys-Array hinzu und warten Sie, bis die TTL vonCache-Controlabgelaufen ist. Entfernen Sie widerrufene oder abgelaufene Schlüssel aus demkeys-Array.
Das folgende Beispiel zeigt eine HTTP-Antwort von GET /.well-known/http-message-signatures-directory:
HTTP/1.1 200 OK
Content-Type: application/http-message-signatures-directory+json
Cache-Control: public, max-age=3600
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"alg": "EdDSA",
"use": "sig",
"kid": "OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw",
"x": "16JuFxlu7PYOFULk_W6t3sZO4NF8Yj-2QpBxCAiXQfs"
}
]
}
Ausgehende HTTP-Anfrage signieren
Wenn Sie eine ausgehende HTTPS-Anfrage mit WBA authentifizieren möchten, hängen Sie drei erforderliche Header an: Signature-Agent, Signature-Input und Signature.
HTTP-Header
Signature-Agent: Gibt den HTTPS-Ursprung Ihres Schlüsselverzeichnisses als String in Anführungszeichen an:Signature-Agent: "https://agent.example.com"Signature-Input: Ordnet ein Signaturlabel (z. B.sig1) einer inneren Liste von abgedeckten HTTP-Komponenten und Signaturmetadatenparametern zu:Signature-Input: sig1=("@authority" "signature-agent");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"Fügen Sie die folgenden abgedeckten Komponenten und Parameter hinzu:
- Abgedeckte Komponenten:
"@authority": erforderlich. Bindet die Signatur an den Zielhost (z. B.www.example.com)."signature-agent": erforderlich. Bindet die Signatur an denSignature-Agent-Header.
- Metadatenparameter:
created: Aktueller Unix-Zeitstempel in Sekunden (Ganzzahl).expires: Unix-Zeitstempel für den Ablauf in Sekunden (Ganzzahl). Legen Sieexpiresauf maximal 300 Sekunden (5 Minuten) nachcreatedfest.keyid: Derkid-String Ihres öffentlichen Schlüssels aus Ihrem JWK-Satz.tag: Muss"web-bot-auth"sein.
- Abgedeckte Komponenten:
Signature: Ordnet dasselbe Signaturlabel (sig1) der base64-codierten Ed25519-Signatur zu, die als RFC 8941-Bytesequenz formatiert ist (in:Doppelpunkte eingeschlossen):Signature: sig1=:BASE64_ENCODED_SIGNATURE:
Signaturbasis erstellen
Erstellen Sie vor dem Signieren den kanonischen Signaturbasisstring, der in RFC 9421, Abschnitt 2.5 definiert ist. Jede abgedeckte Komponente wird in einer eigenen Zeile in Kleinbuchstaben angezeigt, gefolgt von der Zeile @signature-params ohne nachfolgenden Zeilenumbruch:
"@authority": www.example.com
"signature-agent": "https://agent.example.com"
"@signature-params": ("@authority" "signature-agent");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"
Signieren Sie die UTF-8-Bytes dieses Signaturbasisstrings mit Ihrem privaten Ed25519-Schlüssel.
Go-Beispiel
Im folgenden Beispiel wird die von OpenSSL generierte private-key.pem-Datei geladen und ein ausgehender *http.Request mit dem Standardpaket crypto/ed25519 von Go und github.com/dunglas/httpsfv signiert:
package main
import (
"crypto/ed25519"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"net/http"
"strings"
"time"
"github.com/dunglas/httpsfv"
)
// loadPrivateKey parses a PKCS#8 PEM-encoded Ed25519 private key
// generated by OpenSSL.
func loadPrivateKey(pemBytes []byte) (ed25519.PrivateKey, error) {
// Decode the PEM armor to get the raw DER bytes.
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
// OpenSSL generates private keys in PKCS#8 format by default.
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// Confirm that the parsed key is an Ed25519 private key.
privKey, ok := key.(ed25519.PrivateKey)
if !ok {
return nil, fmt.Errorf("not an Ed25519 private key")
}
return privKey, nil
}
// signRequestEd25519 signs req with WBA headers.
func signRequestEd25519(
req *http.Request,
privKey ed25519.PrivateKey,
keyID string,
) error {
const (
// sigLabel links the Signature-Input and Signature entries.
sigLabel = "sig1"
// agentURL is the HTTPS origin where your JWK Set is hosted.
agentURL = "https://agent.example.com"
)
// 1. Set the Signature-Agent header as a quoted string.
sigAgentHeader := fmt.Sprintf("%q", agentURL)
req.Header.Set("Signature-Agent", sigAgentHeader)
// 2. List the HTTP components to cover in the signature.
// Always cover "@authority" (the target host) and "signature-agent".
authority := strings.ToLower(req.URL.Host)
items := []httpsfv.Item{
httpsfv.NewItem("@authority"),
httpsfv.NewItem("signature-agent"),
}
baseLines := []string{
fmt.Sprintf(`"@authority": %s`, authority),
fmt.Sprintf(`"signature-agent": %s`, sigAgentHeader),
}
// If the optional Agent-Context header is set, sign it too.
if agentCtx := req.Header.Get("Agent-Context"); agentCtx != "" {
items = append(items, httpsfv.NewItem("agent-context"))
line := fmt.Sprintf(`"agent-context": %s`, agentCtx)
baseLines = append(baseLines, line)
}
// 3. Set the signature metadata parameters.
// Set expires to 300 seconds (5 minutes) after created.
now := time.Now().Unix()
params := httpsfv.NewParams()
params.Add("created", now)
params.Add("expires", now+300)
params.Add("keyid", keyID)
params.Add("tag", "web-bot-auth")
// Serialize the covered components and parameters for Signature-Input.
innerList := httpsfv.InnerList{
Items: items,
Params: params,
}
sigParamsStr, err := httpsfv.Marshal(innerList)
if err != nil {
return err
}
// 4. Build the RFC 9421 signature base string.
// Join each covered component line and @signature-params with "\n".
// Don't add a trailing newline after the @signature-params line.
paramLine := fmt.Sprintf(`"@signature-params": %s`, sigParamsStr)
baseLines = append(baseLines, paramLine)
sigBase := strings.Join(baseLines, "\n")
// 5. Sign the UTF-8 bytes of the signature base with your private key.
sigBytes := ed25519.Sign(privKey, []byte(sigBase))
sigB64 := base64.StdEncoding.EncodeToString(sigBytes)
// 6. Attach the Signature-Input and Signature headers to the request.
req.Header.Set("Signature-Input", fmt.Sprintf("%s=%s", sigLabel, sigParamsStr))
req.Header.Set("Signature", fmt.Sprintf("%s=:%s:", sigLabel, sigB64))
return nil
}
Experimentellen Agent-Context-Header einfügen
Mehrmandantenfähige Plattformen (z. B. gehostete Browseranbieter oder Cloud-Agent-Laufzeiten) signieren ausgehende Anfragen von mehreren Kunden-Agents oder Mandanten häufig mit einem einzigen freigegebenen Signaturschlüssel. Standardmäßig identifiziert die WBA nur die Signaturdomain (Signature-Agent). Daher hat der gesamte mit diesem Schlüssel signierte Traffic dieselbe Reputation.
Wenn Sie den Header Agent-Context einfügen und signieren, kann reCAPTCHA die Reputation separat für verschiedene Produkte, Projekte, Nutzer oder Sitzungen bewerten, die einen gemeinsamen Signaturschlüssel auf Ihrer Plattform verwenden.
Header „Agent-Context“ formatieren
Formatieren Sie Agent-Context als strukturiertes Feldverzeichnis gemäß RFC 8941, wobei jeder Schlüssel ein kleingeschriebenes Token und jeder Wert ein in Anführungszeichen gesetzter ASCII-String ist:
Agent-Context: product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"
Alle Felder sind optional. Geben Sie nur die Felder an, die für Ihre Architektur gelten:
| Feld | Beschreibung | Beispiel |
|---|---|---|
product |
Anwendungs- oder Handelsname des Agenten, der die Anfrage stellt. | product="ResearchBot" |
project |
Pseudonyme Konto-, Arbeitsbereichs- oder Mandantenprojekt-ID auf der Hostplattform. | project="prj_89a0f" |
user |
Pseudonymisierte Kennung (z. B. eine intransparente UUID oder ein Salted Hash) für den Kunden oder Endnutzer. Dieser Wert sollte bei Anfragen desselben Nutzers gleich bleiben, um eine Bewertung aufzubauen. Er darf keine echten personenidentifizierbaren Informationen enthalten. | user="usr_29df3" |
session |
Kurzlebige, opake Kennung für eine einzelne Aufgabe, einen Workflow-Ausführung oder eine Browsersitzung. | session="sess_88b1" |
Header „Agent-Context“ signieren
reCAPTCHA verwendet Agent-Context nur dann für die Reputationsisolation, wenn Sie "agent-context" in Ihre WBA-Signatur aufnehmen. Nicht signierte Agent-Context-Header werden ignoriert.
So signieren Sie den Agent-Context-Header:
Legen Sie den
Agent-Context-Header für Ihre ausgehende*http.Requestfest, bevor SiesignRequestEd25519aufrufen:req.Header.Set("Agent-Context", `product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"`)Fügen Sie
"agent-context"in die Komponentenliste und den Signaturbasisstring vonSignature-Inputein (wird automatisch vom Go-Beispiel verarbeitet):"@authority": www.example.com "signature-agent": "https://agent.example.com" "agent-context": product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1" "@signature-params": ("@authority" "signature-agent" "agent-context");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"
Das folgende Beispiel zeigt eine vollständige HTTP-Anfrage, die mit Agent-Context signiert ist:
GET /api/data HTTP/1.1
Host: www.example.com
Signature-Agent: "https://agent.example.com"
Agent-Context: product="ResearchBot", project="prj_89a0f", user="usr_29df3", session="sess_88b1"
Signature-Input: sig1=("@authority" "signature-agent" "agent-context");created=1772400000;expires=1772400300;keyid="OE_x89FAZvHnzOd9gMU0zFW-AhHTESAL5cV5aL0dqyw";tag="web-bot-auth"
Signature: sig1=:BASE64_ENCODED_SIGNATURE:
Nächste Schritte
Wenn Sie Ihre Anfragen mit WBA signieren, beweisen Sie die kryptografische Kontrolle über die Domain Ihres Agents. Websites benötigen jedoch auch Metadaten darüber, wer Ihren Agent betreibt und welchem Zweck er dient.
- Informationen dazu, wie Websiteinhaber Signale von verifizierten Agenten verwenden:
- Agenten-Traffic im Dashboard Agentenübersicht beobachten.
- Konfigurieren Sie Challenge-Richtlinien mit der Bedingungsvariable
verified_bots.
- Protokollspezifikationen lesen: