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

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.

9 Kommentare

  1. Fmau

    Guter Beitrag. Danke dafür.

    Was mich ehrlich gesagt wahnsinnig macht: Das sind zwei Zeilen Config. Zwei. Und trotzdem werden 90% der Mailserver da draußen in einem Jahr immer noch ohne PQC laufen, weil „haben wir noch nicht evaluiert“ oder „steht nicht im Migrationsplan“ oder mein persönlicher Favorit: „Quantencomputer gibt es ja noch gar nicht.“

    Doch, Leute. Die Daten, die heute über eure Mailserver laufen, werden gespeichert. Von Akteuren, die sehr geduldig sind. Und sehr gut finanziert. Wer glaubt, dass das nur die anderen betrifft, hat das Problem nicht verstanden.

    Hybrid ist hier genau der richtige Ansatz – man verliert nichts, man gewinnt Schutz. Und wer mit dem Argument kommt, dass die Authentifizierung ja noch klassisch ist: Stimmt. Aber das eine nicht zu tun, weil das andere noch nicht fertig ist, war noch nie eine gute Strategie.

    Also: Weniger diskutieren, mehr main.cf editieren.

  2. Tobias

    Wow, zwei Zeilen Config — damit hatte ich nicht gerechnet. Hab das gestern abend auf meinem Mailserver eingerichtet (Debian 13 mit OpenSSL 3.4). Funktioniert einwandfrei, testssl.sh zeigt X25519MLKEM768 an. Danke dafür!

  3. Stefan M.

    Mich würde interessieren ob du das auch für die Submission-Ports (587/465) konfiguriert hast, oder nur für Port 25? Weil die meisten MUAs werden das ja noch garnicht unterstützen oder?

  4. Nils

    Endlich schreibt mal jemand darüber wie einfach das eigentlich ist. Gefühlt denken die meisten Admins PQC wäre noch Jahre entfernt, dabei ist das halt schon da und braucht quasi null Aufwand. Der Punkt mit „store now decrypt later“ ist auch gut — gerade bei Mails wo die Inhalte auch in 10 Jahren noch sensibel sein können.

  5. Martin Holz

    Hab den SSH-Beitrag von dir schon umgesetzt, jetzt auch die Mailserver. Planst du noch was zu Nginx/Webserver? Das wäre dann sozusagen die Trilogie komplett 😉

  6. aw

    läuft, danke. freebsd 14.2 mit openssl aus den ports.

  7. Andy

    Hallo Sebastian,

    wie ist deine Erfahrung mit dem geschilderten Verhalten im Beitrag https://www.mail-archive.com/postfix-users@postfix.org/msg106509.html

    Sollte der Handshake mit X25519MLKEM768 nicht klappen, gilt wohl der komplette TLS-Aufbau als gescheitert und er fällt auf Plaintext zurück. Das ist natürlich fatal und wohl auch der Grund, warum die Postfix Doku (https://www.postfix.org/postconf.5.html) davon abrät:

    Post-quantum cryptography support: OpenSSL 3.5 introduces new configuration syntax that Postfix will not attempt to emulate. That said, when compiled against OpenSSL 3.5 or later, the Postfix default setting is a minor adjustment of the OpenSSL compiled-in default setting, it just delays generation of the hybrid post-quantum X25519MLKEM768 key-share until it is explicitly requested by the server. This avoids interoperability issues with some SMTP servers that are unable to handle the resulting large TLS Client Hello.

    Das macht mich natürlich stuzig, denn ich möchte auch keine Split-Lösung wie im Folgebeitrag auf der Mailingliste vorgschlagen.

    Idealerweise versucht es Postfix als Client mit X25519MLKEM768 und fällt dann auf X25519 zurück, sollte das nicht möglich sein. Mir ist aber momentan keine derartige Konfigurationsmöglichkeit bekannt. Euch?

    • Sebastian van de Meer

      Moin,

      danke für den Hinweis, das ist ein valider Punkt den ich im Beitrag nicht beleuchtet hatte. Ich habe mir das daraufhin im Detail angeschaut und die Konfiguration angepasst.

      Das Problem: tls_eecdh_auto_curves gilt für SMTP-Client und SMTP-Server gleichermaßen. Steht X25519MLKEM768 an erster Stelle, packt Postfix den PQC Key-Share direkt in den initialen ClientHello. Der wächst dadurch von ~400 auf über 1400 Bytes. Manche Zielserver kommen damit nicht klar, der TLS-Handshake scheitert. Bei smtp_tls_security_level = may fällt Postfix dann auf Plaintext zurück. Bei dane greift der Schutz nur wenn die Zieldomain TLSA-Records hat — ohne TLSA (also bei der Mehrheit aller Domains, inkl. Gmail, Outlook, Yahoo) fällt auch dane auf opportunistisches TLS zurück, gleiches Risiko.

      Das sind aber zwei unterschiedliche Policies: Inbound will man PQC bevorzugen, outbound will man Kompatibilität priorisieren. Die gehören nicht in einen globalen Parameter.

      Die Lösung auf Postfix 3.10.6 ist eine Trennung per master.cf. Kein openssl.cnf-Split nötig. In master.cf gibt es sowohl die smtpd-Listener (inet) als auch den smtp-Client-Transport (unix) — auf beide lassen sich getrennte Overrides setzen:

      Server-Seite (inbound) — PQC bevorzugen:

      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 HRR:

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

      In main.cf wird tls_eecdh_auto_curves nicht mehr global gesetzt.

      Damit bleibt der initiale ClientHello outbound klein (~400 Bytes). X25519MLKEM768 steht in den supported_groups, wird aber nur verhandelt wenn der Zielserver per HelloRetryRequest nachzieht. Inbound bekommen moderne Clients sofort PQC.

      Dovecot ist davon nicht betroffen — da gibt es nur die Server-Seite, ssl_curve_list bleibt wie gehabt.

      Zur postconf(5)-Doku und Postfix 3.11: Die Doku beschreibt für neuere Builds ein Default-Verhalten mit Delayed Key-Share. Auf meinem 3.10.6-Build zeigt postconf -d aber eindeutig kein X25519MLKEM768 im Default. Was die Doku beschreibt und was ein konkreter Build tut, sind zwei Paar Schuhe. Deshalb trenne ich Client und Server explizit statt mich auf einen künftigen Default zu verlassen.

      Ich habe den Beitrag mit einem entsprechenden Nachtrag ergänzt. Danke für den Anstoß!

  8. FS

    Sehr guter und interessanter Beitrag.

    Ich betreibe einen Mailserver mit Arch Linux. Ich habe bezüglich tls_eecdh_auto_curves nichts explizit eingestellt. Es scheint wohl auch so per Default verwendet zu werden. Jedenfalls sehe ich im Log:

    Wed 2026-04-08 08:58:41 CEST postfix/smtpd[10239]: Anonymous TLS connection established from unknown[2604:a940:301:225:0:13::]: TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519MLKEM768 server-signature RSA-PSS (4096 bits) server-digest SHA256

    Wed 2026-04-08 18:52:47 CEST postfix/smtpd[22816]: Anonymous TLS connection established from dnssec-stats.ant.isi.edu[128.9.29.254]: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519MLKEM768 server-signature RSA-PSS (4096 bits) server-digest SHA256

    Wed 2026-04-08 18:53:01 CEST postfix/smtpd[22816]: Anonymous TLS connection established from dnssec-stats.ant.isi.edu[2001:1878:401::8009:1dfe]: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519MLKEM768 server-signature ECDSA (secp384r1) server-digest SHA384

    Der Postfix Default ist: tls_eecdh_auto_curves = ?X25519MLKEM768:DEFAULT

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑