{"id":13033,"date":"2026-08-28T13:25:07","date_gmt":"2026-08-28T11:25:07","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/werkzeuge\/web-bot-auth-signature-inspector\/"},"modified":"2026-08-28T13:25:07","modified_gmt":"2026-08-28T11:25:07","slug":"web-bot-auth-signature-inspector","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/","title":{"rendered":"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">Ein agentischer Browser sieht im Bericht aus wie ein Mensch, weil er eine echte Browser-Engine ist. Zwei Mechanismen beantworten die Frage, wer da wirklich fragt, und ihr Betrieb kostet h\u00f6chst Unterschiedliches. Der eine ist eine Signatur, die der Agent mitbringt; hier werden genau die Bytes neu aufgebaut, die unterschrieben wurden, der Daumenabdruck des Schl\u00fcssels gegen die keyid gehalten und die Signatur gepr\u00fcft. Der andere ist eine Liste von Adressen, die der Betreiber ver\u00f6ffentlicht; hier wird gez\u00e4hlt, was so eine Liste abdeckt, und gepr\u00fcft, ob eine Adresse hineinf\u00e4llt.<\/p>\n<div style=\"margin-bottom: 14px;\">\n        <label for=\"wb-header\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Kopfzeilen: Signature-Agent, Signature-Input, Signature<\/label><br \/>\n        <textarea id=\"wb-header\" rows=\"7\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.82rem;\"><\/textarea>\n    <\/div>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; margin-bottom: 14px;\">\n<div>\n            <label for=\"wb-authority\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Authority, an die die Anfrage ging<\/label><br \/>\n            <input id=\"wb-authority\" type=\"text\" value=\"www.example.com\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\">\n        <\/div>\n<div>\n            <label for=\"wb-adresse\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Zu pr\u00fcfende Adresse<\/label><br \/>\n            <input id=\"wb-adresse\" type=\"text\" value=\"192.0.2.45\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\">\n        <\/div>\n<\/p><\/div>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; margin-bottom: 14px;\">\n<div>\n            <label for=\"wb-jwk\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">\u00d6ffentlicher Schl\u00fcssel als JWK oder JWKS<\/label><br \/>\n            <textarea id=\"wb-jwk\" rows=\"5\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.82rem;\"><\/textarea>\n        <\/div>\n<div>\n            <label for=\"wb-praefixe\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Ver\u00f6ffentlichte Pr\u00e4fixe, eines je Zeile<\/label><br \/>\n            <textarea id=\"wb-praefixe\" rows=\"5\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.82rem;\"><\/textarea>\n        <\/div>\n<\/p><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin-bottom: 16px;\">Alles rechnet im Browser; keine Kopfzeile, kein Schl\u00fcssel und keine Adresse wird \u00fcbertragen oder gespeichert. Das Beispiel tr\u00e4gt feste Zeitstempel, damit seine Signatur pr\u00fcfbar bleibt &#8211; deshalb meldet es zugleich, abgelaufen zu sein. Genau diese Trennung ist das Brauchbare daran: kryptografisch stimmig und derzeit g\u00fcltig sind zwei verschiedene Befunde.<\/p>\n<p>    <button id=\"wb-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Pr\u00fcfen<\/button><\/p>\n<div id=\"wb-ausgabe\" style=\"margin-top: 22px;\"><\/div>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Ein agentischer Browser sieht im Bericht aus wie ein Mensch, weil er eine echte Browser-Engine ist. Zwei Mechanismen beantworten die Frage, wer da wirklich fragt, und ihr Betrieb kostet h\u00f6chst Unterschiedliches. Der eine ist eine Signatur, die der Agent mitbringt; hier werden genau die Bytes neu aufgebaut, die unterschrieben wurden, der Daumenabdruck des Schl\u00fcssels gegen die keyid gehalten und die Signatur gepr\u00fcft. Der andere ist eine Liste von Adressen, die der Betreiber ver\u00f6ffentlicht; hier wird gez\u00e4hlt, was so eine Liste abdeckt, und gepr\u00fcft, ob eine Adresse hineinf\u00e4llt.\",\"label_header\":\"Kopfzeilen: Signature-Agent, Signature-Input, Signature\",\"label_authority\":\"Authority, an die die Anfrage ging\",\"label_adresse\":\"Zu pr\u00fcfende Adresse\",\"label_jwk\":\"\u00d6ffentlicher Schl\u00fcssel als JWK oder JWKS\",\"label_praefixe\":\"Ver\u00f6ffentlichte Pr\u00e4fixe, eines je Zeile\",\"datenschutz\":\"Alles rechnet im Browser; keine Kopfzeile, kein Schl\u00fcssel und keine Adresse wird \u00fcbertragen oder gespeichert. Das Beispiel tr\u00e4gt feste Zeitstempel, damit seine Signatur pr\u00fcfbar bleibt - deshalb meldet es zugleich, abgelaufen zu sein. Genau diese Trennung ist das Brauchbare daran: kryptografisch stimmig und derzeit g\u00fcltig sind zwei verschiedene Befunde.\",\"knopf\":\"Pr\u00fcfen\",\"beispiel_header\":\"Signature-Agent: \\u0022https:\\\/\\\/signer.example.com\\u0022\\nSignature-Input: sig1=(\\u0022@authority\\u0022 \\u0022signature-agent\\u0022);created=1787000000;expires=1787003600;keyid=\\u0022ulMr0bd_FrTSdSiYtpOpPZ0UrGNldXBNi9wDof-iKpc\\u0022;alg=\\u0022ed25519\\u0022;tag=\\u0022web-bot-auth\\u0022;nonce=\\u0022bHctYmVpc3BpZWwtbm9uY2U\\u0022\\nSignature: sig1=:HPpAewTOKvc4nY\\\/dONz6R848m3D8Zf8poPQMu6bNGByXPzztOSHT7tqjx5\\\/THiWk7r\\\/u0lKzn7wPvdPsK3NaAg==:\",\"beispiel_jwk\":\"{\\u0022crv\\u0022:\\u0022Ed25519\\u0022,\\u0022kty\\u0022:\\u0022OKP\\u0022,\\u0022x\\u0022:\\u0022AYQxRK7FjZW-qXcFpYAer7j-nC9VPsKM-5oCT7Vb3kw\\u0022}\",\"beispiel_praefixe\":\"192.0.2.0\\\/24\\n198.51.100.0\\\/24\\n203.0.113.0\\\/25\\n203.0.113.128\\\/25\\n2001:db8::\\\/32\",\"h_agent\":\"Wer zu fragen behauptet\",\"zeile_agent\":\"Host aus Signature-Agent\",\"zeile_verzeichnis\":\"Wo sein Schl\u00fcsselsatz liegen m\u00fcsste\",\"wert_keiner\":\"keiner\",\"fehler_kein_signature_input\":\"Keine Signature-Input-Kopfzeile gefunden. Ohne sie gibt es nichts aufzubauen - die gedeckten Bestandteile und die Parameter stehen allein dort.\",\"fehler_kein_signature\":\"Keine Signature-Kopfzeile gefunden. Die Basis l\u00e4sst sich noch aufbauen, pr\u00fcfen l\u00e4sst sich dagegen nichts.\",\"fehler_signature_input_unlesbar\":\"Die Signature-Input-Kopfzeile l\u00e4sst sich nicht als W\u00f6rterbuch strukturierter Felder lesen.\",\"fehler_signature_unlesbar\":\"Die Signature-Kopfzeile l\u00e4sst sich nicht als W\u00f6rterbuch strukturierter Felder lesen.\",\"h_signatur\":\"Signatur\",\"zeile_komponenten\":\"Gedeckte Bestandteile\",\"zeile_keyid\":\"keyid\",\"zeile_alg\":\"alg\",\"zeile_tag\":\"tag\",\"zeile_lebensdauer\":\"G\u00fcltigkeitsfenster\",\"zeile_signaturlaenge\":\"L\u00e4nge der Signatur\",\"einheit_sekunden\":\"Sekunden\",\"einheit_bytes\":\"Byte\",\"h_basis\":\"Signaturbasis, neu aufgebaut\",\"basis_fehlend\":\"Kein Wert aufl\u00f6sbar f\u00fcr:\",\"pruefung_laeuft\":\"Der Schl\u00fcssel wird gepr\u00fcft...\",\"zeile_abdruck\":\"Daumenabdruck des Schl\u00fcssels, RFC 7638\",\"zeile_abdruck_urteil\":\"Gegen die keyid\",\"abdruck_kein_jwk\":\"kein Schl\u00fcssel angegeben\",\"abdruck_kein_okp\":\"kein OKP-Schl\u00fcssel mit crv und x\",\"abdruck_kein_subtle\":\"dieser Browser bietet kein WebCrypto\",\"abdruck_passt\":\"passt - dieser Schl\u00fcssel ist der, den die Signatur nennt\",\"abdruck_passt_nicht\":\"passt nicht zur keyid: ein anderer Schl\u00fcssel\",\"abdruck_ohne_keyid\":\"die Signatur nennt keine keyid zum Vergleich\",\"zeile_signaturpruefung\":\"Pr\u00fcfung der Signatur\",\"signatur_gueltig\":\"g\u00fcltig f\u00fcr diese Basis und diesen Schl\u00fcssel\",\"signatur_ungueltig\":\"geht nicht auf\",\"pruefung_kein_subtle\":\"dieser Browser bietet kein WebCrypto\",\"pruefung_kein_ed25519\":\"dieser Browser hat kein Ed25519 in WebCrypto; der Daumenabdruck oben wurde trotzdem gepr\u00fcft\",\"pruefung_unvollstaendig\":\"ein Schl\u00fcssel oder eine Signatur fehlt\",\"h_befunde\":\"Befunde\",\"befund_kein_tag\":\"Der tag ist nicht web-bot-auth. Eine Signatur ohne diesen tag kann eine HTTP-Message-Signatur f\u00fcr etwas ganz anderes sein.\",\"befund_keyid_fehlt\":\"Keine keyid. Ohne sie kann eine pr\u00fcfende Seite nicht sagen, welchen Schl\u00fcssel aus einem Satz sie holen soll.\",\"befund_alg_fremd\":\"Der alg nennt etwas anderes als ed25519. Web Bot Auth ist um Ed25519 herum festgelegt.\",\"befund_created_fehlt\":\"Kein created-Parameter, das Alter der Signatur l\u00e4sst sich also nicht bestimmen.\",\"befund_kein_ablauf\":\"Kein expires-Parameter. Eine Signatur ohne Ende ist so lange wiederverwendbar, wie der Schl\u00fcssel lebt.\",\"befund_abgelaufen\":\"Das G\u00fcltigkeitsfenster ist vorbei. Die Signatur kann kryptografisch trotzdem aufgehen - das ist eine andere Frage als die, ob sie jetzt noch angenommen werden soll.\",\"befund_noch_nicht_gueltig\":\"Der created-Zeitstempel liegt in der Zukunft, was meist auf eine falsch gehende Uhr hindeutet und nicht auf einen Angriff.\",\"befund_fenster_weit\":\"Das G\u00fcltigkeitsfenster ist l\u00e4nger als eine Stunde. Je weiter es ist, desto l\u00e4nger bleibt eine abgefangene Signatur brauchbar.\",\"befund_authority_ungedeckt\":\"@authority ist nicht gedeckt. Dieselbe Signatur w\u00e4re dann gegen\u00fcber einem anderen Host g\u00fcltig - und genau das muss die Menge der gedeckten Bestandteile verhindern.\",\"befund_agent_ungedeckt\":\"Eine Signature-Agent-Kopfzeile ist vorhanden, aber nicht von der Signatur gedeckt. Sie l\u00e4sst sich austauschen, ohne dass etwas bricht - die behauptete Herkunft des Schl\u00fcssels ist damit ungesch\u00fctzt.\",\"befund_agent_fehlt\":\"Keine Signature-Agent-Kopfzeile. Der Schl\u00fcsselsatz muss dann anders gefunden werden.\",\"befund_nonce_fehlt\":\"Kein nonce. Eine Wiedereinspielung innerhalb des G\u00fcltigkeitsfensters muss dann anderswo abgefangen werden.\",\"befund_laenge_ungewoehnlich\":\"Die Signatur ist nicht 64 Byte lang. Eine Ed25519-Signatur ist immer genau so lang.\",\"befund_signatur_fehlt\":\"Zu dieser Kennung wurden in der Signature-Kopfzeile keine Signaturbytes gefunden.\",\"befund_basis_unvollstaendig\":\"Mindestens ein gedeckter Bestandteil lie\u00df sich aus der Eingabe nicht aufl\u00f6sen; die Basis oben ist damit unvollst\u00e4ndig, und eine Pr\u00fcfung dagegen scheiterte schon daran.\",\"h_netz\":\"Der Adressbeweis\",\"zeile_praefixe\":\"Gelesene Pr\u00e4fixe\",\"zeile_v4adressen\":\"Abgedeckte IPv4-Adressen\",\"zeile_v6\":\"Abgedeckte IPv6-Adressen\",\"zeile_ungueltig\":\"Nicht als Pr\u00e4fix lesbar\",\"zeile_treffer\":\"Adresse in der Liste\",\"treffer_ja\":\"ja, in\",\"treffer_nein\":\"nein\",\"treffer_unlesbar\":\"die Adresse l\u00e4sst sich nicht lesen\",\"h_vergleich\":\"Was die beiden Beweise unterscheidet\",\"vergleich_pflege\":\"Eine Adressliste muss neu geholt werden, sobald der Betreiber sie \u00e4ndert, und ein Betreiber kann seinen Bestand \u00fcber Nacht vervielfachen. Ein Schl\u00fcsselbeweis braucht diese Pflege nicht: der Schl\u00fcsselsatz wird einmal geholt und gegen die keyid vorgehalten.\",\"vergleich_beweis\":\"Eine Adresse belegt, dass eine Anfrage aus einem Netz kam, \u00fcber das der Betreiber verf\u00fcgt. Eine Signatur belegt, dass wer auch immer den privaten Schl\u00fcssel h\u00e4lt genau diese Anfrage erzeugt hat. Das Zweite ist eine Aussage \u00fcber die Anfrage, das Erste nur \u00fcber ihren Weg.\",\"vergleich_absicht\":\"Keiner von beiden belegt eine Absicht. Eine gepr\u00fcfte Signatur sagt, wer fragt, nicht was mit der Antwort geschieht - und dort h\u00f6rt der Mechanismus auf.\",\"tausender\":\".\"};<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Strukturierte Felder, RFC 8941\n    \/\/ =========================================================================\n    \/\/\n    \/\/ Gebraucht wird nur der Ausschnitt, den RFC 9421 benutzt: ein Woerterbuch,\n    \/\/ dessen Werte innere Listen aus Zeichenketten oder einzelne Bytefolgen\n    \/\/ sind, jeweils mit Parametern. Ein vollstaendiger Parser waere hier\n    \/\/ Ballast; was fehlt, wird als Fehler gemeldet statt stillschweigend\n    \/\/ falsch gedeutet.<\/p>\n<p>    function Leser(text) {\n        this.s = String(text == null ? '' : text);\n        this.i = 0;\n    }\n    Leser.prototype.rest = function () { return this.s.slice(this.i); };\n    Leser.prototype.ende = function () { return this.i >= this.s.length; };\n    Leser.prototype.zeichen = function () { return this.s.charAt(this.i); };\n    Leser.prototype.ows = function () {\n        while (!this.ende() && (this.zeichen() === ' ' || this.zeichen() === '\\t')) { this.i++; }\n    };<\/p>\n<p>    function istZiffer(c) { return c >= '0' && c <= '9'; }\n    function istBuchstabe(c) {\n        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');\n    }\n\n    \/** Zeichenkette mit Anfuehrungszeichen, Fluchtzeichen nur vor \" und Rueckstrich. *\/\n    function leseZeichenkette(l) {\n        if (l.zeichen() !== '\"') { throw new Error('erwartet: Zeichenkette'); }\n        l.i++;\n        var raus = '';\n        while (!l.ende()) {\n            var c = l.s.charAt(l.i++);\n            if (c === '\\\\') {\n                if (l.ende()) { throw new Error('Fluchtzeichen am Ende'); }\n                var n = l.s.charAt(l.i++);\n                if (n !== '\"' &#038;&#038; n !== '\\\\') { throw new Error('unerlaubtes Fluchtzeichen'); }\n                raus += n;\n            } else if (c === '\"') {\n                return raus;\n            } else {\n                raus += c;\n            }\n        }\n        throw new Error('Zeichenkette nicht geschlossen');\n    }\n\n    function leseToken(l) {\n        var start = l.i;\n        while (!l.ende() &#038;&#038; \/[A-Za-z0-9:\\\/!#$%&#038;'*+\\-.^_`|~]\/.test(l.zeichen())) { l.i++; }\n        if (l.i === start) { throw new Error('erwartet: Token'); }\n        return l.s.slice(start, l.i);\n    }\n\n    function leseZahl(l) {\n        var start = l.i;\n        if (l.zeichen() === '-') { l.i++; }\n        while (!l.ende() &#038;&#038; (istZiffer(l.zeichen()) || l.zeichen() === '.')) { l.i++; }\n        var roh = l.s.slice(start, l.i);\n        if (!\/^-?\\d+(\\.\\d+)?$\/.test(roh)) { throw new Error('erwartet: Zahl'); }\n        return Number(roh);\n    }\n\n    function leseBytefolge(l) {\n        if (l.zeichen() !== ':') { throw new Error('erwartet: Bytefolge'); }\n        l.i++;\n        var start = l.i;\n        while (!l.ende() &#038;&#038; l.zeichen() !== ':') { l.i++; }\n        if (l.ende()) { throw new Error('Bytefolge nicht geschlossen'); }\n        var roh = l.s.slice(start, l.i);\n        l.i++;\n        return { typ: 'bytes', wert: roh };\n    }\n\n    function leseWert(l) {\n        var c = l.zeichen();\n        if (c === '\"') { return { typ: 'text', wert: leseZeichenkette(l) }; }\n        if (c === ':') { return leseBytefolge(l); }\n        if (c === '?') {\n            l.i++;\n            var b = l.s.charAt(l.i++);\n            if (b !== '0' &#038;&#038; b !== '1') { throw new Error('erwartet: Wahrheitswert'); }\n            return { typ: 'bool', wert: b === '1' };\n        }\n        if (istZiffer(c) || c === '-') { return { typ: 'zahl', wert: leseZahl(l) }; }\n        return { typ: 'token', wert: leseToken(l) };\n    }\n\n    \/** Parameter: ;name=wert oder ;name (dann true). Reihenfolge bleibt erhalten. *\/\n    function leseParameter(l) {\n        var liste = [];\n        while (!l.ende() &#038;&#038; l.zeichen() === ';') {\n            l.i++;\n            l.ows();\n            var name = leseSchluessel(l);\n            var wert = { typ: 'bool', wert: true };\n            if (!l.ende() &#038;&#038; l.zeichen() === '=') {\n                l.i++;\n                wert = leseWert(l);\n            }\n            liste.push({ name: name, wert: wert });\n        }\n        return liste;\n    }\n\n    function leseSchluessel(l) {\n        var start = l.i;\n        while (!l.ende() &#038;&#038; \/[a-z0-9_\\-.*]\/.test(l.zeichen())) { l.i++; }\n        if (l.i === start) { throw new Error('erwartet: Schluessel'); }\n        return l.s.slice(start, l.i);\n    }\n\n    function leseInnereListe(l) {\n        if (l.zeichen() !== '(') { throw new Error('erwartet: innere Liste'); }\n        l.i++;\n        var eintraege = [];\n        for (;;) {\n            l.ows();\n            if (l.ende()) { throw new Error('innere Liste nicht geschlossen'); }\n            if (l.zeichen() === ')') { l.i++; break; }\n            var w = leseWert(l);\n            var p = leseParameter(l);\n            eintraege.push({ wert: w, parameter: p });\n        }\n        return eintraege;\n    }\n\n    \/** Woerterbuch: schluessel=wert, durch Komma getrennt. *\/\n    function leseWoerterbuch(text) {\n        var l = new Leser(text);\n        var raus = [];\n        l.ows();\n        while (!l.ende()) {\n            var name = leseSchluessel(l);\n            var eintrag = { name: name, liste: null, wert: null, parameter: [] };\n            if (!l.ende() &#038;&#038; l.zeichen() === '=') {\n                l.i++;\n                if (l.zeichen() === '(') {\n                    eintrag.liste = leseInnereListe(l);\n                } else {\n                    eintrag.wert = leseWert(l);\n                }\n            } else {\n                eintrag.wert = { typ: 'bool', wert: true };\n            }\n            eintrag.parameter = leseParameter(l);\n            raus.push(eintrag);\n            l.ows();\n            if (l.ende()) { break; }\n            if (l.zeichen() !== ',') { throw new Error('erwartet: Komma'); }\n            l.i++;\n            l.ows();\n        }\n        return raus;\n    }\n\n    \/\/ --- Zurueckschreiben in die kanonische Form -----------------------------\n\n    function schreibeWert(w) {\n        if (w.typ === 'text') { return '\"' + String(w.wert).replace(\/\\\\\/g, '\\\\\\\\').replace(\/\"\/g, '\\\\\"') + '\"'; }\n        if (w.typ === 'bytes') { return ':' + w.wert + ':'; }\n        if (w.typ === 'bool') { return w.wert ? '?1' : '?0'; }\n        if (w.typ === 'zahl') { return String(w.wert); }\n        return String(w.wert);\n    }\n\n    function schreibeParameter(liste) {\n        return (liste || []).map(function (p) {\n            if (p.wert.typ === 'bool' &#038;&#038; p.wert.wert === true) { return ';' + p.name; }\n            return ';' + p.name + '=' + schreibeWert(p.wert);\n        }).join('');\n    }\n\n    function schreibeInnereListe(eintraege, parameter) {\n        var kern = '(' + (eintraege || []).map(function (e) {\n            return schreibeWert(e.wert) + schreibeParameter(e.parameter);\n        }).join(' ') + ')';\n        return kern + schreibeParameter(parameter);\n    }\n\n    \/\/ =========================================================================\n    \/\/ Kopfzeilen und Signaturbasis\n    \/\/ =========================================================================\n\n    \/** Zerlegt einen eingefuegten Kopfzeilenblock. Fortsetzungszeilen zaehlen dazu. *\/\n    function kopfzeilen(text) {\n        var raus = [];\n        var zeilen = String(text == null ? '' : text).split(\/\\r\\n|\\r|\\n\/);\n        for (var i = 0; i < zeilen.length; i++) {\n            var z = zeilen[i];\n            if (!z.replace(\/[ \\t]\/g, '').length) { continue; }\n            if (\/^[ \\t]\/.test(z) &#038;&#038; raus.length) {\n                raus[raus.length - 1].wert += ' ' + z.replace(\/^[ \\t]+\/, '').replace(\/[ \\t]+$\/, '');\n                continue;\n            }\n            var p = z.indexOf(':');\n            if (p < 1) { continue; }\n            raus.push({\n                name: z.slice(0, p).trim().toLowerCase(),\n                wert: z.slice(p + 1).replace(\/^[ \\t]+\/, '').replace(\/[ \\t]+$\/, '')\n            });\n        }\n        return raus;\n    }\n\n    function feld(liste, name) {\n        var treffer = liste.filter(function (k) { return k.name === name; });\n        if (!treffer.length) { return null; }\n        return treffer.map(function (k) { return k.wert; }).join(', ');\n    }\n\n    \/**\n     * Baut die Signaturbasis nach RFC 9421 Abschnitt 2.5.\n     * Jede Komponentenzeile endet mit einem Zeilenvorschub, die\n     * @signature-params-Zeile steht ohne abschliessenden Vorschub am Ende.\n     *\/\n    function basisBauen(eintraege, parameter, kopf, authority) {\n        var zeilen = [];\n        var fehlend = [];\n        (eintraege || []).forEach(function (e) {\n            var name = String(e.wert.wert).toLowerCase();\n            var kennung = '\"' + name + '\"' + schreibeParameter(e.parameter);\n            var wert = null;\n            if (name === '@authority') {\n                wert = authority ? String(authority).toLowerCase() : null;\n            } else if (name.charAt(0) === '@') {\n                wert = null; \/\/ andere abgeleitete Bestandteile sind hier nicht aufloesbar\n            } else {\n                wert = feld(kopf, name);\n            }\n            if (wert === null || wert === '') {\n                fehlend.push(name);\n                zeilen.push(kennung + ': ');\n            } else {\n                zeilen.push(kennung + ': ' + wert);\n            }\n        });\n        zeilen.push('\"@signature-params\": ' + schreibeInnereListe(eintraege, parameter));\n        return { basis: zeilen.join('\\n'), fehlend: fehlend };\n    }\n\n    \/\/ =========================================================================\n    \/\/ Auswertung der Signatur\n    \/\/ =========================================================================\n\n    function parameterKarte(liste) {\n        var k = {};\n        (liste || []).forEach(function (p) { k[p.name] = p.wert.wert; });\n        return k;\n    }\n\n    var FENSTER_LANG = 3600; \/\/ Sekunden; darueber ist ein Signaturfenster ungewoehnlich weit\n\n    function zerlegen(eingabe, jetzt) {\n        var ein = eingabe || {};\n        var zeit = typeof jetzt === 'number' ? jetzt : Math.floor(Date.now() \/ 1000);\n        var kopf = kopfzeilen(ein.header);\n        var fehler = [];\n\n        var agentRoh = feld(kopf, 'signature-agent');\n        var agent = null;\n        if (agentRoh) {\n            var host = null;\n            try {\n                var l = new Leser(agentRoh);\n                var w = leseWert(l);\n                var adr = String(w.wert);\n                host = adr.replace(\/^https?:\\\/\\\/\/, '').replace(\/\\\/.*$\/, '') || null;\n            } catch (e) {\n                host = agentRoh.replace(\/^\"|\"$\/g, '').replace(\/^https?:\\\/\\\/\/, '').replace(\/\\\/.*$\/, '') || null;\n            }\n            agent = {\n                roh: agentRoh,\n                host: host,\n                verzeichnis: host ? 'https:\/\/' + host + '\/.well-known\/http-message-signatures-directory' : null\n            };\n        }\n\n        var eingabeRoh = feld(kopf, 'signature-input');\n        var signaturRoh = feld(kopf, 'signature');\n        if (!eingabeRoh) { fehler.push('kein_signature_input'); }\n        if (!signaturRoh) { fehler.push('kein_signature'); }\n\n        var eingabeDict = [];\n        var signaturDict = [];\n        if (eingabeRoh) {\n            try { eingabeDict = leseWoerterbuch(eingabeRoh); }\n            catch (e) { fehler.push('signature_input_unlesbar'); }\n        }\n        if (signaturRoh) {\n            try { signaturDict = leseWoerterbuch(signaturRoh); }\n            catch (e) { fehler.push('signature_unlesbar'); }\n        }\n\n        var signaturen = eingabeDict.map(function (eintrag) {\n            var params = parameterKarte(eintrag.parameter);\n            var komponenten = (eintrag.liste || []).map(function (e) { return String(e.wert.wert).toLowerCase(); });\n            var gebaut = basisBauen(eintrag.liste, eintrag.parameter, kopf, ein.authority);\n\n            var passend = signaturDict.filter(function (s) { return s.name === eintrag.name; })[0];\n            var bytesRoh = (passend &#038;&#038; passend.wert &#038;&#038; passend.wert.typ === 'bytes') ? passend.wert.wert : null;\n            var bytes = null;\n            if (bytesRoh !== null) {\n                try { bytes = base64ZuBytes(bytesRoh); } catch (e) { bytes = null; }\n            }\n\n            var befunde = [];\n            if (params.tag !== 'web-bot-auth') { befunde.push('kein_tag'); }\n            if (!params.keyid) { befunde.push('keyid_fehlt'); }\n            if (params.alg &#038;&#038; String(params.alg).toLowerCase() !== 'ed25519') { befunde.push('alg_fremd'); }\n            if (typeof params.created !== 'number') { befunde.push('created_fehlt'); }\n            if (typeof params.expires !== 'number') { befunde.push('kein_ablauf'); }\n            if (typeof params.expires === 'number' &#038;&#038; params.expires < zeit) { befunde.push('abgelaufen'); }\n            if (typeof params.created === 'number' &#038;&#038; params.created > zeit) { befunde.push('noch_nicht_gueltig'); }\n            if (typeof params.created === 'number' && typeof params.expires === 'number'\n                && params.expires - params.created > FENSTER_LANG) { befunde.push('fenster_weit'); }\n            if (komponenten.indexOf('@authority') === -1) { befunde.push('authority_ungedeckt'); }\n            if (agentRoh && komponenten.indexOf('signature-agent') === -1) { befunde.push('agent_ungedeckt'); }\n            if (!agentRoh) { befunde.push('agent_fehlt'); }\n            if (!params.nonce) { befunde.push('nonce_fehlt'); }\n            if (bytes && bytes.length !== 64) { befunde.push('laenge_ungewoehnlich'); }\n            if (!bytes) { befunde.push('signatur_fehlt'); }\n            if (gebaut.fehlend.length) { befunde.push('basis_unvollstaendig'); }<\/p>\n<p>            return {\n                label: eintrag.name,\n                komponenten: komponenten,\n                parameter: params,\n                basis: gebaut.basis,\n                fehlend: gebaut.fehlend,\n                signaturBytes: bytes ? bytes.length : 0,\n                signatur: bytes,\n                lebensdauer: (typeof params.created === 'number' && typeof params.expires === 'number')\n                    ? params.expires - params.created : null,\n                befunde: befunde\n            };\n        });<\/p>\n<p>        return { agent: agent, signaturen: signaturen, kopfzeilen: kopf, fehler: fehler, zeit: zeit };\n    }<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Base64 und WebCrypto\n    \/\/ =========================================================================<\/p>\n<p>    function base64ZuBytes(s) {\n        var t = String(s).replace(\/-\/g, '+').replace(\/_\/g, '\/').replace(\/\\s+\/g, '');\n        while (t.length % 4) { t += '='; }\n        var roh = atob(t);\n        var b = new Uint8Array(roh.length);\n        for (var i = 0; i < roh.length; i++) { b[i] = roh.charCodeAt(i); }\n        return b;\n    }\n\n    function bytesZuBase64Url(b) {\n        var s = '';\n        for (var i = 0; i < b.length; i++) { s += String.fromCharCode(b[i]); }\n        return btoa(s).replace(\/\\+\/g, '-').replace(\/\\\/\/g, '_').replace(\/=+$\/, '');\n    }\n\n    function textZuBytes(t) {\n        if (typeof TextEncoder !== 'undefined') { return new TextEncoder().encode(t); }\n        var s = unescape(encodeURIComponent(String(t)));\n        var b = new Uint8Array(s.length);\n        for (var i = 0; i < s.length; i++) { b[i] = s.charCodeAt(i); }\n        return b;\n    }\n\n    \/**\n     * Daumenabdruck nach RFC 7638: die Pflichtfelder in lexikografischer\n     * Ordnung, ohne Leerraum, durch SHA-256, base64url. Bei Web Bot Auth ist\n     * genau das die keyid - der Abgleich braucht also kein Ed25519.\n     *\/\n    function daumenabdruck(jwk) {\n        if (!jwk || jwk.kty !== 'OKP' || !jwk.crv || !jwk.x) {\n            return Promise.resolve({ moeglich: false, grund: 'kein_okp' });\n        }\n        var kanonisch = '{\"crv\":\"' + jwk.crv + '\",\"kty\":\"' + jwk.kty + '\",\"x\":\"' + jwk.x + '\"}';\n        if (!window.crypto || !window.crypto.subtle) {\n            return Promise.resolve({ moeglich: false, grund: 'kein_subtle' });\n        }\n        return window.crypto.subtle.digest('SHA-256', textZuBytes(kanonisch)).then(function (h) {\n            return { moeglich: true, abdruck: bytesZuBase64Url(new Uint8Array(h)) };\n        }, function () {\n            return { moeglich: false, grund: 'kein_subtle' };\n        });\n    }\n\n    \/** Ed25519 gegen die Signaturbasis. Fehlt der Algorithmus im Browser, wird das gesagt. *\/\n    function verifizieren(basis, signatur, jwk) {\n        if (!signatur || !jwk) { return Promise.resolve({ moeglich: false, grund: 'unvollstaendig' }); }\n        if (!window.crypto || !window.crypto.subtle || !window.crypto.subtle.importKey) {\n            return Promise.resolve({ moeglich: false, grund: 'kein_subtle' });\n        }\n        return window.crypto.subtle.importKey('jwk', {\n            kty: jwk.kty, crv: jwk.crv, x: jwk.x, ext: true\n        }, { name: 'Ed25519' }, false, ['verify']).then(function (key) {\n            return window.crypto.subtle.verify({ name: 'Ed25519' }, key, signatur, textZuBytes(basis))\n                .then(function (ok) { return { moeglich: true, gueltig: !!ok }; });\n        }).catch(function () {\n            return { moeglich: false, grund: 'kein_ed25519' };\n        });\n    }\n\n    \/\/ =========================================================================\n    \/\/ Adressbeweis: Praefixliste\n    \/\/ =========================================================================\n\n    function ipv4ZuZahl(a) {\n        var t = String(a).split('.');\n        if (t.length !== 4) { return null; }\n        var n = 0;\n        for (var i = 0; i < 4; i++) {\n            if (!\/^\\d{1,3}$\/.test(t[i])) { return null; }\n            var o = Number(t[i]);\n            if (o > 255) { return null; }\n            n = n * 256 + o;\n        }\n        return n;\n    }<\/p>\n<p>    \/** IPv6 als 32 Hexziffern, damit sich Praefixe rein textlich vergleichen lassen. *\/\n    function ipv6ZuHex(a) {\n        var s = String(a).trim();\n        if (s.indexOf(':') === -1) { return null; }\n        var teile = s.split('::');\n        if (teile.length > 2) { return null; }\n        function gruppen(t) { return t ? t.split(':').filter(function (x) { return x.length; }) : []; }\n        var links = gruppen(teile[0]);\n        var rechts = teile.length === 2 ? gruppen(teile[1]) : [];\n        var luecke = 8 - links.length - rechts.length;\n        if (teile.length === 1) {\n            if (links.length !== 8) { return null; }\n            luecke = 0;\n        } else if (luecke < 0) {\n            return null;\n        }\n        var nullen = [];\n        for (var k = 0; k < luecke; k++) { nullen.push('0'); }\n        var alle = links.concat(nullen).concat(rechts);\n        if (alle.length !== 8) { return null; }\n        var hex = '';\n        for (var i = 0; i < 8; i++) {\n            if (!\/^[0-9a-fA-F]{1,4}$\/.test(alle[i])) { return null; }\n            hex += ('000' + alle[i].toLowerCase()).slice(-4);\n        }\n        return hex;\n    }\n\n    function hexZuBits(hex, anzahl) {\n        var bits = '';\n        for (var i = 0; i < hex.length &#038;&#038; bits.length < anzahl; i++) {\n            bits += ('000' + parseInt(hex.charAt(i), 16).toString(2)).slice(-4);\n        }\n        return bits.slice(0, anzahl);\n    }\n\n    function netzPruefen(text, adresse) {\n        var zeilen = String(text == null ? '' : text).split(\/[\\s,;]+\/).filter(function (z) { return z.length; });\n        var praefixe = [];\n        var ungueltig = [];\n        var summeV4 = 0;\n        var v6Exponenten = [];\n\n        zeilen.forEach(function (z) {\n            var t = z.split('\/');\n            var netz = t[0];\n            var laenge = t.length > 1 ? Number(t[1]) : null;\n            var v4 = ipv4ZuZahl(netz);\n            if (v4 !== null) {\n                var l4 = laenge === null ? 32 : laenge;\n                if (!(l4 >= 0 && l4 <= 32) || !\/^\\d+$\/.test(String(t.length > 1 ? t[1] : '32'))) { ungueltig.push(z); return; }\n                var anzahl = Math.pow(2, 32 - l4);\n                summeV4 += anzahl;\n                praefixe.push({ roh: z, version: 4, laenge: l4, netz: v4, adressen: anzahl });\n                return;\n            }\n            var v6 = ipv6ZuHex(netz);\n            if (v6 !== null) {\n                var l6 = laenge === null ? 128 : laenge;\n                if (!(l6 >= 0 && l6 <= 128)) { ungueltig.push(z); return; }\n                v6Exponenten.push(128 - l6);\n                praefixe.push({ roh: z, version: 6, laenge: l6, netz: v6, adressen: null, exponent: 128 - l6 });\n                return;\n            }\n            ungueltig.push(z);\n        });\n\n        \/\/ Treffer\n        var treffer = null;\n        var trefferPraefix = null;\n        var adr = String(adresse == null ? '' : adresse).trim();\n        if (adr.length) {\n            var a4 = ipv4ZuZahl(adr);\n            var a6 = a4 === null ? ipv6ZuHex(adr) : null;\n            if (a4 === null &#038;&#038; a6 === null) {\n                treffer = 'unlesbar';\n            } else {\n                treffer = 'nein';\n                for (var i = 0; i < praefixe.length; i++) {\n                    var p = praefixe[i];\n                    if (p.version === 4 &#038;&#038; a4 !== null) {\n                        if (p.laenge === 0) { treffer = 'ja'; trefferPraefix = p.roh; break; }\n                        var maske = p.laenge === 0 ? 0 : (-1 << (32 - p.laenge)) >>> 0;\n                        if (((a4 >>> 0) & maske) === ((p.netz >>> 0) & maske)) {\n                            treffer = 'ja'; trefferPraefix = p.roh; break;\n                        }\n                    } else if (p.version === 6 && a6 !== null) {\n                        if (hexZuBits(a6, p.laenge) === hexZuBits(p.netz, p.laenge)) {\n                            treffer = 'ja'; trefferPraefix = p.roh; break;\n                        }\n                    }\n                }\n            }\n        }<\/p>\n<p>        return {\n            praefixe: praefixe,\n            anzahlPraefixe: praefixe.length,\n            v4Adressen: summeV4,\n            v6Exponenten: v6Exponenten,\n            ungueltig: ungueltig,\n            treffer: treffer,\n            trefferPraefix: trefferPraefix\n        };\n    }<\/p>\n<p>    \/\/ --- Pruefpunkt ----------------------------------------------------------\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.WB = {\n        zerlegen: zerlegen,\n        basisBauen: basisBauen,\n        leseWoerterbuch: leseWoerterbuch,\n        schreibeInnereListe: schreibeInnereListe,\n        kopfzeilen: kopfzeilen,\n        netzPruefen: netzPruefen,\n        daumenabdruck: daumenabdruck,\n        verifizieren: verifizieren,\n        base64ZuBytes: base64ZuBytes,\n        bytesZuBase64Url: bytesZuBase64Url,\n        ipv6ZuHex: ipv6ZuHex,\n        FENSTER_LANG: FENSTER_LANG,\n        T: T\n    };<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Oberflaeche\n    \/\/ =========================================================================<\/p>\n<p>    var btn = document.getElementById('wb-btn');\n    var out = document.getElementById('wb-ausgabe');\n    if (!btn || !out) { return; }<\/p>\n<p>    \/\/ Beispiel vorbelegen, damit das Werkzeug ohne eigene Daten etwas zeigt.\n    var beispielHeader = document.getElementById('wb-header');\n    var beispielJwk = document.getElementById('wb-jwk');\n    var beispielPraefixe = document.getElementById('wb-praefixe');\n    if (beispielHeader && !beispielHeader.value) { beispielHeader.value = T.beispiel_header; }\n    if (beispielJwk && !beispielJwk.value) { beispielJwk.value = T.beispiel_jwk; }\n    if (beispielPraefixe && !beispielPraefixe.value) { beispielPraefixe.value = T.beispiel_praefixe; }<\/p>\n<p>    function el(tag, stil, text) {\n        var e = document.createElement(tag);\n        if (stil) { e.setAttribute('style', stil); }\n        if (text !== undefined) { e.textContent = text; }\n        return e;\n    }\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    function zahl(n) { return String(n).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, T.tausender); }<\/p>\n<p>    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.92rem; margin: 18px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var ZELLE_WERT = 'padding: 5px 12px 5px 0; color: var(--text-primary, #e8e8ee); font-size: 0.87rem; font-family: monospace; word-break: break-all;';\n    var VORFORM = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 10px; color: var(--text-primary, #e8e8ee); font-family: monospace; font-size: 0.78rem; white-space: pre-wrap; word-break: break-all; margin: 0;';\n    var GUT = ' color: #7ee787;';\n    var WARN = ' color: #ffa94d;';\n    var GRAU = ' color: #8a8a99;';<\/p>\n<p>    function gitter(spalten) {\n        return el('div', 'display: grid; grid-template-columns: ' + spalten + '; gap: 0 18px; align-items: baseline;');\n    }\n    function paar(g, name, w, stil) {\n        g.appendChild(el('div', ZELLE, name));\n        g.appendChild(el('div', ZELLE_WERT + (stil || ''), w));\n    }<\/p>\n<p>    btn.addEventListener('click', function () {\n        var erg = zerlegen({ header: wert('wb-header'), authority: wert('wb-authority') });\n        out.innerHTML = '';<\/p>\n<p>        \/\/ --- Absender ---\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_agent));\n        var ga = gitter('auto auto');\n        if (erg.agent) {\n            paar(ga, T.zeile_agent, erg.agent.host || T.wert_keiner, erg.agent.host ? '' : WARN);\n            paar(ga, T.zeile_verzeichnis, erg.agent.verzeichnis || T.wert_keiner, GRAU);\n        } else {\n            paar(ga, T.zeile_agent, T.wert_keiner, WARN);\n        }\n        out.appendChild(ga);<\/p>\n<p>        if (erg.fehler.length) {\n            var fl = el('ul', 'margin: 8px 0 0 18px; padding: 0; color: #ffa94d; font-size: 0.87rem; line-height: 1.6;');\n            erg.fehler.forEach(function (f) { fl.appendChild(el('li', '', T['fehler_' + f] || f)); });\n            out.appendChild(fl);\n        }<\/p>\n<p>        \/\/ --- Signaturen ---\n        erg.signaturen.forEach(function (s) {\n            out.appendChild(el('div', UEBERSCHRIFT, T.h_signatur + ' ' + s.label));\n            var g = gitter('auto auto');\n            paar(g, T.zeile_komponenten, s.komponenten.length ? s.komponenten.join(', ') : T.wert_keiner);\n            paar(g, T.zeile_keyid, s.parameter.keyid || T.wert_keiner, s.parameter.keyid ? '' : WARN);\n            paar(g, T.zeile_alg, s.parameter.alg || T.wert_keiner, GRAU);\n            paar(g, T.zeile_tag, s.parameter.tag || T.wert_keiner, s.parameter.tag === 'web-bot-auth' ? GUT : WARN);\n            paar(g, T.zeile_lebensdauer,\n                s.lebensdauer === null ? T.wert_keiner : zahl(s.lebensdauer) + ' ' + T.einheit_sekunden,\n                s.lebensdauer === null ? WARN : (s.lebensdauer > FENSTER_LANG ? WARN : GUT));\n            paar(g, T.zeile_signaturlaenge,\n                s.signaturBytes ? zahl(s.signaturBytes) + ' ' + T.einheit_bytes : T.wert_keiner,\n                s.signaturBytes === 64 ? GUT : WARN);\n            out.appendChild(g);<\/p>\n<p>            out.appendChild(el('div', UEBERSCHRIFT, T.h_basis));\n            out.appendChild(el('pre', VORFORM, s.basis));\n            if (s.fehlend.length) {\n                out.appendChild(el('p', ZELLE + WARN + ' margin: 8px 0 0;',\n                    T.basis_fehlend + ' ' + s.fehlend.join(', ')));\n            }<\/p>\n<p>            var pruefZeile = el('div', ZELLE + GRAU + ' margin-top: 10px;', T.pruefung_laeuft);\n            out.appendChild(pruefZeile);<\/p>\n<p>            var jwk = null;\n            try { jwk = JSON.parse(wert('wb-jwk')); } catch (e) { jwk = null; }\n            if (jwk && jwk.keys && jwk.keys.length) { jwk = jwk.keys[0]; }<\/p>\n<p>            Promise.all([\n                daumenabdruck(jwk),\n                verifizieren(s.basis, s.signatur, jwk)\n            ]).then(function (r) {\n                var abdruck = r[0];\n                var pruef = r[1];\n                pruefZeile.textContent = '';\n                var gp = gitter('auto auto');\n                if (!jwk) {\n                    paar(gp, T.zeile_abdruck, T.abdruck_kein_jwk, GRAU);\n                } else if (!abdruck.moeglich) {\n                    paar(gp, T.zeile_abdruck, T['abdruck_' + abdruck.grund] || T.wert_keiner, GRAU);\n                } else {\n                    var passt = s.parameter.keyid && abdruck.abdruck === s.parameter.keyid;\n                    paar(gp, T.zeile_abdruck, abdruck.abdruck, passt ? GUT : WARN);\n                    paar(gp, T.zeile_abdruck_urteil,\n                        !s.parameter.keyid ? T.abdruck_ohne_keyid : (passt ? T.abdruck_passt : T.abdruck_passt_nicht),\n                        passt ? GUT : WARN);\n                }\n                if (pruef.moeglich) {\n                    paar(gp, T.zeile_signaturpruefung, pruef.gueltig ? T.signatur_gueltig : T.signatur_ungueltig,\n                        pruef.gueltig ? GUT : WARN);\n                } else {\n                    paar(gp, T.zeile_signaturpruefung, T['pruefung_' + pruef.grund] || T.wert_keiner, GRAU);\n                }\n                pruefZeile.appendChild(gp);\n            });<\/p>\n<p>            if (s.befunde.length) {\n                out.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n                var bl = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; line-height: 1.65;');\n                s.befunde.forEach(function (b) {\n                    var text = T['befund_' + b];\n                    if (text) { bl.appendChild(el('li', '', text)); }\n                });\n                out.appendChild(bl);\n            }\n        });<\/p>\n<p>        \/\/ --- Adressbeweis ---\n        var netz = netzPruefen(wert('wb-praefixe'), wert('wb-adresse'));\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_netz));\n        var gn = gitter('auto auto');\n        paar(gn, T.zeile_praefixe, zahl(netz.anzahlPraefixe));\n        paar(gn, T.zeile_v4adressen, zahl(netz.v4Adressen));\n        if (netz.v6Exponenten.length) {\n            paar(gn, T.zeile_v6, netz.v6Exponenten.map(function (e) { return '2^' + e; }).join(' + '), GRAU);\n        }\n        if (netz.ungueltig.length) {\n            paar(gn, T.zeile_ungueltig, netz.ungueltig.join(', '), WARN);\n        }\n        if (netz.treffer !== null) {\n            paar(gn, T.zeile_treffer,\n                netz.treffer === 'ja' ? (T.treffer_ja + ' ' + netz.trefferPraefix)\n                    : (netz.treffer === 'nein' ? T.treffer_nein : T.treffer_unlesbar),\n                netz.treffer === 'ja' ? GUT : WARN);\n        }\n        out.appendChild(gn);<\/p>\n<p>        out.appendChild(el('div', UEBERSCHRIFT, T.h_vergleich));\n        var vl = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; line-height: 1.65;');\n        ['vergleich_pflege', 'vergleich_beweis', 'vergleich_absicht'].forEach(function (k) {\n            vl.appendChild(el('li', '', T[k]));\n        });\n        out.appendChild(vl);\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Stellt die beiden Wege nebeneinander, auf denen eine Seite erfahren kann, wer fragt: eine Ed25519-Signatur nach RFC 9421, neu aufgebaut und gegen einen \u00f6ffentlichen Schl\u00fcssel gepr\u00fcft, und eine Liste ver\u00f6ffentlichter Adressbereiche, gez\u00e4hlt und auf Enthaltensein gepr\u00fcft.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":9283,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92406,92032,92071],"class_list":["post-13033","page","type-page","status-publish","hentry","tag-artificial-intelligence-de","tag-tracking-de","tag-web-security-de"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Stellt die beiden Wege nebeneinander, auf denen eine Seite erfahren kann, wer fragt: eine Ed25519-Signatur nach RFC 9421, neu aufgebaut und gegen einen \u00f6ffentlichen Schl\u00fcssel gepr\u00fcft, und eine Liste ver\u00f6ffentlichter Adressbereiche, gez\u00e4hlt und auf Enthaltensein gepr\u00fcft.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/web-bot-auth-signature-inspector\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/web-bot-auth-signature-inspector\\\/\",\"name\":\"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-08-28T11:25:07+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/web-bot-auth-signature-inspector\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/web-bot-auth-signature-inspector\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/web-bot-auth-signature-inspector\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/toolbox-2\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/","og_locale":"en_US","og_type":"article","og_title":"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner - Lukas Wojcik - Blog","og_description":"Stellt die beiden Wege nebeneinander, auf denen eine Seite erfahren kann, wer fragt: eine Ed25519-Signatur nach RFC 9421, neu aufgebaut und gegen einen \u00f6ffentlichen Schl\u00fcssel gepr\u00fcft, und eine Liste ver\u00f6ffentlichter Adressbereiche, gez\u00e4hlt und auf Enthaltensein gepr\u00fcft.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/","url":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/","name":"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-08-28T11:25:07+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/web-bot-auth-signature-inspector\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/toolbox-2\/"},{"@type":"ListItem","position":3,"name":"Web-Bot-Auth-Signaturpr\u00fcfer und Adressbereichs-Rechner"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/13033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=13033"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/13033\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9283"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=13033"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=13033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}