IT security, FreeBSD, Linux, mail server hardening, post-quantum crypto, DNS, retro computing & hands-on hardware hacks. Privater Tech-Blog seit 2003.

Kategorie: IT-Security (Seite 1 von 15)

Notizen & Praxis zur IT-Sicherheit – von Responsible Disclosure bis Härtung.

Postfix 3.11.1 mit OpenSSL 3.5: Post-Quantum-TLS jetzt nativ — meine alten Workarounds dürfen raus

Postfix 3.11.1 mit OpenSSL 3.5 – Post-Quantum TLS (X25519MLKEM768) mit kleinem ClientHello durch Delayed Key Share

Kleines Update zur Mini-Saga rund um Post-Quantum-TLS auf Postfix. Zur Erinnerung: Im Original-Beitrag vom Februar stand tls_eecdh_auto_curves noch global in der main.cf, mit X25519MLKEM768 an erster Stelle. Im Nachtrag vom 1. April im selben Beitrag kam dann die Trennung in master.cf, weil die globale Variante einen ClientHello jenseits 1400 Bytes erzeugt und damit gegen manche Zielserver auf die Nase fällt. Dank smtp_tls_security_level = may fällt Postfix in dem Fall stillschweigend auf Plaintext zurück, eure Mail geht raus, aber unverschlüsselt. Klingt akademisch, ist es nicht.

Mit dem letzten regulären pkg upgrade auf FreeBSD 15.0-RELEASE-p7 ist Postfix 3.11.1 eingezogen. Damit erledigen sich beide Workarounds. Der Built-in-Default ist jetzt korrekt, und der ganze master.cf-Override-Block aus dem April darf ersatzlos raus. Wer beide Vorgängerbeiträge nachgebaut hat, darf jetzt rückwärts wieder aufräumen — mit zwei postfix reload ist man durch.

Was Postfix 3.11.1 mitbringt

Postfix 3.11.1 setzt den Built-in-Default für tls_eecdh_auto_curves auf:

tls_eecdh_auto_curves = ?X25519MLKEM768:DEFAULT

Das Fragezeichen vor dem Gruppennamen ist OpenSSL-3.5+ Syntax für Delayed Key-Share, Postfix reicht das 1:1 durch. Bedeutung: X25519MLKEM768 wird in der TLS-Extension supported_groups annonciert, der eigentliche KeyShare aber NICHT vorab im ClientHello generiert. Der KeyShare wird erst materialisiert, wenn der Server per HelloRetryRequest gezielt danach fragt. ClientHello bleibt damit klein, MLKEM wird trotzdem ausgehandelt sobald die Gegenstelle es unterstützt — und fällt sauber auf eine klassische Kurve zurück wenn nicht.

Damit erledigt sich die Begründung für die Client/Server-Trennung aus dem April-Update. Inbound und Outbound können denselben Default nutzen — kein Bloat in eine Richtung, kein Verzicht auf PQC in die andere.

master.cf aufräumen

Aus master.cf werden alle -o tls_eecdh_auto_curves=... Overrides ersatzlos entfernt. Im konkreten Setup hier waren das fünf Stellen: smtp/inet, submission/inet, der eigene 2525/inet-Listener, smtps/inet und smtp/unix für Outbound. Diese Zeilen alle raus:

smtp      inet  n       -       n       -       -       smtpd
  -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1   # raus

submission inet n       -       n       -       -       smtpd
  -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1   # raus

smtps     inet  n       -       n       -       -       smtpd
  -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1   # raus

smtp      unix  -       -       n       -       -       smtp
  -o tls_eecdh_auto_curves=X25519,X25519MLKEM768,prime256v1,secp384r1   # raus

In der main.cf wird ebenfalls nichts mehr explizit gesetzt — der Built-in-Default greift. Verifikation:

# postconf tls_eecdh_auto_curves
tls_eecdh_auto_curves = ?X25519MLKEM768:DEFAULT

# postconf -P | grep tls_eecdh_auto_curves
(leer -- kein Override aktiv)

Anschließend postfix reload. Kein Restart, kein Service-Ausfall, keine offenen Verbindungen verloren. Dovecot bleibt unverändert, dort sieht es weiter so aus:

ssl_curve_list = X25519MLKEM768:X25519:prime256v1:secp384r1

Dovecot ist nur Server, kein Outbound-Client. Da gibt es kein ClientHello-Bloat-Problem.

Was DEFAULT konkret enthält

Hinter dem schlichten Wort DEFAULT versteckt OpenSSL 3.5.4 eine konkrete Liste. Ich habe sie aus dem extension_type=supported_groups-Block eines Test-Handshakes herausgezogen:

X25519MLKEM768 (4588)   # mit ?-Prefix vom davorgestellten Eintrag, dedupliziert
X25519         (29)
secp256r1      (23)     # prime256v1
X448           (30)
secp384r1      (24)
secp521r1      (25)
ffdhe2048      (256)
ffdhe3072      (257)

Damit ist klar warum man den Default in der Regel ungeändert lässt — er enthält MLKEM768 hybrid an erster Stelle, alle gängigen klassischen Kurven dahinter und ein paar FFDHE-Gruppen als Backup. Wer es minimaler will und auf FFDHE verzichten kann, setzt explizit ?X25519MLKEM768:X25519:prime256v1:secp384r1. Notwendig ist das nicht.

Verifikation #1: ClientHello-Größen mit openssl s_client

Um das Delayed-Key-Share-Verhalten ohne Glaubensfrage zu prüfen, vier verschiedene Group-Listen gegen einen MLKEM-fähigen MX gefahren — Gmail tut sich da als Test-Target ganz gut. Die Schleife direkt zum Nachstellen:

for groups in 
    '?X25519MLKEM768:DEFAULT' 
    'X25519MLKEM768:X25519:prime256v1:secp384r1' 
    'X25519:X25519MLKEM768:prime256v1:secp384r1' 
    'X25519'; do
  echo "=== groups=$groups ==="
  openssl s_client -connect gmail-smtp-in.l.google.com:25 -starttls smtp 
    -groups "$groups" -msg </dev/null 2>&1 
    | grep 'Handshake.*ClientHello'
done

In der -msg-Ausgabe steht pro Handshake-Record eine Zeile wie

>>> TLS 1.3, Handshake [length 014e], ClientHello

Die hex-Length ist die Größe des TLS-Records. Tabellarisch:

Group-ListeInitial ClientHelloNach HRR
?X25519MLKEM768:DEFAULT (Postfix 3.11 Default)334 Bytes (0x014e)1518 Bytes (0x05ee)
X25519MLKEM768:X25519:prime256v1:secp384r1 (alt-Inbound)1510 Bytes (0x05e6)
X25519:X25519MLKEM768:prime256v1:secp384r1 (alt-Outbound)326 Bytes (0x0146)1510 Bytes (0x05e6)
X25519 (klassisch ohne PQC)320 Bytes (0x0140)

Interpretation: Der neue Default in Zeile 1 verhält sich praktisch identisch zum alten Outbound-Workaround in Zeile 3 — kleines Initial-ClientHello mit nur klassischen KeyShares, MLKEM wird erst über HelloRetryRequest aktiviert. Der alte Inbound-Style in Zeile 2 hingegen pusht den rund 1184 Byte großen ML-KEM-768-PublicKey schon im ersten ClientHello mit und sprengt damit die 1400-Byte-Schwelle, an der einige Mail-Frontends regelmäßig stoppen. Genau das Verhalten, das im Original-Beitrag und im April-Update als Problem identifiziert wurde — jetzt nativ vermieden. Ich muss ja gerade etwas grinsen …

Meine Frau ist immer mal wieder so cool und liest mir meine neuen Beiträge vor. So höre ich sie noch einmal, und ihr Feedback ist mir sehr wichtig.

Sie versteht aber im Grunde kein Wort und ersetzt jegliche Codeblöcke oder zu technische Specs immer durch „bla / irgendwas“.

Ich glaube, dieser Beitrag wird einer der schwierigeren. Dennoch wird sie ihn für mich lesen — und das ist toll. 😀

Danke!

Verifikation #2: tcpdump auf dem Wire

Wer dem OpenSSL-Output nicht glaubt, kann sich das ClientHello auch direkt vom Draht holen. Damit der Capture funktioniert, sind zwei Stolpersteine zu beachten:

  • tcpdump muss auf dem Host laufen, nicht in der Jail. In FreeBSD-Jails ist der direkte BPF-Zugriff aufs Interface defaultmäßig abgeschaltet (Packet capture is not supported on that device). tcpdump auf der Host-Seite sieht den Jail-Verkehr aber sauber über das geteilte Interface.
  • openssl s_client greift per Default zu IPv6, wenn der Zielhost AAAA-Records hat. Ein reiner IPv4-Filter bleibt dann leer (0 packets captured / 689 packets received by filter — ich gestehe, der Moment war kurz verwirrend). Lösung: openssl mit -4 zwingen oder den Filter dual-stack auslegen. Ja klar, ist ja eine selbstverständliche Kleinigkeit. Sehe ich auch so. Nur warum habe ich dann fünf Minuten lang mit komplett leerem Blick wie ein Eichhörnchen auf meinen Monitor geschaut und nicht verstanden, warum ich keine Packets sehe? Bis zu diesem Moment habe ich mich ja für einen kurzen Augenblick cool gefühlt, weil ich dachte, das alles so weit verstanden zu haben. Tja, und dann? Dann fünf Minuten lang: „Kein Anschluss unter dieser Nummer …“

Konkret im Test gelaufen (smtp-Jail mit IPv4 148.251.30.205, IPv6 2a01:4f8:262:4716::25, Interface igb0):

tcpdump -i igb0 -n -s 0 -c 8 -w /tmp/ch.pcap 
  'dst port 25 and (host 148.251.30.205 or host 2a01:4f8:262:4716::25)' &

jexec smtp openssl s_client -4 
  -connect gmail-smtp-in.l.google.com:25 -starttls smtp 
  -groups '?X25519MLKEM768:DEFAULT' </dev/null

Anschließend pcap auslesen:

tcpdump -nn -tttt -r /tmp/ch.pcap        # Paketsequenz
tcpdump -nn -r /tmp/ch.pcap -X            # Hex-Dump für TLS-Record-Inspektion

Gekürzte Sequenz:

18:35:59.443  IP 148.251.30.205.10420 > 142.251.127.27.25: Flags [S],   length 0
18:35:59.448  IP                                            Flags [.],  length 0
18:35:59.525  IP                                            Flags [P.], length 23: SMTP: EHLO ...
18:35:59.541  IP                                            Flags [P.], length 10: SMTP: STARTTLS
18:35:59.554  IP                                            Flags [P.], length 339: SMTP

Das letzte Paket mit 339 Byte Payload enthält das ClientHello. Hex-Auszug aus tcpdump -X:

0x0030:  ...1603 0101 4e 01 0001 4a 03 0354
0x0040:  045a 83dd 0481 ee15 c537 75f2 6b38 f360
...

Aufgeschlüsselt:

0x16     -- TLS Record Type Handshake
0x0301   -- TLS Version (legacy in TLS 1.3 ClientHello)
0x014e   -- TLS Record Length 334 Bytes
0x01     -- Handshake Type ClientHello
0x00014a -- ClientHello-Body 330 Bytes

339 Byte TCP-Payload teilen sich auf in 5 Byte TLS-Record-Header und 334 Byte Body. Das passt locker in ein einziges TCP-Segment, weit unter der 1400-Byte-Schwelle. Damit ist das ?-Präfix-Verhalten auch auf dem Wire bestätigt.

Verifikation #3: eigene Inbound-Seite von extern

Zum Abschluss von einem zweiten FreeBSD-Host aus — also nicht aus der smtp-Jail selbst, sondern als komplett externer Client — gegen alle relevanten Ports getestet:

for entry in 25:smtp 465: 587:smtp 993: 4190:sieve; do
  port=${entry%:*}; starttls=${entry#*:}
  host=smtp.kernel-error.de
  case $port in 993|4190) host=imap.kernel-error.de ;; esac
  if [ -z "$starttls" ]; then args="-connect $host:$port"
  else args="-connect $host:$port -starttls $starttls"; fi
  echo "--- Port $port ---"
  echo QUIT | openssl s_client $args -brief 2>&1 | grep -E 'Negotiated|Verification:'
done

Ergebnis für jeden der fünf Ports identisch:

Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768

Postfix-Inbound auf 25/465/587 und Dovecot auf 993/4190 verhandeln durchweg X25519MLKEM768. Der Default greift für smtpd genauso wie für smtp outbound, ohne dass irgendwo noch eine Trennung händisch erzwungen werden muss.

Fazit

Mit Postfix 3.11 und OpenSSL 3.5 ist sowohl die main.cf-globale Variante aus dem Original-Beitrag als auch die master.cf-Trennung aus dem April-Update Geschichte. Der Built-in-Default ?X25519MLKEM768:DEFAULT liefert genau das Verhalten, das ich vorher manuell aufgebaut hatte: kleiner ClientHello outbound, MLKEM via HelloRetryRequest, klassischer Fallback wo nötig.

Konkret: wer den Original-Beitrag oder den April-Update nachgebaut hat, kann nach dem pkg-Upgrade auf Postfix 3.11.x die tls_eecdh_auto_curves-Einträge ersatzlos rausnehmen — aus main.cf und/oder master.cf — und mit postfix reload aktivieren. openssl s_client -msg und ein kurzer tcpdump bestätigen anschließend, dass das initiale ClientHello tatsächlich klein bleibt und MLKEM über HRR aushandelt.

Drei Iterationen, ein Default, der am Ende einfach passt. So darf das gerne häufiger laufen.

Siehe auch: Post-Quantum TLS für E-Mail (Original-Beitrag mit April-Nachtrag), Post-Quantum TLS für Nginx, Post-Quantum TLS auf Nginx: 15 Tage $ssl_curve ausgewertet und der Mailinglisten-Thread auf postfix-users, der die ganze Saga überhaupt erst angestoßen hat.

Wie immer: bei Fragen, fragen.

Post-Quantum TLS auf Nginx: 15 Tage $ssl_curve ausgewertet, wer macht mit?

Post-Quantum TLS auf Nginx – Auswertung der Key-Exchange-Gruppen (X25519MLKEM768 vs. klassisch)

Vor einigen Wochen habe ich Nginx hier auf X25519MLKEM768 umgestellt und den Weg dorthin in einem eigenen Beitrag dokumentiert: Post-Quantum TLS für Nginx auf FreeBSD 15. Am Ende des Beitrags stand ein kleines Versprechen. Ich erweitere das Logging um die ausgehandelte Key-Exchange-Gruppe, lasse das ein paar Wochen laufen und werte dann aus, wer was tatsächlich spricht. Das ist jetzt eingelöst.

Der Messaufbau, in zwei Zeilen

Nginx kennt die Variable $ssl_curve. Die ist seit Ewigkeiten verfügbar und liefert pro Verbindung zurück, welche Kurve bzw. Gruppe beim TLS-Handshake benutzt wurde. Also X25519MLKEM768, X25519, secp384r1, prime256v1 und so weiter. Im Log-Format einfach nach $ssl_cipher eingehängt, einen Reload in den Nginx geschickt, fertig.

log_format goa_ext
  '$remote_addr - $remote_user [$time_local] '
  '"$request" $status $body_bytes_sent '
  '"$http_referer" "$http_user_agent" '
  '$host $server_protocol $scheme '
  '$request_time $upstream_response_time $upstream_status $upstream_addr '
  '$ssl_protocol $ssl_cipher $ssl_curve $upstream_cache_status';

Nach rund 15 Tagen liegen grob 180.000 HTTPS-Handshakes im Log, verteilt über alles, was so an HTTPS-Clients vorbeikommt. Das ist genug Masse, um ein paar belastbare Muster zu sehen, ohne dass einzelne Ausreißer das Gesamtbild kippen. Exakte Besucherzahlen werde ich in diesem Beitrag bewusst nicht auflisten, das ist auch nicht das Thema. Mich interessiert die relative Verteilung. Wer macht PQ, wer macht es nicht?

Die eine Zahl vorweg

Über alle HTTPS-Verbindungen (Browser, Bots, Crawler, Monitore, alles) sieht die Verteilung der Kurven so aus:

X25519MLKEM768   57,0 %   <- Post-Quantum-Hybrid
X25519           40,0 %   <- klassisches TLS 1.3
secp384r1         2,2 %   <- meist TLS 1.2
prime256v1        0,8 %   <- meist TLS 1.2

Klingt erstmal ordentlich. 57 % PQ über das komplette Gemisch, 98 % TLS 1.3, lediglich 2 % TLS 1.2. Aber in dieser Zahl stecken ein paar Dinge versteckt drin, die man erst sieht, sobald man die User-Agents grob auseinandersortiert. Ich habe das in ein paar Kübel geworfen: Browser, AI-Crawler, klassische Suchmaschinen, SEO-Spider, Fediverse-Software, RSS-Reader, Monitore und CLI-Tools. Nicht perfekt, aber brauchbar.

Browser, die Post-Quantum-Avantgarde

Bei echten Browsern (Chrome, Firefox, Safari, Edge) sieht es deutlich freundlicher aus. Zusammengenommen sprechen rund 77 % der Browser-Verbindungen bereits MLKEM768. Aufgeschlüsselt:

Firefox    87 % PQ   <- klarer Champion
Safari     75 % PQ
Edge       73 % PQ
Chrome     72 % PQ
Opera       2 % PQ   <- haengt seltsam weit hinten

Firefox liegt erkennbar vorn. Nicht weltbewegend weit, aber deutlich. Mozilla hat MLKEM768 früh aktiviert und nutzt standardmäßig die Hybrid-Gruppe, wenn der Server sie anbietet. Chrome hängt etwas hinter den anderen und ich vermute, das liegt an den ganzen älteren Chrome-Builds, die in Embedded-Devices, WebViews und seltsamen Apps stecken und auch als Chrome im User-Agent stehen. Opera dagegen nimmt fast immer nur klassisches X25519. Keine Ahnung warum, schaue ich mir vielleicht mal gesondert an.

Das Schöne daran ist: Ich habe für diese 77 % exakt nichts getan außer die Nginx-Konfiguration anzupassen. Kein Opt-in, kein Banner, keine Weiche. X25519MLKEM768 steht ganz oben in ssl_ecdh_curve und wird genommen, wenn der Client es kann. Der Rest ist reines Client-Upgrade-Verhalten. Das ist eigentlich die schönste Erkenntnis aus der ganzen Auswertung. Wenn die Serverseite rechtzeitig aktualisiert wird, zieht die Clientseite fast geräuschlos nach.

AI-Crawler, flächendeckend bei null

Jetzt kommt der Teil, den ich so nicht erwartet hätte. Die großen AI-Crawler holen sich hier regelmäßig Inhalte ab (siehe auch von SEO zu AEO: llms.txt und llms-full.txt), der TLS-Stack dahinter ist bei praktisch allen auf dem Stand von vor zwei Jahren:

OpenAI GPTBot             0 % PQ    -> X25519
Anthropic ClaudeBot       0 % PQ    -> X25519
Meta AI (ExternalAgent)   0 % PQ    -> X25519
PerplexityBot             0 % PQ    -> X25519
ChatGPT-User              0 % PQ    -> X25519
OpenAI SearchBot          0 % PQ    -> X25519
GoogleOther               0 % PQ    -> X25519
Amazonbot                 0 % PQ    -> TLS 1.2 + secp384r1

Amazon ist nochmal ein eigenes Kapitel. Amazonbot kommt hier ausschließlich mit TLS 1.2 und secp384r1. Das ist ein TLS-Stack, den ich persönlich bei einem Unternehmen, das Cloud-Sicherheit verkauft, nicht mehr erwartet hätte. Aber Messungen lügen nicht.

Zwei echte Ausnahmen gibt es:

ByteDance Bytespider     91 % PQ   <- ueberraschend
DuckAssistBot           100 % PQ   <- auch ueberraschend

Auf diese zwei hätte ich nicht gesetzt. TikToks Crawler macht zu über 90 % PQ, DuckDuckGos AI-Helper zu 100 %. Wer hätte das gedacht.

Klassische Suchmaschinen, auch nicht besser

Bei den traditionellen Suchmaschinen ist das Bild fast identisch zum AI-Lager:

Googlebot       0 % PQ
Applebot        0 % PQ
PetalBot        0 % PQ
Baiduspider     0 % PQ
SeznamBot       0 % PQ
Qwant           0 % PQ
YandexBot       1 % PQ
Bingbot         0 % PQ und zu 99,6 % auf TLS 1.2 + secp384r1
DuckDuckBot    84 % PQ   <- der einzige helle Fleck

Besonders bitter: Bingbot. Der läuft hier praktisch ausschließlich auf TLS 1.2 mit secp384r1. Microsofts Produktions-Webcrawler, 2026, mit einem TLS-Stack, den Webauditoren seit Jahren rot anstreichen. Googlebot ist immerhin auf TLS 1.3, aber halt ohne PQ. Der einzige, der fürs Thema etwas tut, ist DuckDuckBot. Respekt dafür.

SEO-Spider, am weitesten hinten

Der traurigste Haufen. AhrefsBot, SemrushBot, MJ12bot, DotBot, Barkrowler, DataForSeoBot, SeekportBot, Vebidoobot, alle zwischen 0 % und 3 % PQ. Vebidoobot kommt komplett auf TLS 1.2 rein. Das sind kommerzielle Produkte, die von Seitenbetreibern dafür bezahlt werden, Webseiten zu analysieren und Empfehlungen auszusprechen. Analysieren tun sie mit einem TLS-Stack, den sie ihren eigenen Kunden vermutlich als kritischen Finding in den Bericht schreiben würden. Kurios.

Fediverse, alles drin je nach Codebase

Seit dem Anschluss ans Fediverse ist das hier die zweitgrößte Traffic-Quelle nach den Browsern, deshalb habe ich mir das extra angeschaut. Mastodon stellt davon den Löwenanteil, weil es schlicht die meisten Instanzen gibt:

Mastodon                  62 % PQ
snac / GoToSocial /
  Friendica / Hubzilla    73 % PQ   <- Go/C-basiert
Misskey / Sharkey / ...   34 % PQ
Akkoma                     0 % PQ
Pleroma                    0 % PQ

Bei Mastodon gibt es eine große Varianz zwischen den Instanzen, weil die Ruby- und OpenSSL-Version des jeweiligen Server-Hosts entscheidet, ob PQ geht. Aktuelle Distribution mit OpenSSL 3.5 oder neuer: dabei. Noch auf OpenSSL 3.0 festhängend: nicht dabei. Der Schnitt liegt bei 62 %, was für ein so diverses Ökosystem schon erstaunlich ordentlich ist.

snac und GoToSocial liegen deutlich höher, weil sie in Go beziehungsweise C geschrieben sind und moderne TLS-Stacks mitbringen. Akkoma und Pleroma (beide Elixir/Erlang) zeigen dagegen gar keine PQ-Adoption. Das hängt an der OpenSSL-Version, die die BEAM-VM dort nutzt. Misskey und die ganzen Forks dazwischen liegen bei rund einem Drittel.

RSS-Reader, unerwartet modern

Hätte ich vorher schätzen sollen, hätte ich RSS-Reader eher am Ende dieser Liste verortet. Alte Technologie, alte Software, wahrscheinlich alter TLS-Stack. Stimmt aber nicht:

Miniflux              100 % PQ   <- Go-basiert
FreshRSS               82 % PQ
NextCloud-News         81 % PQ   <- zahlenmaessig vorn
Tiny Tiny RSS          37 % PQ
Inoreader               0 % PQ
Feedly                  0 % PQ   <- haengt auch hinten

Miniflux macht 100 % PQ, weil es in Go geschrieben ist und ab Go 1.24 MLKEM768 standardmäßig im TLS-Stack sitzt. FreshRSS und NextCloud-News laufen meist auf aktuellen PHP/curl-Umgebungen und ziehen MLKEM darüber mit. Feedly als kommerzieller Anbieter: 0 %. Also genau das Gegenteil dessen, was ich erwartet hätte. Self-Hosted ist hier eindeutig moderner unterwegs als die SaaS-Variante.

CLI-Werkzeuge und Kuriositäten

go-http-client     93 % PQ   <- Go 1.24+
curl               30 % PQ   <- je nach OpenSSL-Build
python-requests    16 % PQ
Node (axios)       61 % PQ
okhttp              0 % PQ
wget                0 % PQ
Twitterbot         97 % PQ   <- unerwartet weit vorn

Das Muster ist eigentlich immer dasselbe. Der TLS-Stack der Laufzeitumgebung entscheidet. Go ≥ 1.24 macht es automatisch, moderne Node-Versionen bringen einen aktuellen OpenSSL mit, Python und curl hängen an der Distribution. okhttp auf Android und wget: Fehlanzeige.

Kleiner Spaß am Rande. Twitterbot ist zu 97 % auf MLKEM. Also ausgerechnet der Link-Preview-Crawler von X/Twitter ist moderner unterwegs als alle anderen Social-Preview-Bots zusammen. WhatsApp: 4 %, Discord: 0 %, LinkedIn ist kaum vertreten. Warum Twitter? Keine Ahnung. Vermutlich ein moderner Go-Client unter der Haube.

TLS 1.2, wer hängt noch ganz unten?

2 % des Traffics kommen komplett mit TLS 1.2, also ohne jede Chance auf PQ. Die Top-Kandidaten sind:

vebidoobot (SEO-Spider)
Amazonbot
DotBot, MJ12bot (SEO)
theoldreader.com (RSS-SaaS)
http.rb/Mastodon auf aelteren Instanzen
ein paar vereinzelte Alt-Browser und Skype-Link-Previews

Also fast ausschließlich kommerzielle Crawler, deren TLS-Library vor der ganzen 1.3-Welle kompiliert wurde, und ein paar ältere Mastodon-Instanzen. Reale Leser sind so gut wie nicht betroffen. Wer heute einen Feed-Reader mit TLS 1.2 nutzt, hat vermutlich andere Probleme zuerst zu lösen.

Gibt es einen Trend in den 15 Tagen?

Nicht wirklich. Der PQ-Anteil pro Tag schwankt zwischen 46 % und 65 %, je nach Traffic-Mix (mehr Browser an Wochentagen, mehr Crawler nachts und am Wochenende). Einen klaren Aufwärts- oder Abwärtstrend gibt es nicht. Wir sind im Plateau. Die Browser haben den Sprung gemacht, der Rest der Welt noch nicht. Der nächste Sprung kommt, wenn die großen Crawler-Betreiber ihre Go-, Python- oder Node-Stacks aktualisieren oder OpenSSL 3.5+ in den gängigen Distributionen ankommt. Bei Debian Trixie, RHEL 10 und Ubuntu 26.04 sollte das passieren, dann reden wir in einem Jahr nochmal.

Was ich daraus mitnehme

  • Echte Besucher sind bei MLKEM768 schon sehr weit. Browser-Entwicklung funktioniert erstaunlich gut.
  • AI- und SEO-Crawler sind deutlich hinter dem, was man erwarten würde. Cutting Edge in der Marketing-Abteilung, Uralt-Stack im Maschinenraum.
  • Fediverse-Software ist so divers wie ihre Codebasen. Bei Mastodon entscheidet die Instanz, nicht die Software.
  • RSS-Reader sind unerwartet modern unterwegs. Self-Hosted schlägt SaaS auch hier.
  • Am Ende hängt fast alles an der OpenSSL-Version unter der Anwendung. Wieder mal.
  • Bingbot auf TLS 1.2 ist 2026 trotzdem noch bemerkenswert.

Was mich am meisten gefreut hat: Ich habe keinerlei Reibungsverluste gesehen. Kein Client ist wegen PQ gestolpert, keine Verbindung ist fehlgeschlagen, die vorher funktioniert hätte. Der Handshake wählt einfach die beste gemeinsame Gruppe und gut ist. Deshalb nochmal der Appell an alle, die den Einstellungs-Beitrag noch vor sich haben: Das ist wirklich ein Zweizeiler. Macht es einfach.

Nächster Check in ein paar Monaten

Ich lasse das Logging weiterlaufen und schaue in ein paar Monaten nochmal rein. Was mich besonders interessiert:

  • Wann machen OpenAI, Anthropic und Meta ihren Crawler modern? Bleibt das auf Jahre bei 0 % PQ, oder kommt da plötzlich ein Sprung?
  • Schafft es OpenSSL 3.5 in die nächsten Long-Term-Release-Linuxe, und wie schnell ziehen Mastodon-Instanzen nach?
  • Springt Googlebot irgendwann auf PQ um? Bisher nein. Wenn das kommt, dürfte das unmittelbar sichtbar sein.
  • Kommt TLS 1.3 für Amazonbot? Zumindest das wäre ein Anfang.

Siehe auch

Wie immer: Bei Fragen, fragen.

DNS missbrauchen: Dateisysteme, DOOM und Tunnel durch Port 53

DNS-Missbrauch: Datenübertragung, Tunnel und C2-Kommunikation über DNS (Port 53)

Es gibt Dinge, bei denen man sich fragt, ob die Menschheit vielleicht einfach zu viel Freizeit hat. DNS ist so ein Protokoll, das eigentlich nur eine Aufgabe hat: Namen in IP-Adressen auflösen. Fertig. Simpel. Seit 1983 im Dienst. Aber nein, das reicht manchen Leuten natürlich nicht. Irgendwer schaut sich DNS an und denkt: „Da geht noch was.“ Und dann passieren Dinge. Michael hat mich zuletzt noch einmal daran erinnert (dankööö).

Ich habe mir mal ein paar Projekte angeschaut, die DNS auf eine Art und Weise nutzen, die man nur als kreative Vergewaltigung bezeichnen kann. Jedes einzelne davon ist gleichzeitig brillant und komplett wahnsinnig.

dnsfs: DNS-Resolver als Festplatte

Diagramm: dnsfs speichert Datei-Chunks als TXT-Records in fremden DNS-Resolver-Caches

Ben Cox, bekannt als benjojo, hatte offensichtlich eines Tages die Idee: Was, wenn man die Caches von DNS-Resolvern weltweit als verteiltes Dateisystem benutzt? Nicht die eigenen Resolver. Die von anderen Leuten. Einfach so.

dnsfs zerlegt Dateien in Chunks, kodiert sie als Base64 und schiebt sie als TXT-Records mit einem TTL von 2.147.483.646 Sekunden raus. Das sind knapp 68 Jahre. An fremde, offene DNS-Resolver. Die cachen das brav, und wenn man die Datei wieder haben will, fragt man dieselben Resolver einfach nochmal. Jeder Chunk wird auf drei verschiedenen Resolvern abgelegt, falls einer mal seinen Cache leert.

Verschlüsselung? Nein. Integritätsprüfung? Auch nein. Die Fehlerbehandlung beim Upload besteht darin, 2,5 Sekunden zu warten und bei Misserfolg ein :3 auszugeben. Als Null-Wert verwendet der Code den String „kittens“. Ich liebe es.

Ben hat das Ganze natürlich auch verbloggt und den Titel „true cloud storage“ gegeben. Technisch korrekt. Die Daten liegen ja wirklich verteilt in der Cloud. Nur halt in der Cloud anderer Leute.

DOOM over DNS: Es läuft immer DOOM

Screenshot: DOOM laeuft, geladen aus DNS TXT-Records ueber Cloudflare

Man kennt das Meme: „But can it run DOOM?“ Die Antwort ist immer ja. Taschenrechner, Schwangerschaftstests, Geldautomaten, alles läuft DOOM. Aber doom-over-dns treibt das auf die Spitze.

Die komplette DOOM-WAD (~4 MB) und die Game-Engine werden in fast 2.000 DNS TXT-Records zerlegt und über die Cloudflare API hochgeladen. Ein PowerShell-Skript fragt diese Records zur Laufzeit ab, setzt alles im RAM zusammen und startet das Spiel. Keine Datei wird jemals auf die Festplatte geschrieben. DOOM materialisiert sich quasi aus dem DNS.

Der besondere Clou: Cloudflare liefert die Records über sein globales CDN aus. Man bekommt also kostenloses, weltweites Content-Delivery für DOOM. Im Free-Tier. Man braucht allerdings mehrere Domains, weil Cloudflare die Anzahl der TXT-Records pro Zone begrenzt. Kein Sound, nur Windows, aber hey, es ist DOOM. Aus DNS. Was will man mehr.

iodine: VPN durch die Hintertür

Diagramm: iodine tunnelt IPv4-Traffic durch DNS-Queries an einem restriktiven Netzwerk vorbei

Während die ersten beiden Projekte eher in die Kategorie „weil man es kann“ fallen, ist iodine bitterer Ernst. Seit 2006 aktiv, 7.700 Stars auf GitHub, in C geschrieben und absolut produktionstauglich.

iodine tunnelt kompletten IPv4-Traffic durch DNS-Queries. Daten werden in Subdomain-Labels kodiert (Base32, Base64 oder Base128, je nachdem was der Resolver durchlässt), die Antworten kommen als NULL-, TXT-, SRV-, MX- oder A-Records zurück. Auf beiden Seiten wird ein TUN-Device erstellt und man hat einen vollständigen IP-Tunnel. Durch DNS. Port 53.

Der klassische Use-Case: Du sitzt im Hotel oder am Flughafen, das WLAN kostet 15 Euro pro Stunde, aber DNS-Queries gehen durch. iodine raus, SSH drüber, fertig. Kein besonders schneller Tunnel, einstellige Mbit/s wenn man Glück hat, aber es funktioniert. Seit fast 20 Jahren. Weil DNS-Traffic einfach fast nie geblockt wird.

dnscat2: Command & Control für Pentester

Diagramm: dnscat2 C2-Framework kommuniziert verschluesselt ueber die DNS-Hierarchie

Wo iodine ein Tunnel ist, ist dnscat2 ein komplettes C2-Framework. Ron Bowes hat das Ding für Penetration-Tests gebaut, und es kann deutlich mehr als nur Daten durchschleusen.

Ein C-Client auf dem Zielrechner kommuniziert über DNS-Queries (TXT, CNAME, MX) mit einem Ruby-Server auf dem eigenen autoritativen Nameserver. Der Traffic traversiert die normale DNS-Hierarchie, sieht also für jeden Beobachter aus wie ganz normales DNS. Das Framework bietet interaktive Shells, Dateitransfer, Port-Forwarding und Multi-Session-Management mit einer Metasploit-artigen Konsole.

Die Verschlüsselung nutzt ECDH, Salsa20 und SHA3. Allerdings ist das Crypto selbst designed und wurde nie professionell auditiert. In einem Pentest ist das okay. Für alles andere, naja.

DNSExfiltrator: Daten rausschmuggeln

Screenshot: DNSExfiltrator exfiltriert verschluesselte Dateien ueber DNS-Subdomain-Queries

DNSExfiltrator macht genau das, was der Name sagt: Dateien über DNS-Queries aus einem Netzwerk schmuggeln. Die Datei wird komprimiert, mit RC4 oder AES verschlüsselt und in Base64-kodierte Subdomain-Labels zerlegt. Jedes Label ist eine DNS-Query an den eigenen autoritativen Nameserver, der die Chunks reassembliert.

Das ist kein Spaßprojekt mehr. DNSExfiltrator wird in echten Red-Team-Assessments eingesetzt. Es funktioniert fast überall, weil kaum eine Firewall DNS-Traffic komplett blockiert. Der Client läuft als PowerShell-Skript oder kompiliertes C#, also genau das, was man auf einer Windows-Kiste in einem Unternehmensnetzwerk vorfindet.

Warum DNS?

Die ehrliche Antwort: Weil DNS überall durchkommt. Port 53 ist der eine Port, den wirklich jede Firewall aufmacht. DNS-Traffic wird selten inspiziert, selten rate-limited, selten als verdächtig eingestuft. Das Protokoll ist so fundamental für das Funktionieren des Internets, dass man es schlecht abdrehen kann. Und genau das macht es zum perfekten Kanal für alles, wofür es nie gedacht war.

TXT-Records nehmen quasi beliebigen Text auf. Subdomain-Labels können kodierte Daten enthalten. TTLs bestimmen, wie lange Resolver Daten cachen. Das sind alles Features, die für völlig legitime Zwecke existieren, aber in Kombination ein erstaunlich flexibles Daten-Transportmedium ergeben.

Von „Dateisystem in fremden Resolver-Caches“ über „DOOM aus TXT-Records“ bis hin zu „vollständiges C2-Framework für Pentester“: DNS hält das alles aus. Das Protokoll ist 43 Jahre alt und wurde seitdem in einer Art missbraucht, die sich Paul Mockapetris 1983 sicherlich nicht vorgestellt hat. Aber es funktioniert. Und das ist irgendwie das Schönste daran.

Sollte man eines dieser Projekte produktiv einsetzen? Auf gar keinen Fall. Sind sie trotzdem großartig? Absolut. Manchmal ist die richtige Reaktion auf „Aber warum?“ einfach: „Weil es geht.“

Ich bin vor kurzem auf Podcast „Security as a Podcast“ aufmerksam gemacht worden. Hier werden verschiedene Themen um DNS und Security behandelt. Ebenfalls wird dort erklärt, warum DNS hin und wieder so „missbraucht“ wird, speziell das DNSExfiltrations Thema hat mir gefallen.

Siehe auch:

Fragen oder eigene DNS-Verbrechen zu gestehen? Dann kannst du mich gerne fragen.

Post-Quantum TLS für Nginx — X25519MLKEM768 auf FreeBSD 15 konfigurieren

Nachdem ich zuerst OpenSSH und dann Postfix und Dovecot mit Post-Quantum-Kryptografie ausgestattet habe, kamen einige Rückfragen: Wie sieht das eigentlich für Nginx aus? Kann man das auf dem Webserver genauso einfach aktivieren? Kurze Antwort: Ja. Noch kürzer sogar als bei E-Mail.

Nginx TLS-Konfiguration mit Post-Quantum-Key-Exchange X25519MLKEM768 für HTTPS.

Spannend finde ich dabei, dass ausgerechnet der Webserver die meisten Nachfragen erzeugt hat. SSH und E-Mail laufen hier längst mit X25519MLKEM768, aber die Leser wollten vor allem wissen, wie das für HTTPS geht. Vermutlich weil jeder eine Webseite hat, aber nicht jeder seinen eigenen Mailserver betreibt?! Es kommt ja immer darauf an, was man macht und welche Daten man übermittelt. Aber SSH oder E-Mail würde mich selbst nervöser machen als normales surfen. Wobei…. Ich melde mich ja auch an, hm. OK, immer MFA. Na, eine normale E-Mail ist ja schon immer eine Postkarte, wenn man keine zusätzliche Verschlüsselung nutzt (was kaum jemand macht).

Worum geht es?

Wer die Vorgeschichte noch nicht kennt: X25519MLKEM768 ist ein hybrider Schlüsselaustausch, der klassisches X25519 (Curve25519 ECDH) mit dem Post-Quantum-Algorithmus ML-KEM-768 kombiniert. Standardisiert vom NIST als FIPS 203. Der Vorteil des hybriden Ansatzes: Selbst wenn sich ML-KEM irgendwann als unsicher herausstellt, bleibt X25519 als Absicherung. Und andersherum genauso.

Das „Store now, decrypt later“ Szenario kennt ihr vielleicht schon aus den anderen Beiträgen. Jemand schneidet heute euren TLS-verschlüsselten Datenverkehr mit und entschlüsselt ihn in ein paar Jahren mit einem Quantencomputer. Bei HTTPS betrifft das alles, was über die Leitung geht: Formulardaten, Login-Credentials, API-Aufrufe, Session-Cookies. Ob das in der Praxis relevant ist? Kommt auf euer Bedrohungsmodell an. Aber der Aufwand für die Absicherung ist so gering, dass es keinen Grund gibt, es nicht zu tun.

Es hängt an OpenSSL, nicht an Nginx

Das ist eigentlich die zentrale Erkenntnis aus allen drei Beiträgen: Ob PQC funktioniert oder nicht, entscheidet fast ausschließlich die OpenSSL-Version. Nginx, Postfix, Dovecot, OpenSSH, sie alle delegieren den Schlüsselaustausch an OpenSSL (oder LibreSSL, BoringSSL, je nach System). Die Anwendung selbst muss lediglich die gewünschte Gruppe konfigurieren können. Und das können alle genannten Programme seit Jahren.

Konkret braucht ihr OpenSSL 3.5 oder neuer. Erst ab dieser Version ist ML-KEM nativ im Default-Provider enthalten, ohne externen OQS-Provider, ohne liboqs, ohne selbst kompilieren. FreeBSD 15 liefert das von Haus aus. Bei den meisten Linux-Distributionen sieht es Stand heute leider noch anders aus. Ubuntu 24.04 hat OpenSSL 3.0, Debian 12 hat 3.0, RHEL 9 hat 3.0. Für ein aktuelles OpenSSL müsst ihr dort entweder selbst bauen oder auf neuere Releases warten.

Voraussetzungen prüfen

Auf meinem FreeBSD 15:

$ openssl version
OpenSSL 3.5.4 30 Sep 2025 (Library: OpenSSL 3.5.4 30 Sep 2025)

Nginx muss natürlich gegen dieses OpenSSL gelinkt sein. Das prüft ihr so:

$ nginx -V 2>&1 | grep -oE 'OpenSSL [0-9]+\.[0-9]+\.[0-9]+'
OpenSSL 3.5.4

Und dann die entscheidende Frage: Kennt OpenSSL die Gruppe X25519MLKEM768?

$ openssl list -tls-groups | grep -i mlkem
  X25519MLKEM768
  SecP256r1MLKEM768
  SecP384r1MLKEM1024

Wenn X25519MLKEM768 in der Liste auftaucht, kann es losgehen.

Nginx konfigurieren

In Nginx heißt die relevante Direktive ssl_ecdh_curve. Der Name ist etwas irreführend, denn sie steuert nicht nur ECDH-Kurven, sondern alle Key-Exchange-Gruppen die OpenSSL kennt. Also auch hybride PQC-Gruppen.

Meine Konfiguration in der TLS-Defaults-Datei, die per include in alle vHosts eingebunden wird:

ssl_ecdh_curve  X25519MLKEM768:X25519:secp384r1:prime256v1;

Das war’s. Eine Zeile. X25519MLKEM768 steht als bevorzugte Gruppe ganz vorne. Dahinter folgen die klassischen Kurven als Fallback für Clients, die noch kein ML-KEM sprechen. Die Reihenfolge ist die Präferenz.

Wer die Direktive lieber pro vHost setzen möchte, statt global, kann das natürlich auch tun. Ich bevorzuge eine zentrale TLS-Datei, weil ich sonst bei jedem TLS-Update zwanzig Configs anfassen müsste.

Zusätzlich habe ich die TLS 1.3 Cipher Suites explizit gesetzt:

ssl_conf_command  Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256;
ssl_conf_command  Options PrioritizeChaCha;

ChaCha20 als erste Wahl, weil es auf Clients ohne AES-NI (ältere Smartphones, ARM-Geräte) deutlich schneller ist. Auf Servern mit AES-NI ist der Unterschied minimal. PrioritizeChaCha sorgt dafür, dass der Server ChaCha20 bevorzugt, wenn der Client es an erster Stelle anbietet.

Die komplette TLS-Konfiguration sieht bei mir so aus:

# Protokolle
ssl_protocols              TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers  on;

# Key-Exchange-Gruppen (Reihenfolge = Präferenz)
ssl_ecdh_curve             X25519MLKEM768:X25519:secp384r1:prime256v1;

# TLS 1.3 Cipher Suites
ssl_conf_command           Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256;
ssl_conf_command           Options PrioritizeChaCha;

# TLS 1.2 Cipher Suites (nur ECDSA, kein RSA)
ssl_ciphers                ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256;

# Session Handling
ssl_session_cache          shared:SSL:50m;
ssl_session_timeout        1d;
ssl_session_tickets        off;

# OCSP Stapling
ssl_stapling               on;
ssl_stapling_verify        on;

# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

Danach die Konfiguration testen und Nginx neu laden:

# nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
# service nginx reload

Überprüfen

Funktioniert es? Mit openssl s_client lässt sich das schnell prüfen:

$ openssl s_client -connect www.kernel-error.de:443 \
    -groups X25519MLKEM768 -brief </dev/null 2>&1 | grep -E 'Protocol|group|Cipher'
Protocol version: TLSv1.3
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256
Negotiated TLS1.3 group: X25519MLKEM768

TLSv1.3 mit X25519MLKEM768. Läuft. Der hybride Post-Quantum-Schlüsselaustausch ist aktiv.

Und was passiert, wenn ein Client kein ML-KEM kann?

$ openssl s_client -connect www.kernel-error.de:443 \
    -groups X25519 -brief </dev/null 2>&1 | grep -E 'Protocol|group|Cipher'
Protocol version: TLSv1.3
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256

Sauberer Fallback auf X25519. Kein Fehler, keine Unterbrechung. Der Client bekommt einfach die stärkste Gruppe, die beide Seiten gemeinsam haben.

Browser-Support

Das Schöne bei HTTPS im Vergleich zu E-Mail oder SSH: Die Browser haben Post-Quantum-Kryptografie teilweise schon vor den Servern implementiert. Chrome verwendet seit Version 124 (April 2024) X25519MLKEM768 als bevorzugte Gruppe. Firefox seit Version 124 ebenfalls. Safari seit macOS 15 Sequoia und iOS 18. Edge zieht mit Chrome mit.

Wenn ihr also Nginx mit X25519MLKEM768 konfiguriert, profitieren die meisten eurer Besucher sofort davon. Ohne dass sie irgendetwas tun müssen. Das ist bei SSH und E-Mail anders, da sind die Clients noch nicht so weit. Womit sich auch erklärt, warum das Thema bei Webservern mehr Aufmerksamkeit bekommt.

HTTP/3 und QUIC

Wer wie ich HTTP/3 (QUIC) aktiviert hat, bekommt PQC dort automatisch mit. QUIC verwendet TLS 1.3 als Transportverschlüsselung und nutzt dieselbe ssl_ecdh_curve Konfiguration. Kein zusätzlicher Konfigurationsaufwand.

listen [::]:443 quic reuseport;
listen 443 quic reuseport;

Wer QUIC noch nicht nutzt: Der Overhead ist minimal, der Vorteil bei mobilen Verbindungen deutlich spürbar. Mehr dazu in meinem HTTP/3 Beitrag.

Davon abgesehen, wie ist das eigentlich mit Microsoft? Cloudflare macht ja schon knapp zu 66% PQC im HTTP Traffic. Es ist 2026, fragen wir doch mal eine AI: claude „Sag mal, wie ist das eigentlich bei Microsoft Produkten, wie Exchange oder IIS im Bezug auf PQC? Wird das schon unterstützt oder kommt das bald?“

● Gute Frage — Microsoft ist da tatsächlich in einer interessanten Position:                                                                                                                                                                                                                                                  
                 
  Windows / Schannel (TLS-Stack)                                                                                                                                                                                                                                                                                              
                  
  - Windows 11 24H2 / Windows Server 2025 haben experimentelle Unterstützung für ML-KEM (X25519MLKEM768) in TLS 1.3 bekommen — allerdings zunächst nur als Preview/Feature-Flag, nicht standardmäßig aktiv.                                                                                                                   
  - Der zugrunde liegende TLS-Stack (Schannel) muss das können, damit IIS und Exchange davon profitieren.                                                                                                                                                                                                                     

  IIS

  - IIS nutzt Schannel direkt. Sobald Schannel auf dem jeweiligen Windows Server PQC-Key-Exchange unterstützt und es aktiviert ist, kann IIS das theoretisch sofort.
  - Stand meines Wissens (Mai 2025): Noch kein produktiver PQC-Support in IIS. Die Preview in Windows Server 2025 ist eher für Tests gedacht.

  Exchange

  - Exchange Online (Microsoft 365): Microsoft hat angekündigt, PQC schrittweise in ihre Cloud-Dienste zu integrieren. Für TLS-Verbindungen zwischen Microsoft-Datacentern wurde bereits mit PQC experimentiert.
  - Exchange On-Premises: Hängt komplett am darunterliegenden Windows Server / Schannel. Solange Schannel kein produktives PQC kann, kann Exchange es auch nicht.

  Microsofts Roadmap

  - Microsoft hat ein "Quantum-Safe Program" und arbeitet aktiv an PQC-Integration in Windows, Azure und M365.
  - Die Priorität liegt auf TLS 1.3 mit ML-KEM (hybrid, also X25519MLKEM768.
  - Einen festen Zeitplan für "PQC by default" in Exchange/IIS gibt es meines Wissens noch nicht.

  Fazit

  Microsoft ist hintendran verglichen mit dem Open-Source-Stack. OpenSSL 3.5+ / LibreSSL / BoringSSL haben PQC-Support bereits produktionsreif. Bei Microsoft wartet man darauf, dass Schannel das nativ und stabil liefert — und bis das
  in Exchange On-Prem ankommt, dauert es erfahrungsgemäß nochmal länger.

  Für Exchange Online dürfte Microsoft das irgendwann einfach serverseitig einschalten, ohne dass Admins etwas tun müssen. Für On-Prem heißt es: warten auf den nächsten Windows Server Release, der PQC in Schannel GA (General Availability) hat.

  Caveat: Mein Wissensstand ist Mai 2025 — es kann sein, dass Microsoft seitdem konkretere Termine genannt hat. Wäre ggf. einen Blick in den Microsoft Security Blog wert.

Klingt gut. Ich muss ja zugeben, dass ich, seit ich nicht mehr mit Martin Peuker bei Items arbeite, irgendwie komplett von der Microsoft-Welt abgekoppelt bin. Oh, ist das jetzt korrekt von der AI? Keine Ahnung, „klingt“ richtig.

Was das nicht leistet

Den Absatz kennt ihr inzwischen aus den anderen Beiträgen, aber er gehört dazu: Wir sichern hier den Schlüsselaustausch ab, nicht die Authentifizierung. Die TLS-Zertifikate nutzen weiterhin klassische Algorithmen (in meinem Fall ECDSA P-384). Für Post-Quantum-Signaturen bräuchte man ML-DSA (ehemals CRYSTALS-Dilithium) in den Zertifikaten, aber keine öffentliche CA stellt solche Zertifikate aus. Das wird kommen, aber noch nicht heute.

In der Praxis heißt das: Ein Angreifer mit Quantencomputer könnte die Serverauthentifizierung angreifen, müsste das aber in Echtzeit tun. „Store now, decrypt later“ greift dort nicht. Der Schlüsselaustausch und damit die Vertraulichkeit eurer Daten ist durch X25519MLKEM768 geschützt. Auch in Zukunft.

Fazit

Eine Zeile in der Nginx-Konfiguration, ein Reload, fertig. Euer Webserver verhandelt danach mit jedem modernen Browser einen quantensicheren Schlüsselaustausch. Vollständig abwärtskompatibel für ältere Clients. Kein Risiko, kein Aufwand, kein Nachteil.

Die eigentliche Hürde ist nicht Nginx, sondern die OpenSSL-Version auf eurem System. Wer FreeBSD 15 oder ein System mit OpenSSL 3.5+ hat, kann sofort loslegen. Alle anderen müssen auf ihre Distribution warten oder selbst bauen.

Damit habe ich jetzt SSH, E-Mail und Webserver mit Post-Quantum-Kryptografie abgedeckt. Fehlt eigentlich nur noch DNS. Aber DoH und DoT laufen ja auch über TLS … *grübel*

Update: Inzwischen habe ich HTTPS RR und SVCB Records für alle Dienste deployt. Damit wissen Clients schon beim DNS-Lookup, dass HTTP/3 und QUIC verfügbar sind.

Update 2: Wer wissen will, ob die eigenen Besucher tatsächlich PQC nutzen: Nginx kann die ausgehandelte Key-Exchange-Gruppe loggen. Die Variable $ssl_curve zeigt pro Verbindung, ob X25519MLKEM768, klassisches X25519 oder etwas anderes verhandelt wurde. Einfach ins bestehende Log-Format einfügen, irgendwo nach $ssl_cipher:

log_format combined_pqc
  '$remote_addr - $remote_user [$time_local] '
  '"$request" $status $body_bytes_sent '
  '"$http_referer" "$http_user_agent" '
  '$ssl_protocol $ssl_cipher $ssl_curve';

Im Log sieht das dann so aus. Ein Chrome mit PQC-Support:

203.0.113.42 - - [07/Apr/2026:13:22:36 +0200] "GET / HTTP/2.0" 200 56687 "-" "Mozilla/5.0 [...] Chrome/146.0.0.0" TLSv1.3 TLS_CHACHA20_POLY1305_SHA256 X25519MLKEM768

Und ein älterer Client ohne PQC:

198.51.100.7 - - [07/Apr/2026:14:05:11 +0200] "GET / HTTP/2.0" 200 56687 "-" "Mozilla/5.0 [...] Chrome/109.0.0.0" TLSv1.3 TLS_AES_256_GCM_SHA384 X25519

Der Unterschied steht ganz am Ende: X25519MLKEM768 vs. X25519. Ein Reload, ein paar Wochen sammeln, und ihr habt echte Zahlen statt Theorie. Ich werde die Ergebnisse in einem eigenen Beitrag auswerten, sobald genug Daten zusammengekommen sind.

Wie immer: Bei Fragen, fragen.

Quantis USB – Alter Quantenzufall aus der Schublade

Ich hatte noch einen Quantis USB in der Schublade liegen. Einen Hardware-Quantenzufallsgenerator von ID Quantique aus Genf. Ein Gerät, das echten Zufall erzeugt. Nicht pseudo, nicht algorithmisch, nicht „irgendwie aus Interrupts zusammengewürfelt“, sondern auf Basis von Quantenphysik. Fundamental unvorhersagbar.

Image of quantis usb

Nachdem ich in den letzten Beiträgen OpenSSH und Postfix/Dovecot mit Post-Quantum-Kryptografie abgesichert habe, fiel mir wieder ein: PQC schützt die Algorithmen vor Quantencomputern. Schön und gut. Aber was ist eigentlich mit der Zufallsquelle, die diese Algorithmen füttert? Zeit, das Teil mal wieder anzuschließen und zu schauen, was es taugt.

Was steckt in dem Gerät?

Der Quantis USB von ID Quantique ist ein sogenannter Quantum Random Number Generator, kurz QRNG. Das Prinzip dahinter: Ein Photonendetektor misst quantenoptisches Vakuumrauschen. Das sind Fluktuationen im elektromagnetischen Feld, die nach den Gesetzen der Quantenmechanik fundamental zufällig sind. Nicht „fast zufällig“ oder „praktisch zufällig“, sondern physikalisch beweisbar unvorhersagbar. Das ist ein wichtiger Unterschied zu allem, was ein Algorithmus je leisten kann. Dazu gleich mehr.

Das Gerät selbst ist fast schon enttäuschend simpel. USB 2.0 High-Speed, ein einziger Bulk-IN-Endpoint (0x86), 512 Bytes pro Read, rund 4 Mbit/s Durchsatz. Flashbare Firmware gibt es nicht. Die „Intelligenz“ steckt in der Optik und einem FPGA, nicht in Software. Das Ding macht genau eine Sache, und die macht es gut.

Mein Testgerät hat die Seriennummer 132244A410. Der Quantis USB ist inzwischen ein Legacy-Produkt, ID Quantique hat einen Nachfolger mit höherem Durchsatz im Programm. Einen öffentlichen Preis hatte das Gerät nie. „Request a Quote“, wie das bei Nischenprodukten mit Zertifizierungsanforderungen so üblich ist. Das Gerät ist METAS-zertifiziert und war für Kunden gedacht, die Common-Criteria-Anforderungen erfüllen müssen. Vergleichbare QRNGs bewegen sich im Bereich von 900 bis 2.000 Euro. Nicht gerade ein Impulskauf.

Einrichten unter Linux

Angeschlossen an mein Linux Mint 22.3 (Ubuntu 24.04 Basis) meldet sich das Gerät sofort im Kernel-Log:

$ dmesg | tail
usb 1-1: New USB device found, idVendor=0aba, idProduct=0102
usb 1-1: Product: Quantis USB
usb 1-1: Manufacturer: id Quantique
usb 1-1: SerialNumber: 132244A410

Kein spezieller Treiber nötig. Das ist ein generisches USB-Bulk-Device, der Kernel erkennt es und das war’s. Die proprietäre libquantis von ID Quantique kann man sich komplett sparen. Man kann direkt mit pyusb auf den Endpoint zugreifen. So mag ich das.

Damit das auch ohne Root funktioniert, legt man eine udev-Regel an:

# /etc/udev/rules.d/99-quantis.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="0aba", ATTR{idProduct}=="0102", MODE="0666", GROUP="plugdev", TAG+="uaccess"

Danach:

$ sudo udevadm control --reload-rules && sudo udevadm trigger

Gerät abstecken, wieder anstecken, fertig. Ab jetzt kann jeder Benutzer in der Gruppe plugdev auf das Gerät zugreifen.

Daten lesen mit Python

Zum Auslesen reicht das Paket python3-usb (pyusb). Installieren via apt install python3-usb, falls nicht vorhanden. Dann braucht man erstaunlich wenig Code:

import usb.core, usb.util

dev = usb.core.find(idVendor=0x0ABA, idProduct=0x0102)
dev.set_configuration()

cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]
ep = usb.util.find_descriptor(
    intf,
    custom_match=lambda e:
        usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN
)

data = ep.read(512, timeout=5000)
print(f"{len(data)} Bytes Quantenzufall gelesen")

Das ist alles. USB öffnen, Configuration setzen, den einen IN-Endpoint finden, 512 Bytes lesen. Fertig. Kein SDK, keine Bibliothek, kein Account, kein Cloud-Dienst. USB rein, Bytes raus.

Wichtig: Immer volle 512-Byte-Blöcke lesen (wMaxPacketSize). Wer weniger anfordert, bekommt USB-Overflow-Fehler. Das Gerät kennt keine halben Sachen. Es produziert kontinuierlich Zufallsdaten und schiebt sie in den USB-Puffer. Die müssen abgeholt werden, so wie sie kommen.

Für den Test habe ich das Ganze in eine Schleife gepackt und 100.000 Bytes gesammelt. Parallel dazu 100.000 Bytes aus /dev/urandom. Beide Datensätze dann durch dieselben statistischen Tests gejagt.

Der Test: Quantis vs. /dev/urandom

Jetzt wird’s spannend. Wie gut ist echter Quantenzufall im Vergleich zum Software-PRNG des Linux-Kernels?

Spoiler: Statistisch seht ihr keinen Unterschied. Und genau das ist der Punkt.

MetrikQuantis USB/dev/urandom
Shannon-Entropie7,998513 Bits/Byte7,998077 Bits/Byte
Maximum (theoretisch)8,0000008,000000
Effizienz99,9814 %99,9760 %
Chi² (Byte-Verteilung)205,8267,3
Erwartet (Chi²)~255 ± 23~255 ± 23
Bit-Balance (Anteil Einsen)49,975 %50,018 %
Serielle Korrelation+0,001230+0,003801
Längster Bit-Run (10 kB)15 Bits21 Bits
Erwarteter Run~16~16

Die Shannon-Entropie liegt bei beiden Quellen über 99,97 % des theoretischen Maximums von 8 Bit pro Byte. Das ist hervorragend. Die Chi²-Werte zeigen eine gleichmäßige Byte-Verteilung, beide liegen im erwarteten Bereich um 255. Die Bit-Balance ist nahezu perfekt bei 50/50, die serielle Korrelation praktisch null.

In den einfachen Tests schneidet der Quantis sogar minimal besser ab: niedrigere Korrelation, gleichmäßigere Verteilung, kürzerer maximaler Bit-Run. Aber ehrlich gesagt liegt das im statistischen Rauschen. Bei 100.000 Bytes Sample-Größe kann man keine belastbare Aussage über die Überlegenheit einer Quelle treffen. Man müsste Millionen oder Milliarden Bytes testen und Testsuiten wie die NIST SP 800-22 oder Dieharder durchlaufen lassen, um wirklich statistisch signifikante Unterschiede zu finden.

Heißt das, der Quantis ist überflüssig? Nein. Denn der Unterschied liegt nicht in der Statistik.

Wo liegt dann der echte Unterschied?

Die spannende Frage ist nicht, ob die Zahlen „zufälliger“ sind, sondern warum sie es sind.

/dev/urandom verwendet intern ChaCha20, einen deterministischen CSPRNG (Cryptographically Secure Pseudo-Random Number Generator). Der initiale Seed kommt aus der Kernel-Entropie: Hardware-Interrupts, Timing-Jitter, Geräte-Events, und seit einigen Jahren auch RDRAND/RDSEED aus der CPU, falls vorhanden. Das funktioniert in der Praxis hervorragend und ist extrem gut untersucht.

Aber es bleibt ein Algorithmus mit einem internen State. Wer diesen State kennt (und sei es nur theoretisch), kann alle zukünftigen Outputs berechnen. Das ist kein realistisches Angriffsszenario für euren Laptop. Aber es ist eine fundamentale Eigenschaft: Die Sicherheit von /dev/urandom basiert auf Berechnungsannahmen. Man nimmt an, dass ChaCha20 nicht effizient invertierbar ist. Stand heute stimmt das. Aber es ist eine Annahme, kein Beweis.

Der Quantis hingegen erzeugt Zufall aus Quantenvakuum-Fluktuationen. Da gibt es keinen Algorithmus, keinen State, keinen Seed. Die Unvorhersagbarkeit ist nicht durch die Komplexität eines Algorithmus geschützt, sondern durch die Gesetze der Quantenmechanik. Kein Angreifer, egal mit welcher Rechenleistung und egal mit wie viel Zeit, kann die nächsten Bits vorhersagen. Auch kein Quantencomputer. Das ist nicht berechnungstheoretisch sicher, sondern informationstheoretisch sicher. Die höchste Sicherheitskategorie, die es gibt.

Klingt akademisch? Zum Teil. Für den Alltag auf eurem Desktop oder Server reicht /dev/urandom völlig aus. Es gibt keinen bekannten praktischen Angriff darauf, und Linux‘ CSPRNG ist schnell, überall verfügbar und gut gewartet.

Aber es gibt Szenarien, in denen der Unterschied real zählt:

  • Erzeugung kryptografischer Schlüssel mit höchsten Sicherheitsanforderungen
  • Seeding von HSMs (Hardware Security Modules), die selbst keine eigene Entropiequelle haben
  • Regulatorische und Zertifizierungsanforderungen, also Common Criteria, FIPS-Validierung, BSI-Vorgaben
  • Wissenschaftliche Experimente, die physikalisch echten Zufall benötigen (z. B. Quantenoptik, Monte-Carlo-Simulationen)
  • Quantenschlüsselaustausch (QKD), ein Bereich in dem ID Quantique ebenfalls aktiv ist

Das größere Bild: QRNG und PQC

Post-Quantum Cryptography schützt kryptografische Algorithmen davor, von Quantencomputern gebrochen zu werden. ML-KEM für den Schlüsselaustausch, ML-DSA für Signaturen. Das ist die eine Seite der Medaille.

Die andere Seite ist die Zufallsquelle. Ein kryptografischer Algorithmus kann noch so quantensicher sein. Wenn der Zufall, mit dem Schlüssel erzeugt werden, vorhersagbar oder manipulierbar ist, hilft das alles nichts. Der Zufall ist das Fundament, auf dem alles andere aufbaut.

Ein QRNG schützt genau diesen Angriffsvektor. Beides zusammen, PQC-Algorithmen und eine quantenphysikalische Zufallsquelle, ergibt ein quantum-safe Gesamtsystem. Das ist heute für die meisten von uns Overkill. Aber die Bausteine existieren, sie sind verfügbar, und es schadet nicht zu wissen, wie sie funktionieren.

Übrigens: Wer jetzt denkt „dann stecke ich den Quantis in meinen Server und bin sicher“, der macht es sich zu einfach. Die Vertrauensfrage verschiebt sich nur. Woher weiß ich, dass das Gerät tatsächlich Quantenzufall liefert und nicht einfach einen internen PRNG hat? Bei einem zertifizierten Gerät wie dem Quantis gibt es dafür Prüfberichte. Aber Vertrauen in Hardware bleibt immer ein Thema. Das ist bei Intel RDRAND nicht anders.

Einbindung ins System

Für die Vollständigkeit: Der Quantis USB lässt sich über rng-tools (rngd) als zusätzliche Entropiequelle in den Kernel-Entropiepool einbinden. Für Server mit hohem Entropie-Bedarf, also TLS-Terminierung unter Last, Massenerzeugung von Schlüsseln oder VPN-Gateways, kann das sinnvoll sein.

Ich habe das auf meinem Desktop nicht gemacht. Brauche ich dort nicht. Aber die Möglichkeit steht im Raum, falls jemand von euch einen Quantis oder ein vergleichbares Gerät an einen Server hängen möchte.

Fazit

Ein alter Hardware-QRNG, ein USB-Port, ein paar Zeilen Python, und man hat echten Quantenzufall auf dem Tisch. Statistisch nicht unterscheidbar von /dev/urandom, aber fundamental anders in der Entstehung. Die Sicherheit kommt nicht aus einem Algorithmus, sondern aus der Physik. Informationstheoretisch statt berechnungstheoretisch. Ein Unterschied, der in den allermeisten Fällen keine praktische Rolle spielt. Aber ein verdammt eleganter.

Für euren Desktop braucht ihr das nicht. Aber verstehen, warum es existiert und wie es sich einordnet, gerade im Kontext von Post-Quantum-Kryptografie, das lohnt sich. Warum denke ich jetzt an CIA und MAD? ;-D

Wie haltet ihr es mit euren Zufallsquellen? Vertraut ihr blind auf /dev/urandom, oder habt ihr euch schon mal Gedanken über die Entropiequelle dahinter gemacht?

Post-Quantum TLS für E-Mail — Postfix und Dovecot mit X25519MLKEM768 auf FreeBSD 15

Visualisierung hybrider Post-Quantum-TLS-Verschlüsselung für E-Mail mit X25519MLKEM768 (ML-KEM-768 + X25519) auf Postfix und Dovecot unter FreeBSD 15

Siehe auch: Post-Quantum TLS für Nginx — X25519MLKEM768 auf FreeBSD 15 konfigurieren

Nachdem ich im letzten Beitrag OpenSSH mit hybriden Post-Quantum-Algorithmen abgesichert habe, lag die Frage nahe: Was ist eigentlich mit E-Mail? Mein FreeBSD 15 liefert Postfix 3.10.6, Dovecot 2.3.21.1 und OpenSSL 3.5.4 – und genau diese Kombination bringt alles mit, was man für quantensichere Verschlüsselung im Mailverkehr braucht. Ohne zusätzliche Pakete, ohne Patches, ohne Gefrickel.

Warum überhaupt PQC für E-Mail?

Das „Store now, decrypt later„-Szenario, das ich beim SSH-Beitrag angesprochen habe, trifft auf E-Mail mindestens genauso zu. E-Mails werden über SMTP zwischen Servern transportiert – und dieser Transport ist grundsätzlich abfangbar. Wer heute TLS-verschlüsselten Mailverkehr mitschneidet und archiviert, könnte diesen in einigen Jahren mit einem ausreichend leistungsfähigen Quantencomputer entschlüsseln. Zumindest theoretisch.

Heißt das, morgen liest jemand eure Mails? Nein. Aber wenn ihr vertrauliche Kommunikation betreibt und die heute eingesetzte Kryptografie in zehn Jahren noch standhalten soll, ist jetzt der richtige Zeitpunkt zum Handeln. Zumal der Aufwand (wie ihr gleich seht) überschaubar ist.

Was steckt hinter X25519MLKEM768?

Kurz zur Einordnung: ML-KEM (ehemals CRYSTALS-Kyber) ist der vom NIST im August 2024 standardisierte Post-Quantum-Algorithmus für den Schlüsselaustausch (FIPS 203). X25519MLKEM768 ist ein sogenannter Hybrid-Algorithmus – er kombiniert das klassische X25519 (Curve25519 ECDH) mit ML-KEM-768 zu einem gemeinsamen Schlüssel.

Der Clou dabei: Selbst wenn ML-KEM irgendwann gebrochen werden sollte, bleibt die klassische X25519-Komponente intakt. Und umgekehrt. Man muss also nicht darauf vertrauen, dass der neue Algorithmus auch wirklich hält – man bekommt das Beste aus beiden Welten.

Wer Firefox nutzt, hat das übrigens vermutlich schon in Aktion gesehen: Seit Firefox 124 wird bei TLS 1.3 standardmäßig X25519MLKEM768 für den Schlüsselaustausch verwendet. Schaut mal in die Verbindungsdetails einer HTTPS-Seite – die Chancen stehen gut, dass dort bereits ein hybrider PQC-Schlüsselaustausch stattfindet. Also, wenn der Server das anbietet, wie dieser hier *zwinker*.

Voraussetzungen prüfen

Bevor ihr konfiguriert, solltet ihr sicherstellen, dass euer OpenSSL ML-KEM überhaupt kann. Auf meinem FreeBSD 15:

$ openssl version
OpenSSL 3.5.4 30 Sep 2025 (Library: OpenSSL 3.5.4 30 Sep 2025)

Und dann die entscheidende Frage – kennt OpenSSL die benötigten KEM-Algorithmen?

$ openssl list -kem-algorithms | grep -i mlkem
  ML-KEM-512
  ML-KEM-768
  ML-KEM-1024
  X25519MLKEM768
  SecP256r1MLKEM768

Wenn X25519MLKEM768 in der Liste auftaucht, seid ihr startklar. Das ist bei OpenSSL ab Version 3.5 der Fall – der ML-KEM-Support ist im Default-Provider enthalten, es wird kein zusätzlicher OQS-Provider und kein liboqs benötigt.

Noch ein Check – sind die Algorithmen auch als TLS-Gruppen verfügbar?

$ openssl list -tls-groups | grep -i mlkem
  X25519MLKEM768
  SecP256r1MLKEM768
  SecP384r1MLKEM1024

Perfekt. Weiter geht’s.

Postfix konfigurieren

Postfix steuert die verwendeten TLS-Gruppen für den Schlüsselaustausch über den Parameter tls_eecdh_auto_curves. Dieser gilt sowohl für eingehende (smtpd) als auch für ausgehende (smtp) Verbindungen.

Vorher:

tls_eecdh_auto_curves = X25519, prime256v1, secp384r1

Nachher:

tls_eecdh_auto_curves = X25519MLKEM768, X25519, prime256v1, secp384r1

Das war’s. Eine Zeile. X25519MLKEM768 wird als bevorzugte Gruppe an den Anfang gestellt, die klassischen Kurven bleiben als Fallback erhalten. Clients die kein ML-KEM beherrschen, verhandeln einfach X25519 oder prime256v1 – die Abwärtskompatibilität bleibt also vollständig gewahrt.

Die Änderung setzt ihr entweder direkt in /usr/local/etc/postfix/main.cf oder über:

# postconf "tls_eecdh_auto_curves = X25519MLKEM768, X25519, prime256v1, secp384r1"
# postfix reload

Wichtig: Dieser Parameter beeinflusst alle Postfix-Dienste – SMTP (Port 25), Submission (Port 587) und SMTPS (Port 465). Ihr müsst also nicht jeden Port einzeln konfigurieren.

Update 01.04.2026: Die oben gezeigte globale Methode über main.cf hat einen Haken, den ich erst später auf der Postfix-Users Mailingliste realisiert habe. Die korrigierte Konfiguration mit getrennten Einstellungen für Inbound und Outbound findet ihr weiter unten im Nachtrag.

Dovecot konfigurieren

Dovecot verwendet den Parameter ssl_curve_list um die TLS-Gruppen für IMAP-Verbindungen festzulegen. Standardmäßig ist dieser leer, was bedeutet, dass OpenSSL seine eigenen Defaults verwendet. Das kann funktionieren, muss aber nicht.

In /usr/local/etc/dovecot/conf.d/10-ssl.conf:

ssl_curve_list = X25519MLKEM768:X25519:prime256v1:secp384r1

Achtung: Dovecot verwendet Doppelpunkte als Trennzeichen (OpenSSL-Syntax), Postfix verwendet Kommas. Nicht verwechseln. Ja, passiert mir oft.

Danach:

# doveadm reload

Überprüfen

Jetzt wird’s spannend. Funktioniert es tatsächlich? Zum Testen verwende ich openssl s_client direkt auf dem Server; denn euer lokales Linux oder macOS hat möglicherweise noch kein OpenSSL 3.5 mit ML-KEM-Support. Mein Linux Mint 22.3 hat es leider noch nicht *schnief*

SMTP (Port 25, STARTTLS):

$ openssl s_client -connect smtp.kernel-error.de:25 -starttls smtp \
    -groups X25519MLKEM768 -brief </dev/null 2>&1 | grep -E 'Protocol|group'
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519MLKEM768

SMTPS (Port 465):

$ openssl s_client -connect smtp.kernel-error.de:465 \
    -groups X25519MLKEM768 -brief </dev/null 2>&1 | grep -E 'Protocol|group'
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519MLKEM768

Submission (Port 587, STARTTLS):

$ openssl s_client -connect smtp.kernel-error.de:587 -starttls smtp \
    -groups X25519MLKEM768 -brief </dev/null 2>&1 | grep -E 'Protocol|group'
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519MLKEM768

IMAPS (Port 993):

$ openssl s_client -connect imap.kernel-error.de:993 \
    -groups X25519MLKEM768 -brief </dev/null 2>&1 | grep -E 'Protocol|group'
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519MLKEM768

Alle vier Ports verhandeln TLSv1.3 mit X25519MLKEM768. Die hybride Post-Quantum-Verschlüsselung ist aktiv.

Wenn ihr testen wollt, was passiert wenn ein Client kein ML-KEM unterstützt:

$ openssl s_client -connect imap.kernel-error.de:465 \
    -groups X25519 -brief </dev/null 2>&1 | grep -E 'Protocol|group'
Protocol version: TLSv1.3
Negotiated TLS1.3 group: X25519

Fallback auf X25519 – funktioniert sauber.

Was das nicht leistet

Wie schon beim SSH-Beitrag muss ich auch hier einschränken: Wir sichern damit den Schlüsselaustausch ab, nicht die Authentifizierung. Die TLS-Zertifikate verwenden weiterhin klassische Algorithmen (RSA, ECDSA). Für Post-Quantum-Signaturen in Zertifikaten bräuchte man ML-DSA (ehemals CRYSTALS-Dilithium) – und obwohl OpenSSL 3.5 das theoretisch unterstützt, gibt es Stand heute keine öffentliche Zertifizierungsstelle, die ML-DSA-Zertifikate ausstellt. Das wird kommen, ist aber noch Zukunftsmusik. Hey, wie ECDSA bei S/MIME (oder ist das schon anders?).

Für die Praxis bedeutet das: Ein Angreifer mit einem Quantencomputer könnte theoretisch die Serverauthentifizierung angreifen (ECDSA/RSA brechen), müsste das aber in Echtzeit tun – hier greift „store now, decrypt later“ nicht, weil eine gefälschte Authentifizierung nur im Moment der Verbindung nützt. Der Schlüsselaustausch hingegen – und damit die eigentliche Vertraulichkeit der transportierten E-Mails – ist durch X25519MLKEM768 auch gegen zukünftige Quantenangriffe geschützt.

Nachtrag (01.04.2026): Inbound und Outbound trennen

Ich muss die Postfix-Konfiguration oben korrigieren. Der Parameter tls_eecdh_auto_curves gilt für SMTP-Client und SMTP-Server gleichzeitig. Steht X25519MLKEM768 an erster Stelle, wird der PQC Key-Share direkt im initialen ClientHello mitgeschickt. Das bläht den ClientHello von rund 400 auf über 1400 Bytes auf.

Klingt erstmal harmlos. Ist es aber nicht. Manche Zielserver kommen mit dem übergroßen ClientHello nicht klar, der TLS-Handshake scheitert. Bei smtp_tls_security_level = may fällt Postfix dann stillschweigend auf Plaintext zurück. Dasselbe passiert bei dane ohne TLSA-Records, also bei der Mehrheit aller Domains da draußen. Eure Mails gehen raus, aber unverschlüsselt. Super.

Das Problem ist, dass hier zwei unterschiedliche Policies in einem Parameter stecken. Inbound will man PQC bevorzugen, weil man selbst kontrolliert was der Server akzeptiert. Outbound will man Kompatibilität priorisieren, weil man nicht weiß was auf der anderen Seite steht. Die gehören nicht in einen globalen Parameter.

Die Lösung: tls_eecdh_auto_curves nicht mehr global in main.cf setzen, sondern per master.cf pro Dienst überschreiben.

Server-Seite (Inbound) – PQC bevorzugen, an allen smtpd-Listenern:

smtp      inet  n       -       n       -       -       smtpd
  -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1

submission inet n       -       n       -       -       smtpd
  -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1

smtps     inet  n       -       n       -       -       smtpd
  -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1

Client-Seite (Outbound) – kleiner ClientHello, PQC nur via HelloRetryRequest:

smtp      unix  -       -       n       -       -       smtp
  -o tls_eecdh_auto_curves=X25519,X25519MLKEM768,prime256v1,secp384r1

Der Trick: Outbound steht X25519 an erster Stelle. Der initiale ClientHello bleibt damit klein. X25519MLKEM768 steht trotzdem in den supported_groups und wird verhandelt, wenn der Zielserver per HelloRetryRequest nachzieht. Inbound bekommen moderne Clients dagegen sofort PQC.

Dovecot ist davon nicht betroffen. Da gibt es nur die Server-Seite, ssl_curve_list bleibt wie oben beschrieben.

Noch ein Wort zum Postfix-Default: postconf -d zeigt auf meinem 3.10.6 kein X25519MLKEM768 im Default. Die postconf(5)-Doku beschreibt zwar für neuere Builds ein Delayed-Key-Share-Verhalten, aber was die Doku beschreibt und was ein konkreter Build tut, können zwei verschiedene Dinge sein. Deshalb die explizite Trennung per master.cf. Danke an die Postfix-Users Mailingliste für die Diskussion, die mich auf dieses Problem aufmerksam gemacht hat und selbstverständlich an den Kommentierenden!

Zwei Zeilen Konfiguration, ein Reload pro Dienst – und euer Mailserver verhandelt quantensichere Verschlüsselung. Okay, es sind jetzt ein paar mehr Zeilen als ursprünglich versprochen. Aber die Trennung zwischen Inbound und Outbound ist es wert, denn blind auf Kompatibilität aller Zielserver zu hoffen ist keine Strategie.

Update 01.05.2026: Mit Postfix 3.11.1 (FreeBSD 15.0-RELEASE-p7) und OpenSSL 3.5 sind sowohl die main.cf-Variante als auch dieser master.cf-Workaround Geschichte – der Built-in-Default ?X25519MLKEM768:DEFAULT macht beides überflüssig. Wire-Mitschnitt, ClientHello-Größenvergleich und externe Verifikation im Folgebeitrag.

Viel Spaß beim Nachbauen – und wie immer: bei Fragen, fragen.

SSH-Bruteforce, DigitalOcean und AbuseIPDB – warum Blocken das Problem nicht löst

Aus gegebenem Anlass möchte ich ein paar Gedanken zu DigitalOcean aufschreiben. Nicht, weil ich glaube, dass DigitalOcean ein grundsätzliches Problem hat oder etwas falsch macht. Sondern weil DigitalOcean in meinen Logs seit Jahren besonders auffällt. Am Ende steht DigitalOcean hier eher sinnbildlich für ein größeres Thema.

Wer Systeme im Internet betreibt, kennt das Spiel. Server werden dauerhaft von außen angefasst. SSH-Ports werden gescannt, Login-Versuche laufen durch, Webseiten bekommen Requests auf bekannte Pfade, WordPress-Logins, XML-RPC, das volle Programm. Das ist kein gezielter Angriff, sondern automatisiertes Dauerrauschen. Bots, Skripte, Scanner, manchmal Security-Tools, manchmal schlicht schlecht konfigurierte Kisten.

Darstellung von automatisierten SSH-Bruteforce-Angriffen und Server-Härtung in Cloud-Umgebungen

Findet so ein Bot irgendwo ein offenes Loch, einen Standard-Login, ein vergessenes Passwort oder eine ungepatchte Schwachstelle, dann geht es weiter. Meistens wird erst einmal weitere Software nachgeladen. Der Host wird Teil eines Botnetzes, scannt selbst weiter, verteilt Spam, nimmt an DDoS-Aktionen teil oder schürft Kryptowährungen. Nichts davon ist neu, nichts davon ist überraschend.

Was mir allerdings seit mindestens vier Jahren auffällt: Ein sehr großer Teil dieser Brute-Force-Versuche, insbesondere auf SSH, kommt bei mir aus Netzen von DigitalOcean. Nicht ein bisschen mehr, sondern konstant irgendwo im Bereich von achtzig bis neunzig Prozent. Über Jahre. Über verschiedene Systeme hinweg.

Der erste Reflex liegt nahe. Wenn so viel aus einem Netz kommt, warum blockt man dann nicht einfach alle Netze dieses Anbieters? Dann ist Ruhe. Und wenn das alle machen würden, müsste der Anbieter ja reagieren. Der Gedanke ist verständlich. Ich hatte ihn selbst. Er ist aber aus meiner Sicht der falsche.

Ein pauschales Blocken ist im Grunde nichts anderes als eine Decke über das eigentliche Problem zu werfen. Das Problem ist damit nicht weg, es ist nur woanders. Die Bots wechseln dann eben zum nächsten Cloud-Provider. Außerdem produziert man sich damit ganz eigene Probleme. DigitalOcean-Netze komplett zu sperren heißt auch, legitimen Traffic auszusperren. APIs, Dienste, Kunden, Monitoring, externe Abhängigkeiten. Je nach Setup schneidet man sich damit schneller ins eigene Fleisch, als einem lieb ist.

Relativ schnell landet man dann bei Reputation-Diensten wie AbuseIPDB. Dort melden Betreiber IPs, von denen Scans, Brute-Force-Versuche oder andere Auffälligkeiten ausgehen. Auch ich melde dort seit Jahren IPs, automatisiert und manuell. Formal funktioniert das gut. IPs bekommen Scores, werden gelistet, tauchen in Datenbanken auf.

Das Problem ist nur: Diese Systeme arbeiten IP-basiert. Und genau das passt schlecht zur Realität moderner Netze. In Cloud-Umgebungen sind IPs kurzlebig. Heute gehört sie einem kompromittierten Host, morgen einem völlig legitimen Kunden. Ein hoher Abuse-Score sagt wenig über den aktuellen Nutzer dieser IP aus. Reputation ist träge, Infrastruktur ist schnell.

Jan  6 22:58:08 honeypot03 sshd-session[61904]: Invalid user sonar from 64.23.228.101 port 38610
Jan  6 22:58:08 honeypot03 sshd-session[61904]: Connection closed by invalid user sonar 64.23.228.101 port 38610 [preauth]
Jan  6 23:02:13 honeypot03 sshd-session[62101]: Invalid user sonar from 64.23.228.101 port 38174
Jan  6 23:02:13 honeypot03 sshd-session[62101]: Connection closed by invalid user sonar 64.23.228.101 port 38174 [preauth]
Jan  6 23:06:12 honeypot03 sshd-session[62175]: Invalid user sonar from 64.23.228.101 port 35952
Jan  6 23:06:12 honeypot03 sshd-session[62175]: Connection closed by invalid user sonar 64.23.228.101 port 35952 [preauth]
Jan  6 23:10:10 honeypot03 sshd-session[62248]: Invalid user steam from 64.23.228.101 port 38236
Jan  6 23:10:10 honeypot03 sshd-session[62248]: Connection closed by invalid user steam 64.23.228.101 port 38236 [preauth]
Jan  6 23:14:17 honeypot03 sshd-session[62335]: Invalid user steam from 64.23.228.101 port 35952
Jan  6 23:14:18 honeypot03 sshd-session[62335]: Connection closed by invalid user steam 64.23.228.101 port 35952 [preauth]
Jan  6 23:18:22 honeypot03 sshd-session[62455]: Invalid user steam from 64.23.228.101 port 50096
Jan  6 23:18:22 honeypot03 sshd-session[62455]: Connection closed by invalid user steam 64.23.228.101 port 50096 [preauth]
Jan  6 23:22:24 honeypot03 sshd-session[62599]: Invalid user sugi from 64.23.228.101 port 53212
Jan  6 23:22:25 honeypot03 sshd-session[62599]: Connection closed by invalid user sugi 64.23.228.101 port 53212 [preauth]
Jan  6 23:26:26 honeypot03 sshd-session[62671]: Invalid user svnuser from 64.23.228.101 port 44820
Jan  6 23:26:26 honeypot03 sshd-session[62671]: Connection closed by invalid user svnuser 64.23.228.101 port 44820 [preauth]
Jan  6 23:30:26 honeypot03 sshd-session[62763]: Invalid user svnuser from 64.23.228.101 port 52156
Jan  6 23:30:27 honeypot03 sshd-session[62763]: Connection closed by invalid user svnuser 64.23.228.101 port 52156 [preauth]
Jan  6 23:34:30 honeypot03 sshd-session[62867]: Invalid user taryn from 64.23.228.101 port 54128
Jan  6 23:34:31 honeypot03 sshd-session[62867]: Connection closed by invalid user taryn 64.23.228.101 port 54128 [preauth]
Jan  6 23:38:41 honeypot03 sshd-session[62939]: Invalid user taryn from 64.23.228.101 port 39894
Jan  6 23:38:42 honeypot03 sshd-session[62939]: Connection closed by invalid user taryn 64.23.228.101 port 39894 [preauth]
Jan  6 23:42:44 honeypot03 sshd-session[63013]: Invalid user taryn from 64.23.228.101 port 57728
Jan  6 23:42:45 honeypot03 sshd-session[63013]: Connection closed by invalid user taryn 64.23.228.101 port 57728 [preauth]
Jan  6 23:46:45 honeypot03 sshd-session[63160]: Invalid user taryn from 64.23.228.101 port 38438
Jan  6 23:46:45 honeypot03 sshd-session[63160]: Connection closed by invalid user taryn 64.23.228.101 port 38438 [preauth]
Jan  6 23:50:49 honeypot03 sshd-session[63252]: Invalid user taryn from 64.23.228.101 port 54070
Jan  6 23:50:49 honeypot03 sshd-session[63252]: Connection closed by invalid user taryn 64.23.228.101 port 54070 [preauth]
Jan  6 23:54:55 honeypot03 sshd-session[63354]: Invalid user terrance from 64.23.228.101 port 57960
Jan  6 23:54:55 honeypot03 sshd-session[63354]: Connection closed by invalid user terrance 64.23.228.101 port 57960 [preauth]
Jan  6 23:59:05 honeypot03 sshd-session[63472]: Invalid user terrance from 64.23.228.101 port 47558
Jan  6 23:59:05 honeypot03 sshd-session[63472]: Connection closed by invalid user terrance 64.23.228.101 port 47558 [preauth]
Jan  7 00:03:11 honeypot03 sshd-session[64731]: Invalid user terrance from 64.23.228.101 port 42938
Jan  7 00:03:11 honeypot03 sshd-session[64731]: Connection closed by invalid user terrance 64.23.228.101 port 42938 [preauth]

Damit erklärt sich auch, warum Provider solche externen Feeds nicht einfach hart umsetzen. Würde man IPs automatisiert abschalten, nur weil sie in einer Datenbank schlecht bewertet sind, träfe man regelmäßig Unbeteiligte. False Positives wären vorprogrammiert. Rechtlich, operativ und wirtschaftlich ist das für Provider kaum tragbar.

Warum also fällt DigitalOcean so stark auf? Das kann ich nicht belegen, nur einordnen. DigitalOcean ist günstig, schnell, einfach. In wenigen Minuten hat man dort eine VM mit öffentlicher IP. Das ist für legitime Nutzer attraktiv, aber eben auch für Leute mit schlechten Absichten. Wenn Infrastruktur billig und niedrigschwellig ist, taucht sie zwangsläufig häufiger in Logs auf. Dazu kommt, dass viele Systeme dort von Menschen betrieben werden, die vielleicht noch nicht so tief im Thema Security stecken. Offene Dienste, schwache Konfigurationen, fehlendes Hardening – all das macht solche Hosts wiederum zu guten Kandidaten für Kompromittierung und Weiterverwendung.

Wichtig dabei: DigitalOcean selbst macht aus meiner Sicht nichts grundlegend falsch. Der Abuse-Prozess funktioniert. Meldungen lassen sich automatisiert einreichen, werden angenommen, werden beantwortet, werden bearbeitet. Ich habe das über Jahre hinweg genutzt, sowohl manuell als auch automatisiert. Das ist sauber umgesetzt.

Was sich dadurch aber nicht ändert, ist die Menge der Versuche. Die wird nicht weniger. Sie bleibt konstant. Einzelne Hosts verschwinden, neue tauchen auf. Abuse-Meldungen – egal ob direkt beim Provider oder über Plattformen wie AbuseIPDB – wirken immer nur lokal und zeitverzögert. Gegen ein strukturelles Phänomen kommen sie nicht an.

Aus Sicht eines Providers ist das auch logisch. Ein paar tausend fehlgeschlagene SSH-Logins sind kein Incident. Kein DDoS, kein Ausfall, kein messbarer Schaden. Das fällt unter Hintergrundrauschen. Niemand bezahlt dafür, dieses Rauschen global zu eliminieren. Und ehrlich gesagt: Das kann auch niemand realistisch leisten.

Die eigentliche Konsequenz daraus ist unbequem, aber klar. Man darf nicht erwarten, dass Provider oder Reputation-Datenbanken einem dieses Problem abnehmen. Scan- und Brute-Force-Traffic gehört heute zum Betrieb eines öffentlich erreichbaren Systems dazu. Die einzige Stelle, an der man sinnvoll ansetzen kann, ist das eigene Setup.

Saubere Konfiguration. Keine Passwort-Logins per SSH. Kein Root-Login. Rate-Limits. Monitoring, das zwischen Rauschen und echten Zustandsänderungen unterscheidet. Fail2Ban als Dämpfer, nicht als Illusion von Sicherheit. Und vor allem: Gelassenheit gegenüber Logs, die voll sind, aber nichts bedeuten.

DigitalOcean ist hier nicht der Feind. AbuseIPDB ist kein Allheilmittel. Beides sind sichtbare Teile eines größeren Bildes. Das eigentliche Thema ist, wie man Systeme so betreibt, dass dieses Hintergrundrauschen irrelevant wird.

Siehe auch: SSH-Server absichern mit MFA

BIND auf FreeBSD: DoT & DoH einrichten mit Views, IP‑Trennung und Testplan für IPv4/IPv6.

Wofür braucht man noch gleich DoT oder DoH?

Nun, wenn du eine Internetadresse eingibst, muss dein Gerät zuerst herausfinden, zu welchem Server diese Adresse gehört. Diese Nachfragen heißen DNS. Lange Zeit liefen sie unverschlüsselt durchs Netz, vergleichbar mit einer Postkarte. Jeder, der den Datenverkehr sehen konnte, wusste dadurch sehr genau, welche Webseiten aufgerufen werden, und konnte die Antworten sogar manipulieren.

Beitragsgrafik zu BIND 9.20 auf FreeBSD 15: schematische Trennung von autoritativem DNS und rekursivem Resolver. Links ein Authoritative-DNS-Server mit deaktivierter Rekursion und blockiertem UDP/53, rechts ein Resolver, der ausschließlich DNS over TLS (Port 853) und DNS over HTTPS (Port 443) anbietet. In der Mitte ein Schild mit DoT/DoH-Symbolen, Pfeile zeigen verschlüsselten DNS-Verkehr. Fokus auf Sicherheits- und Rollen-Trennung.

DoT und DoH lösen genau dieses Problem. Beide sorgen dafür, dass diese DNS-Nachfragen verschlüsselt übertragen werden. Bei DNS over TLS, kurz DoT, wird die Anfrage in eine eigene sichere Verbindung gepackt. Außenstehende sehen noch, dass eine DNS-Anfrage stattfindet, aber nicht mehr, welche Webseite gemeint ist. Bei DNS over HTTPS, kurz DoH, wird dieselbe Anfrage zusätzlich im normalen Webseitenverkehr versteckt. Von außen sieht sie aus wie ein ganz gewöhnlicher Zugriff auf eine Website.

Der Zweck von beiden ist also derselbe: Schutz der Privatsphäre und Schutz vor Manipulation. Der Unterschied liegt darin, wie sichtbar diese Nachfragen noch sind. DoT ist transparent und gut kontrollierbar, DoH ist unauffälliger, kann dafür aber lokale Regeln und Schutzmechanismen umgehen.

Mal angenommen, du möchtest eine gewisse Webseite aufrufen. Dann geht der Client los und holt über einen DNS-Server die IP-Adressen vom Server. Dies kann man mitlesen und ggf. verändern. Mitlesen sagt dem Mitlesenden, wo du dich so im Internet herumtreibst. Verändern könnte man als Angriff nutzen, indem man dir einfach eine andere Webseite vorsetzt, während du versuchst, dich in deinen Mailaccount einzuloggen. Beides wird durch DoH und DoT deutlich erschwert.

Dann soll es ja Netzwerke geben, in welchen dir ein bestimmter DNS-Server aufgezwungen wird, weil dieser DNS-Server nach Werbung oder ungewollten Inhalten filtert. Damit dies nun ebenfalls nicht einfach umgangen werden kann, blockt man den Zugriff aus dem Netzwerk einfach auf die Ports, welche sonst für eine DNS-Abfrage benutzt werden (TCP/53, UDP/53, TCP/853). Da kommt nun DoH ins Spiel, denn das läuft auf dem ganz normalen HTTPS-Port TCP/443. Blockt man den, kann keiner mehr auf Webseiten zugreifen (ok, unverschlüsselt, aber hey, das macht doch keiner mehr, oder?).

Die Zeit ging weiter – BIND auch.
Meine älteren Artikel zu DoT/DoH waren für ihren Zeitpunkt korrekt, aber inzwischen hat sich an zwei Stellen richtig was getan:

  1. BIND spricht DoT/DoH nativ (kein Stunnel-/Proxy-Zirkus mehr nötig – außer du willst bewusst terminieren/filtern).
  2. „Authoritative + Public Resolver auf derselben Kiste“ ist ohne klare Trennung schnell ein Sicherheitsproblem (Open-Resolver/Reflection-Missbrauch lässt grüßen).

Darum gibt’s hier das Update:

  • ns1.kernel-error.de: nur autoritativ auf UDP/TCP 53 (Zonen, DNSSEC wie gehabt)
  • dns.kernel-error.de: Public Resolver nur auf DoT 853/TCP und DoH 443/TCP (rekursiv, DNSSEC-validierend)
  • Trennung über zusätzliche IPs + Views. Ergebnis: Authoritative bleibt „stumm rekursiv“, Resolver ist nur über TLS/HTTPS erreichbar.

Zielbild

Uff, ich muss zugeben, diesen Beitrag schon VIEL zu lange als Draft zu haben. Es ist einfach viel zu schreiben, bschreiben und mir fehlte die Zeit. Aber das kennt ihr ja. OK… das Zielbild, was soll es werden?

Was soll am Ende gelten:

  • Port 53 auf Authoritative-IP(s):
    • beantwortet nur meine autoritativen Zonen
    • keine Rekursion → REFUSED bei google.com
  • DoT/DoH auf separaten Resolver-IP(s):
    • rekursiv für „das ganze Internet“
    • DNSSEC-Validation aktiv
    • kein offenes UDP/53 → weniger Angriffsfläche für Reflection/Amplification

Warum das wichtig ist:
Ein „Public Resolver“ ist per Definition attraktiv für Missbrauch. Der Klassiker ist DNS-Amplification über UDP/53. Wenn man Rekursion auf 53 offen hat, ist man sehr schnell Teil fremder Probleme. DoT/DoH sind TCP-basiert – das ist schon mal deutlich unattraktiver für Reflection. (Nicht „unmöglich“, aber praktisch viel weniger lohnend.)

Warum „Views“ – und warum zusätzliche IPs?

1) Views – weil Policy pro Anfrage gelten muss

Wir wollen auf derselben named-Instanz zwei sehr unterschiedliche Rollen:

  • Authoritative: recursion no;
  • Resolver: recursion yes; + Root-Hints/Cache

Das muss pro eingehender Anfrage entschieden werden. Dafür sind Views da.

2) Also: Trennung über Ziel-IP (match-destinations)

Wenn wir DoH/DoT auf andere IPs legen, kann die View anhand der Zieladresse entscheiden:

  • Anfrage geht an 93.177.67.26 / 2a03:4000:38:20e::53auth-View
  • Anfrage geht an 37.120.183.220 / 2a03:4000:38:20e::853resolver-View

Und genau deshalb brauchen wir:

  • zusätzliche IPs (damit die Rollen sauber getrennt sind)
  • separaten FQDN dns.kernel-error.de (damit Clients überhaupt sinnvoll DoT/DoH nutzen können – und für TLS/SNI/Cert-Match)

Wenn du also grade ein ripe from ausfüllst und angeben musst, warum da eine weitere IPv4 Adresse „verbrannt“ werden soll, hast du nun eine gute Antwort.

BIND-Config

Ich beschreibe hier nur die Teile, die für das Rollen-Split relevant sind. Die Zonendateien/Slaves bleiben wie sie sind.

1) /usr/local/etc/namedb/named.conf – Views

Wichtig: Sobald wir view {} nutzen, müssen alle Zonen in Views liegen, sonst bricht named-checkconf ab. Das ist kein „Feature“, das ist BIND. Leicht nervig, vor allem wenn man nun viel in seinem Setup umschreiben muss. Aber ich eigentlich schon mal erwähnt, dass ich auf der Arbeit mal einen, nennen wir es mal View Ersatz, für powerdns gesehen habe? Da hat tatsächlich jemand mit einer Cisco ASA in die DNS Pakete geschaut und je nachdem welche quelle angefragt hat, wurde dann durch die ASA eine neue Adresse in die DNS Pakete geschrieben. Furchtbar! Richtig schlimm. Bis man so etwas findet, wenn man es nicht weiß. DNSsec geht kaputt und aaahhhhhhaaaaaahhhhh. Egal, mein PTBS kickt da grade. Öhm wo waren wir? Genau…

Beispiel:

include "/usr/local/etc/namedb/named.conf.options";

view "auth" {
    match-clients { any; };
    match-destinations { 93.177.67.26; 2a03:4000:38:20e::53; };

    recursion no;
    allow-recursion { none; };
    allow-query-cache { none; };
    allow-query { any; };

    include "/usr/local/etc/namedb/named.conf.default-zones";
    include "/usr/local/etc/namedb/named.conf.master";
    include "/usr/local/etc/namedb/named.conf.slave";
};

view "resolver" {
    match-clients { any; };
    match-destinations { 37.120.183.220; 2a03:4000:38:20e::853; 127.0.0.1; ::1; };

    recursion yes;
    allow-recursion { any; };
    allow-query-cache { any; };
    allow-query { any; };

    zone "." { type hint; file "/usr/local/etc/namedb/named.root"; };
};

Warum Root-Hints nur im Resolver-View?
Weil nur dieser View rekursiv arbeiten soll. Ohne Root-Hints ist Rekursion tot; dat wolln wa so!

2) /usr/local/etc/namedb/named.conf.options – Listener-Trennung + DoH/DoT

Der „Aha-Moment“ hier: Wir trennen nicht nur per View, sondern auch per listen-on.
Damit bindet named die Ports wirklich nur auf den gewünschten IPs.

Authoritative (nur 53):

listen-on { 93.177.67.26; 127.0.0.1; };
listen-on-v6 { 2a03:4000:38:20e::53; ::1; };

DoT auf Resolver-IPs (+ Loopback für lokale Tests):

listen-on port 853 tls local-tls { 37.120.183.220; 127.0.0.1; };
listen-on-v6 port 853 tls local-tls { 2a03:4000:38:20e::853; ::1; };

DoH auf Resolver-IPs (+ Loopback):
BIND 9.18+ kann DoH nativ, Endpoint typischerweise /dns-query

http doh-local {
    endpoints { "/dns-query"; };
    listener-clients 1000;
    streams-per-connection 256;
};

listen-on port 443 tls local-tls http doh-local { 37.120.183.220; 127.0.0.1; };
listen-on-v6 port 443 tls local-tls http doh-local { 2a03:4000:38:20e::853; ::1; };

TLS-Block (DoT/DoH):

tls local-tls {
    cert-file "/usr/local/etc/nginx/ssl/wild.kernel-error.de/2025/ecp/chain.crt";
    key-file "/usr/local/etc/nginx/ssl/wild.kernel-error.de/2025/ecp/http.key";
    protocols { TLSv1.2; TLSv1.3; };
    ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256";
    cipher-suites "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256";
    prefer-server-ciphers yes;
    session-tickets no;
};

„Ich schalte nginx davor – muss BIND TLS können?“
Wenn nginx wirklich TLS terminiert, kann BIND auch ohne TLS dahinter laufen – dann sprichst du intern HTTP/2 cleartext oder HTTP/1.1, je nach Setup. Das habe ich ebenfalls so umgesetzt, es hängt immer etwas davon ab, was man so will und wie groß das Setup wird. Ich lasse es in diesem Beitrag aber mal weg, so läuft alles nur mit bind. Ob BIND dafür „tls none“/HTTP-Listener sauber unterstützt, hängt an der BIND-DoH-Implementierung – hier ist die BIND/ARM-Doku die Wahrheit. bind9.readthedocs.io+1

Testplan – Linux-CLI – bewusst IPv4 und IPv6

Wir wollen natürlich einmal reproduzierbar testen. Also: jede Stufe zweimal. Einmal -4, einmal -6. Also ob es bei IPv4 und bei IPv6 jeweils korrekt ist. Ihr könnt euch nicht vorstellen, wie oft ich fest davon überzeugt bin, es für beide Adressfamilien korrekt konfiguriert zu haben, dann aber noch ein unterschied zwischen v4 und v6 ist. Daher testen wir das.

Voraussetzungen auf Linux

which dig kdig curl openssl

Schritt 1 – DoT-TLS-Handshake prüfen (IPv4/IPv6)

IPv4

openssl s_client \
  -connect 37.120.183.220:853 \
  -servername dns.kernel-error.de \
  -alpn dot

Erwartung:

  • Zertifikat passt auf dns.kernel-error.de (SAN / Wildcard ok)
  • ALPN protocol: dot
  • Verify return code: 0 (ok)

IPv6

openssl s_client \
  -connect '[2a03:4000:38:20e::853]:853' \
  -servername dns.kernel-error.de \
  -alpn dot

Wenn das passt, ist TLS-Transport ok. Also nur die TLS Terminierung für IPv4 und IPv6, da war noch keine DNS Abfrage enthalten.

Schritt 2 – DoT-Query (kdig) – IPv4/IPv6

IPv4

kdig +tls @37.120.183.220 google.com A

Erwartung:

  • status: NOERROR
  • Flags: rd ra (Recursion Desired/Available)
  • eine A-Antwort

IPv6

kdig +tls @[2a03:4000:38:20e::853] google.com A

Gleiche Erwartungshaltung wie bei IPv4.

Schritt 3 – Sicherstellen: kein Resolver auf UDP/TCP 53

Resolver-IPs dürfen auf 53 nicht antworten

dig -4 @37.120.183.220 google.com A
dig -6 @2a03:4000:38:20e::853 google.com A

Erwartung:

  • Timeout / no servers reached
    Genau das wollen wir ja: kein UDP/53 auf den Resolver-IPs.

Authoritative-IPs dürfen nicht rekursiv sein

dig -4 @93.177.67.26 google.com A
dig -6 @2a03:4000:38:20e::53 google.com A

Erwartung:

  • status: REFUSED
  • idealerweise EDE: (recursion disabled)
    Das ist genau die „nicht missbrauchbar als Open-Resolver“-Bremse.

Und unser positiver Check:

dig -4 @93.177.67.26 kernel-error.de A
dig -6 @2a03:4000:38:20e::53 kernel-error.de A

Erwartung:

  • aa gesetzt (authoritative answer)
  • Antwort aus meiner Zone

Schritt 4 – DoH GET (Base64url) – IPv4/IPv6

4.1 Query bauen (DNS-Wireformat → base64url)

Beispiel google.com A:

echo -n -e '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01' \
| base64 -w0 | tr '+/' '-_' | tr -d '='

Das Ergebnis ist mein dns= Parameter (base64url ohne = padding). Das ist DoH-Standard nach RFC 8484.

4.2 DoH GET erzwingen – IPv4

curl -4 --http2 -s \
'https://dns.kernel-error.de/dns-query?dns=<DEIN_DNS_PARAM>' \
| hexdump -C

IPv6

curl -6 --http2 -s \
'https://dns.kernel-error.de/dns-query?dns=<DEIN_DNS_PARAM>' \
| hexdump -C

Erwartung:

  • HTTP/2 200
  • content-type: application/dns-message
  • Im Hexdump siehst du eine valide DNS-Response.

Schritt 5 – DoH POST (application/dns-message) – IPv4/IPv6

Das ist der „richtige“ DoH-Weg für Tools/Clients.

IPv4

printf '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01' \
| curl -4 --http2 -s \
  -H 'content-type: application/dns-message' \
  --data-binary @- \
  https://dns.kernel-error.de/dns-query \
| hexdump -C

IPv6

printf '\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01' \
| curl -6 --http2 -s \
  -H 'content-type: application/dns-message' \
  --data-binary @- \
  https://dns.kernel-error.de/dns-query \
| hexdump -C

Erwartung:

  • DNS-Response im Wireformat
  • keine HTML-Antwort, kein Redirect-Quatsch

Was wir damit jetzt sicher(er) gelöst haben:

  • Kein Open-Resolver auf UDP/53 → massiver Gewinn gegen DNS-Amplification.
  • Authoritative bleibt Authoritative → Zonen-Betrieb unverändert stabil.
  • Resolver nur über DoT/DoH → TCP/TLS-Transport, weniger Missbrauchsfläche.
  • Saubere technische Trennung → Views per Ziel-IP sind simpel, robust, nachvollziehbar.

Und ja: „Public Resolver“ heißt trotzdem Monitoring/Rate-Limiting/Abuse-Handling.
Das Feintuning (RRL, QPS-Limits, minimal-responses, Response-Policy, ggf. ECS-Handling, Logging, Fail2ban-Signale) ist das nächste Kapitel. Wobei, wenn ich grade auf die TLS Parameter schaue, sollte ich da vielleicht noch mal nacharbeiten, hm?

Wenn ihr noch eine kleine liste von erreichbaren Servern sucht: GitHub-curl-wiki

Alles hilft natürlich nicht, wenn man am Ende doch komplett IP- oder Hostnamebasiert geblockt wird. In China ist da nicht viel zu holen und auch hier gibt es immer mal wieder etwas.


Japp… TLS geht besser. Im Beitrag habe ich es oben schon angepasst, es war:

tls local-tls {
    cert-file "/pfad/chain.crt";
    key-file  "/pfad/http.key";
    dhparam-file "/pfad/dhparam.pem";
    protocols { TLSv1.2; TLSv1.3; };
    ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256";
    prefer-server-ciphers yes;
    session-tickets no;
};
  • dhparam-file ist komplett raus weil, ja weil es nicht benutzt wird ich mach ja kein DHE sondern ECDHE
  • cipher-suites für TLS1.3 waren nicht gesetzt.
  • Dann konnten auch gleich die Cipher aufgeräumt werden.

Hey, da hat es sich doch gelohnt, das mal runter zu schreiben. So habe ich es direkt gefunden und nicht erst, weil mich jemand von euch darauf hinweist (macht das aber bitte immer wenn ich hier Mist schreibe) oder es beim nächsten eigenen Audit auffällt.

Siehe auch: HTTPS RR und SVCB Records — die passenden DNS-Records, damit Clients dieses DoH/DoT-Setup automatisch entdecken können (RFC 9461).

Quantensichere Kryptografie mit OpenSSH auf FreeBSD 15 richtig konfigurieren

Mein FreeBSD 15 kommt mit OpenSSH 10.0p2 und OpenSSL 3.5.4.
Beide bringen inzwischen das mit, was man aktuell als quantensichere Kryptografie bezeichnet. Oder genauer gesagt das, was wir Stand heute für ausreichend robust gegen zukünftige Quantenangriffe halten.

Illustration zu quantensicherer Kryptografie mit OpenSSH auf FreeBSD 15. Dargestellt sind ein Quantenchip, kryptografische Symbole, ein Server, ein SSH Schlüssel sowie der FreeBSD Daemon als Sinnbild für post-quantum Key Exchange und sichere Serverkommunikation.

Quantensicher? Nein, das hat nichts mit Füßen zu tun, sondern tatsächlich mit den Quanten aus der Physik. Quantencomputer sind eine grundlegend andere Art von Rechnern. Googles aktueller Quantenchip war in diesem Jahr bei bestimmten Physiksimulationen rund 13.000-mal schneller als der derzeit leistungsstärkste klassische Supercomputer. Der chinesische Quantencomputer Jiuzhang wurde bei speziellen Aufgaben sogar als 100 Billionen Mal schneller eingestuft.

Kurz gesagt: Quantencomputer sind bei bestimmten Berechnungen extrem viel schneller als heutige klassische Rechner. Und genau das ist für Kryptografie ein Problem.

Als Vergleich aus der klassischen Welt: Moderne Grafikkarten haben die Zeit zum Knacken von Passwörtern in den letzten Jahren drastisch verkürzt.

  • Nur Zahlen: Ein 12-stelliges Passwort wird praktisch sofort geknackt.
  • Nur Kleinbuchstaben: wenige Wochen bis Monate.
  • Groß- und Kleinschreibung plus Zahlen: etwa 100 bis 300 Jahre.
  • Zusätzlich Sonderzeichen: 2025 noch als sehr sicher einzustufen mit geschätzten 226 bis 3.000 Jahren.

Quantencomputer nutzen spezielle Algorithmen wie den Grover-Algorithmus, der die effektive Sicherheit symmetrischer Verfahren halbiert. Ein ausreichend leistungsfähiger Quantencomputer könnte damit die benötigte Zeit drastisch reduzieren. Was heute Jahrhunderte dauert, könnte theoretisch auf Tage oder Stunden schrumpfen.

Stand 2025 sind solche Systeme zwar real und in der Forschung extrem leistungsfähig, werden aber noch nicht flächendeckend zum Brechen realer Kryptosysteme eingesetzt.

Heißt das also alles entspannt bleiben? Jein.

Verschlüsselte Datenträger lassen sich kopieren und für später weglegen. Gleiches gilt für aufgezeichneten verschlüsselten Netzwerkverkehr. Heute kommt man nicht an die Daten heran, aber es ist absehbar, dass das in Zukunft möglich sein könnte. Genau hier setzt quantensichere Kryptografie an. Ziel ist es, auch aufgezeichnete Daten dauerhaft vertraulich zu halten.

Ein praktisches Beispiel ist der Schlüsselaustausch mlkem768x25519. Wenn ihr diese Seite nicht gerade über Tor lest, ist die Wahrscheinlichkeit hoch, dass euer Browser bereits eine solche hybride, post-quantum-fähige Verbindung nutzt. Im Firefox lässt sich das einfach prüfen über F12, Network, eine Verbindung anklicken, dann Security und dort die Key Exchange Group. Taucht dort mlkem768x25519 auf, ist die Verbindung entsprechend abgesichert. Richtig, auf dem Screenhot seht ihr auch HTTP/3.

Image of mlkem768+x25519 in firefox.

Für diese Webseite ist das nicht zwingend nötig. Für SSH-Verbindungen zu Servern aber unter Umständen schon eher. Deshalb zeige ich hier, wie man einen OpenSSH-Server entsprechend konfiguriert.

Ich beziehe mich dabei bewusst nur auf die Kryptografie. Ein echtes SSH-Hardening umfasst deutlich mehr, darum geht es hier aber nicht.

Die zentrale Konfigurationsdatei ist wie üblich: /etc/ssh/sshd_config

Stand Ende 2025 kann ich folgende Konfiguration empfehlen:

KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com

Die Zeilen werden entweder an die bestehende Konfiguration angehängt oder ersetzen vorhandene Einträge. Da wir nicht einfach blind kopieren wollen, hier kurz die Erklärung.

Schlüsselaustausch:
Bevorzugt werden hybride Verfahren wie mlkem768 kombiniert mit x25519 sowie sntrup761 kombiniert mit x25519. Diese verbinden klassische elliptische Kryptografie mit post-quantum-resistenten Algorithmen. Damit ist die Verbindung sowohl gegen heutige Angreifer als auch gegen zukünftige Store-now-decrypt-later-Szenarien abgesichert. Curve25519 dient als bewährter Fallback. Klassische Diffie-Hellman-Gruppen sind nur aus Kompatibilitätsgründen enthalten.

Verschlüsselung:
Es werden ausschließlich moderne Algorithmen eingesetzt. Primär kommen AEAD-Ciphers wie ChaCha20-Poly1305 und AES-GCM zum Einsatz, die Vertraulichkeit und Integrität gleichzeitig liefern und bekannte Schwächen älterer Modi vermeiden. Ältere Verfahren wie CBC sind bewusst ausgeschlossen.

Integrität:
Zum Einsatz kommen ausschließlich SHA-2-basierte MACs im Encrypt-then-MAC-Modus. Dadurch werden klassische Angriffe auf SSH wie Padding-Oracles und bestimmte Timing-Leaks wirksam verhindert.

Serveridentität:
Als Hostkey-Algorithmus wird Ed25519 verwendet. Optional auch mit Zertifikaten oder hardwaregestützten Security Keys. Das bietet hohe kryptografische Sicherheit bei überschaubarem Verwaltungsaufwand.

Wichtig: Das funktioniert nur, wenn Server und Client diese Algorithmen auch unterstützen. Wer bereits mit SSH-Keys arbeitet, sollte prüfen, dass es sich um Ed25519-Keys handelt. Andernfalls sperrt man sich im Zweifel selbst aus.

Auf dem Server lässt sich die aktive Konfiguration prüfen mit:

sshd -T | grep -Ei 'kexalgorithms|ciphers|macs|hostkeyalgorithms'

Auf dem Client geht es am einfachsten mit:

ssh -Q kex
ssh -Q cipher
ssh -Q mac
ssh -Q key

So sieht man schnell, welche Algorithmen tatsächlich verfügbar sind.

Zur externen Überprüfung der SSH-Konfiguration kann ich außerdem das Tool ssh-audit empfehlen. Aufruf einfach per:

ssh-audit hostname oder IP -p PORT

Das liefert eine brauchbare Einschätzung der aktiven Kryptografie und möglicher Schwachstellen. Oh, wenn ihr schon dabei seit, vergesst nicht:

Hinweis zur Einordnung der Quantensicherheit:
Die hier gezeigte Konfiguration verbessert ausschließlich den Schlüsselaustausch (Key Exchange) durch hybride post-quantum-fähige Verfahren. Hostkeys und Signaturen in OpenSSH basieren weiterhin auf klassischen Algorithmen (z. B. Ed25519 oder ECDSA); standardisierte post-quantum-Signaturalgorithmen sind in OpenSSH aktuell noch nicht implementiert. Es existieren zwar experimentelle Forks (z. B. aus dem Open-Quantum-Safe-Projekt), diese gelten jedoch ausdrücklich nicht als produktionsreif und sind nicht Bestandteil des OpenSSH-Mainlines. Die hier gezeigte Konfiguration ist daher als pragmatischer Übergangsschritt zu verstehen, um „store-now-decrypt-later“-Risiken beim Schlüsselaustausch bereits heute zu reduzieren, ohne auf instabile oder nicht standardisierte Komponenten zu setzen.
Weiterführende Informationen zum aktuellen Stand der post-quantum-Unterstützung in OpenSSH finden sich in der offiziellen Dokumentation: https://www.openssh.com/pq.html

Siehe auch: Post-Quantum TLS für E-Mail — Postfix und Dovecot mit X25519MLKEM768 auf FreeBSD 15, Post-Quantum TLS für Nginx — X25519MLKEM768 auf FreeBSD 15 konfigurieren

Viel Spaß beim Nachbauen. Und wie immer: bei Fragen, fragen.

Ist mein Netzwerk kompromittiert? Warum das kaum jemand merkt

Ich habe ja bereits etwas zum Thema IoT-Geräte geschrieben und warum diese oft deutlich schneller gehijackt werden, als man vielleicht erwartet.

Aber woher weiß man nun als normaler Anwender, ob zu Hause oder im eigenen Netzwerk etwas sein Unwesen treibt?
Nun ja; das ist leider überhaupt nicht so einfach.

Symbolische Darstellung eines kompromittierten Netzwerks mit Warnhinweisen, IoT-Kamera und verdächtigem Datenverkehr.

Klar, man kann sich ganz tolle IPS oder IDS aufbauen. Es gibt dafür auch Open-Source-Systeme; Snort fällt mir da als einer der älteren Vertreter als erstes ein.

Aber das alles ist nichts für den normalen Anwender oder den Privathaushalt. Dann gibt es noch ganz furchtbar viele Schlangenölanbieter mit ihrer „Sicherheitssoftware“ für Windows, Android und Co. Klar, man kann dort Firewall, Virenscanner usw. installieren. Aber hilft das wirklich? Jein, würde ich dazu sagen.

Ist man auf einem aktuellen Patchstand, sollten zumindest die bekannten Löcher geschlossen sein. Dann bleiben fast nur noch Zero-Day-Lücken Ein Virenfilter kennt diese in der Regel auch nicht und lässt so etwas dann schlicht durch.

Eine Firewall-Lösung kann zumindest erkennen, ob plötzlich ungewöhnlicher Traffic unterwegs ist oder ob versehentlich gestartete Dienste nach außen offen stehen. Nur steht und fällt das Ganze oft genau in dem Moment, in dem der Anwender nach einer Entscheidung gefragt wird.

Sicherheitssoftware muss naturgemäß sehr tief im Betriebssystem eingebettet werden. Hat diese Sicherheitssoftware dann selbst Sicherheitslücken, was deutlich häufiger vorkommt, als man zunächst glauben möchte, öffnet man im Zweifel die eigene Infrastruktur über genau die Software, die das eigentlich verhindern soll. Vertraut mir da bitte einfach, wenn ich sage, dass ich das schon sehr oft gesehen habe. Zudem installiert sich so eine Sicherheitssoftware oft nicht einfach auf einer Netzwerkkamera.

Der beste Schutz sind, meiner Meinung nach, noch immer gepflegte Systeme, gute Zugangsdaten und das nötige Misstrauen. Wie kam ich jetzt darauf? Ach richtig; wie findet man eigentlich heraus, ob es überhaupt ein Problem gibt?

Klar, man kann abwarten. Irgendwann merkt man es sicher; spätestens dann, wenn die Polizei mit einer Hausdurchsuchung vor der Tür steht und wissen möchte, was man denn da so alles im Internet verteilt oder angreift.

Eine wirklich gute Lösung habe ich da leider auch nicht. Am ehesten noch Dienste wie GreyNoise (https://check.labs.greynoise.io/). Dort kann man beispielsweise gegen AbuseDB prüfen, ob die eigene IPv4-Adresse irgendwo im Internet „auffällig“ geworden ist; etwa durch Portscans, Spam-Versand oder Malware-Traffic. Ebenfalls kann man hin und wieder bei Have I Been Pwned (https://haveibeenpwned.com/) vorbei schauen, um zu prüfen, ob die eignen Zugangsdaten irgendwo gefunden wurden.

Im Allgemeinen ist aber auch das nur ein Indiz. IP-Adressen wechseln; vor allem bei privaten Anschlüssen. Die eigene IP muss erst auffallen, gemeldet werden und so weiter.

Aber hey; vielleicht hat ja noch jemand einen besseren Tipp?

Fragen? Einfach melden.

« Ältere Beiträge

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑