Skip to content
Zurück zum Blog
Sicherheit

Wie TOTP funktioniert: der Algorithmus hinter 2FA-Codes

Wie TOTP funktioniert, für Entwickler erklärt: der RFC-6238-Algorithmus Schritt für Schritt, die serverseitige Prüfung und was 2FA wirklich schützt.

12 Min. Lesezeit

Wie TOTP funktioniert: der Algorithmus hinter Ihren Authenticator-Codes

Ein paarmal pro Woche tippen Sie einen 6-stelligen Code aus einer Authenticator-App ab, und irgendwie erscheint derselbe Code auf Ihrem Telefon und passt zu dem, was der Server erwartet, obwohl die beiden nie miteinander reden. Wie geht das? Dasselbe gemeinsame Geheimnis erzeugt alle 30 Sekunden eine neue Zahl, und dahinter steckt nur ein kleiner, deterministischer Algorithmus, den beide Seiten getrennt ausführen. Über das Netzwerk wandert dabei kein Code, und es gibt keinen zentralen Server, der die Zahl verteilt.

TOTP (Time-based One-Time Password), definiert in RFC 6238, macht aus einem gemeinsamen Geheimnis und der aktuellen Zeit einen kurzen numerischen Code: Es berechnet einen HMAC über die Zeit und kürzt das Ergebnis. Die Zwei-Faktor-Authentifizierung (2FA) beruht darauf, dass beide Seiten denselben Wert berechnen, ohne ihn auszutauschen, und damit hängt am Algorithmus das ganze Vertrauen.

Dieser Leitfaden geht den Algorithmus mit konkreten Zahlen durch und behandelt danach die Hälfte, die in den meisten Erklärungen fehlt: wie ein Server einen Code tatsächlich prüft, und was 2FA verhindert und was nicht. Während Sie mitlesen, können Sie in unserem TOTP-Generator einen aktuellen Code berechnen.

Was ist TOTP eigentlich?

TOTP (Time-based One-Time Password), definiert in RFC 6238, ist ein Algorithmus, der ein gemeinsames Geheimnis mit der aktuellen Zeit kombiniert und daraus einen kurzen Code erzeugt, der in einem festen Intervall wechselt. Authenticator-App und Server halten beide dasselbe Geheimnis, lesen dieselbe Uhr und rechnen dieselbe Mathematik, und so kommen sie auf denselben Code, ohne ihn jemals zu übertragen.

Diesen letzten Punkt sollte man sich merken. Der Code selbst wird beim Einrichten nie gesendet, nur das Geheimnis, und danach leitet jede Seite die Codes selbst ab. Auf der Leitung lässt sich nichts abfangen außer dem Geheimnis bei der Registrierung und den 6 Ziffern, die der Nutzer beim Anmelden eingibt. Im Kopf läuft das auf drei Eingaben hinaus, die zu einer Ausgabe zusammenfallen:

EingabeRolleTypischer Wert
Gemeinsames GeheimnisDer langlebige Schlüssel, einmal bei der Registrierung vereinbartJBSWY3DPEHPK3PXP (Base32)
ZeitschrittDer Zähler, der vorwärtstickt30-Sekunden-Fenster
AusgabeDer aus den beiden abgeleitete kurze Code324550

Geschrieben wird das Geheimnis fast immer in Base32 (die Buchstaben A–Z und die Ziffern 2–7), denn dieses Alphabet unterscheidet nicht zwischen Groß- und Kleinschreibung und übersteht das Drucken, Abtippen oder Verpacken in einen QR-Code unbeschadet. Ein Geheimnis registrieren Sie, indem Sie eine otpauth://-URI scannen, die sich als Authenticator-QR-Code rendern lässt, oder indem Sie die Base32-Zeichenkette von Hand eintippen.

TOTP vs. HOTP vs. SMS vs. Passkeys: das 2FA-Umfeld

TOTP ist eine von mehreren Optionen, und eine gute Wahl trifft nur, wer das ganze Feld kennt. Den einen Satz, der die Beziehung erklärt, sollte man im Kopf haben: TOTP ist HOTP, bei dem der Zähler durch die Anzahl der Zeitschritte seit der Unix-Epoche ersetzt ist. Alles Weitere ist eine Abwägung zwischen Phishing-Resistenz, Bequemlichkeit und der nötigen Infrastruktur.

MechanismusTreiberCode-LebensdauerPhishing-resistent?Netzwerk nötig?Typischer Einsatz
HOTP (RFC 4226)Hochzählender ZählerBis zur NutzungNeinNeinHardware-Token, Altsysteme
TOTP (RFC 6238)Aktuelle Zeit~30 SekundenNeinNein (nach Registrierung)Authenticator-Apps
SMS-OTPServer sendet einen CodeEin paar MinutenNeinJa (Mobilfunk)Verbraucher-Rückfalloption
Push-BestätigungServer-Anfrage an ein GerätPro AnfrageTeilweiseJaApp-basierte 2FA
Passkey / FIDO2Public-Key-ChallengePro AnfrageJa (an Origin gebunden)JaModerne Konten

Das Muster der Tabelle ist schnell erklärt. TOTP und HOTP laufen nach der Registrierung offline, was sie robust und datenschutzfreundlich macht, aber für sich genommen ist keiner von beiden phishing-resistent: Eine überzeugende Fälschungsseite kann nach dem Code fragen und ihn weiterreichen. SMS bringt einen Netzwerkkanal mit, und damit eine eigene Angriffsfläche. Passkeys schließen die Phishing-Lücke, weil sie die Anmeldedaten an die Origin der Website binden, und deshalb steuert die Branche darauf zu. TOTP ist stark, überall verfügbar und kostenlos, und genau deshalb bleibt es so verbreitet.

Wie der TOTP-Algorithmus funktioniert, Schritt für Schritt

Der ganze Algorithmus besteht aus vier Schritten. Wir rechnen jeden mit dem RFC-Testgeheimnis JBSWY3DPEHPK3PXP und einer festen Unix-Zeit von 1700000000 durch, damit jede Zahl reproduzierbar bleibt.

  1. Das Base32-Geheimnis dekodieren zu rohen Schlüssel-Bytes.
  2. Den Zeitschritt-Zähler berechnen aus der aktuellen Unix-Zeit.
  3. Den Zähler per HMAC verarbeiten mit dem geheimen Schlüssel.
  4. Kürzen des Digests auf einen 6-stelligen Code.

Schritt 1 — Das Base32-Geheimnis zu Bytes dekodieren

Base32 packt 5 Bit in jedes Zeichen. Der Dekodierer gruppiert die Zeichen wieder zu 8-Bit-Bytes. Das Geheimnis JBSWY3DPEHPK3PXP dekodiert zu den 10 rohen Bytes 48 65 6c 6c 6f 21 de ad be ef. Als HMAC-Schlüssel dient dieses Byte-Array, nicht die druckbare Zeichenkette.

Schritt 2 — Den Zeitschritt-Zähler berechnen

Der Zähler ist die Anzahl der vollen Zeitschritte seit einem Startpunkt: T = floor((unixTime − T0) / period). Die RFC-Vorgaben lauten T0 = 0 (die Unix-Epoche) und period = 30. Mit unixTime = 1700000000 ergibt das T = floor(1700000000 / 30) = 56666666. Diese Ganzzahl wird als 8-Byte-Big-Endian-Wert kodiert: 00 00 00 00 03 60 aa 2a. Der Zähler ändert sich nur, wenn ein neues 30-Sekunden-Fenster beginnt, deshalb bleibt jeder Code ein Fenster lang stabil und springt dann.

Schritt 3 — Den Zähler per HMAC mit dem Geheimnis verarbeiten

Der Algorithmus führt HMAC-SHA1 über den 8-Byte-Zähler aus und nimmt die Geheimnis-Bytes als Schlüssel. HMAC ist eine kryptografische Einwegfunktion: Ohne das Geheimnis lässt sich der Digest weder umkehren noch ein gültiger fälschen, und deshalb ist der Code nicht fälschbar. Für unsere Eingaben lautet der Digest die 20 Bytes 1d 70 6e 94 1a c7 6b 6d 4a 46 dd 6f af a4 5f e3 35 11 bf 86.

Schritt 4 — Dynamische Kürzung zu einem 6-stelligen Code (RFC 4226)

Ein 20-Byte-Digest ist zu lang zum Eintippen, deshalb zieht die dynamische Kürzung aus RFC 4226 eine Zahl daraus. Das untere Nibble des letzten Bytes liefert den Offset: Das letzte Byte ist 0x86, sein unteres Nibble ist 6, also ist der Offset 6. Lesen Sie 4 Bytes ab diesem Offset (6b 6d 4a 46), maskieren Sie das oberste Bit des ersten aus, damit die Zahl positiv bleibt, und Sie erhalten die Ganzzahl 1802324550. Modulo 10^6 und mit Nullen aufgefüllt: 1802324550 % 1000000 = 324550. Das ist der Code, den Ihre App für dieses Geheimnis in diesem Moment anzeigt.

Derselbe Algorithmus in JavaScript, mit der nativen Web-Crypto-API des Browsers und ohne Abhängigkeiten. Jeder Kommentar ordnet einen Block einem der vier Schritte oben zu:

// TOTP nach RFC 6238 — SHA-1, 6 Ziffern, 30s Periode (die Vorgaben).
async function generateTotp(base32Secret, unixTime = Date.now() / 1000) {
  // Schritt 1: das Base32-Geheimnis (A-Z, 2-7) zu rohen Schlüssel-Bytes dekodieren.
  const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
  let bits = '';
  for (const ch of base32Secret.replace(/=+$/, '').toUpperCase()) {
    bits += alpha.indexOf(ch).toString(2).padStart(5, '0');
  }
  const keyBytes = new Uint8Array(
    bits.match(/.{8}/g).map((b) => parseInt(b, 2)));

  // Schritt 2: Zähler = Anzahl der 30s-Schritte seit der Epoche (8-Byte big-endian).
  let counter = Math.floor(unixTime / 30);
  const msg = new Uint8Array(8);
  for (let i = 7; i >= 0; i--) {
    msg[i] = counter & 0xff;
    counter = Math.floor(counter / 256);
  }

  // Schritt 3: den Zähler per HMAC-SHA1 mit dem geheimen Schlüssel verarbeiten.
  const key = await crypto.subtle.importKey(
    'raw', keyBytes, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
  const hmac = new Uint8Array(await crypto.subtle.sign('HMAC', key, msg));

  // Schritt 4: dynamische Kürzung (RFC 4226) -> 6-stelliger Code.
  const offset = hmac[hmac.length - 1] & 0x0f;
  const binary = ((hmac[offset] & 0x7f) << 24) | (hmac[offset + 1] << 16) |
                 (hmac[offset + 2] << 8) | hmac[offset + 3];
  return (binary % 1_000_000).toString().padStart(6, '0');
}

const code = await generateTotp('JBSWY3DPEHPK3PXP', 1700000000);
console.log(code); // -> "324550"

Derselbe Algorithmus in Python, ausschließlich mit der Standardbibliothek (hmac und struct):

import base64, hmac, hashlib, struct, time

def totp(secret, for_time=None, period=30, digits=6, digest='sha1'):
    if for_time is None:
        for_time = time.time()
    # Schritt 1: das Geheimnis per Base32 zu rohen Schlüssel-Bytes dekodieren.
    key = base64.b32decode(secret.upper())
    # Schritt 2: Zähler = Anzahl der Zeitschritte seit der Epoche (8-Byte big-endian).
    counter = int(for_time // period)
    msg = struct.pack(">Q", counter)
    # Schritt 3: den Zähler per HMAC mit dem Geheimnis verarbeiten.
    h = hmac.new(key, msg, digest).digest()
    # Schritt 4: dynamische Kürzung (RFC 4226) -> N-stelliger Code.
    offset = h[-1] & 0x0f
    binary = ((h[offset] & 0x7f) << 24 |
              (h[offset + 1] & 0xff) << 16 |
              (h[offset + 2] & 0xff) << 8 |
              (h[offset + 3] & 0xff))
    return str(binary % (10 ** digits)).zfill(digits)

print(totp('JBSWY3DPEHPK3PXP', 1700000000))  # -> 324550

Beide Implementierungen geben für unsere feste Zeit 324550 aus, und beide reproduzieren die offiziellen RFC-6238-Testvektoren (so liefert der SHA-1-Vektor bei T = 59 den Wert 94287082). Tauschen Sie SHA-1 gegen SHA-256 oder SHA-512 oder ändern Sie die Ziffernanzahl, muss der Prüfer auf der anderen Seite genau dieselben Entscheidungen treffen, sonst passen die Codes nie zusammen.

Einen TOTP-Code serverseitig prüfen

Einen Code zu erzeugen ist die halbe Miete. Die andere Hälfte ist die Entscheidung des Servers, ob er die 6 Ziffern annimmt, die ein Nutzer gerade getippt hat, und in diesem Schritt stecken alle sicherheitsrelevanten Abwägungen.

Der Server speichert keine Codes. Er speichert das Geheimnis und berechnet beim Anmelden aus diesem Geheimnis und der aktuellen Zeit den erwarteten Code neu, dann vergleicht er. Der Haken ist die Uhrenabweichung: Nutzergerät und Server sind sich selten auf die Sekunde genau einig, also würde eine strikte Gleichheitsprüfung Codes nahe einer Fenstergrenze ablehnen. Die Lösung ist ein kleines Prüffenster. Akzeptieren Sie den aktuellen Schritt und je einen Schritt auf beiden Seiten, also die Codes für die Zähler T−1, T und T+1. Ein breiteres Fenster toleriert mehr Abweichung, vergrößert aber die Ratefläche, deshalb ist Fenster 1 (eine Toleranz von ±30 Sekunden) der übliche Ausgleich. Genau diese ±1-Schritt-Toleranz sehen Sie auf dem Verify-Tab des Tools.

import { createHmac, timingSafeEqual } from 'crypto';

function verifyTotp(secret, code, { window = 1, period = 30, digits = 6 } = {}) {
  const counter = Math.floor(Date.now() / 1000 / period);
  const submitted = Buffer.from(code);
  // Den aktuellen Schritt und ±window Schritte auf Uhrenabweichung prüfen.
  for (let i = -window; i <= window; i++) {
    const expected = Buffer.from(totpAt(secret, counter + i, digits));
    // Konstantzeit-Vergleich, damit das Timing keine Teilübereinstimmung verrät.
    if (expected.length === submitted.length &&
        timingSafeEqual(expected, submitted)) {
      return counter + i; // passender Schritt — speichern, um Replay zu blockieren
    }
  }
  return false;
}

Zwei weitere Details machen aus “funktioniert” ein “sicher”. Erstens die Replay-Verhinderung: Speichern Sie je Nutzer den letzten akzeptierten Zähler und lehnen Sie jeden Code aus einem Schritt ab, der auf oder unter diesem liegt, damit ein einmal abgehörter Code nicht im selben Fenster ein zweites Mal funktioniert. Deshalb gibt verifyTotp den passenden Schritt zurück und nicht ein blankes true. Zweitens die Ratenbegrenzung: Ein 6-stelliger Code ist einer von einer Million Werten, und ein ±1-Fenster macht zu jedem Zeitpunkt drei davon gültig, also kann ein Angreifer ohne Drosselung den Raum per Brute Force durchprobieren. Sperren Sie das Konto oder bauen Sie nach einer Handvoll Fehlversuchen eine Verzögerung ein. Und das Geheimnis ist ein langlebiger Schlüssel: Verschlüsseln Sie es im Ruhezustand, halten Sie es aus der Versionsverwaltung heraus und behandeln Sie es wie ein Passwort. Erzeugen Sie dazu starke Wiederherstellungscodes für den Tag, an dem ein Gerät verloren geht.

Wovor TOTP schützt – und wovor nicht

TOTP ist eine echte Verbesserung gegenüber Passwörtern allein, aber kein Allheilmittel, und Marketingseiten beschönigen die Lücken gern. Die folgende Aufteilung sagt, was wirklich Sache ist.

TOTP verhindertTOTP verhindert NICHT
Geleakte oder wiederverwendete PasswörterEchtzeit-Phishing / Adversary-in-the-Middle
Credential StuffingSchadsoftware, die das Geheimnis vom Gerät liest
Remote-Passwort-Brute-ForceSchwache Konto-Wiederherstellungsabläufe, die 2FA überspringen
Eine DB-Verletzung, die nur Passwort-Hashes offenlegt(diese brauchen andere Schutzmaßnahmen)

Der Gewinn ist erheblich. Die Anmeldung verlangt jetzt einen Code, den nur das Geheimnis erzeugen kann, also reicht ein geleaktes Passwort nicht mehr, und Credential Stuffing wie Remote-Brute-Force sind damit vom Tisch. Leakt Ihre Datenbank, sind die TOTP-Geheimnisse aber im Ruhezustand verschlüsselt, kann der Angreifer trotzdem keine Codes erzeugen.

Die Lücken sind genauso real. Ein Echtzeit-Phishing-Proxy (eine Adversary-in-the-Middle-Seite) kann dem Nutzer ein perfektes Abbild zeigen, den aktuellen Code abgreifen und ihn im selben Fenster an die echte Website weiterreichen. Dass der Code an der falschen Stelle eingegeben wurde, kann TOTP nicht erkennen. Schadsoftware auf dem Gerät, die das Geheimnis exfiltriert, hebelt es komplett aus, und ein nachlässiger “2FA vergessen”-Wiederherstellungsablauf kann es ganz umgehen. Eine häufige Verwechslung sei hier ausgeräumt: SIM-Swap-Angriffe hebeln SMS-Einmalcodes aus, nicht TOTP. TOTP hat keinen Telefonnummern-Kanal, also gibt es nichts, was ein Angreifer umleiten könnte.

Was kommt danach? Passkeys und FIDO2/WebAuthn sind an die Origin gebunden und damit von Grund auf phishing-resistent: Die Anmeldedaten weigern sich schlicht, sich bei der falschen Domain zu authentifizieren. Behandeln Sie TOTP als einen starken, überall verfügbaren Schritt über Passwörter hinaus, nicht als das Endziel. Es fügt sich nahtlos in den Rest Ihres Auth-Stacks ein: Die JWT-Sicherheits-Best-Practices decken die Session-Token-Schicht ab, die auf einer geprüften Anmeldung aufsetzt, und Passwort-Hashing (bcrypt vs. Argon2) die Passwort-im-Ruhezustand-Schicht, die 2FA ergänzt.

Häufige Fallstricke bei der TOTP-Implementierung

Die meisten TOTP-Fehler stecken nicht im Algorithmus, den der RFC festlegt, sondern in der Verdrahtung drumherum. Über diese stolpern Implementierende immer wieder.

  • Uhrenabweichung des Servers. Läuft auf dem Server kein NTP, gleitet seine Vorstellung von “jetzt” von der des Nutzergeräts weg und die Codes stimmen für niemanden mehr. Aktivieren Sie auf jedem Host die Netzwerk-Zeitsynchronisation.
  • Klartext- oder eingecheckte Geheimnisse. Ein Geheimnis in einer Konfigurationsdatei, die in git liegt, ist eine dauerhafte Hintertür. Speichern Sie es verschlüsselt in einem Secrets-Manager, niemals in der Versionsverwaltung.
  • Kein Replay-Schutz. Akzeptieren Sie einen Code, ohne den passenden Schritt zu erfassen, funktioniert derselbe Code in seinem Fenster ein zweites Mal. Speichern Sie je Nutzer dauerhaft den zuletzt verwendeten Schritt und lehnen Sie eine Wiederverwendung ab.
  • Ein Fenster, das zu breit oder zu schmal ist. Zu breit vervielfacht die erratbaren Codes und schwächt die Sicherheit; zu schmal lehnt legitime Nutzer schon bei kleiner Abweichung ab. Fenster 1 ist der übliche Ausgleich.
  • Parameter-Diskrepanz. Kodiert die Registrierung SHA-256 und 8 Ziffern in der otpauth://-URI, nimmt der Prüfer aber SHA-1 und 6 Ziffern an, wird nie ein Code gültig sein. Lesen Sie Algorithmus, Ziffernanzahl und Periode aus der URI und verwenden Sie sie auf beiden Seiten.
  • Keine Backup- oder Wiederherstellungscodes. Geht ein Telefon verloren, ist der einzige Weg zurück ein Wiederherstellungspfad. Geben Sie beim Einrichten Wiederherstellungscodes aus und machen Sie sie so stark, wie es das Konto verlangt; dieselbe Logik hinter der Passwort-Entropie gilt auch für Wiederherstellungsgeheimnisse.

FAQ

Ist TOTP phishing-sicher?

Nein. TOTP verhindert geleakte Passwörter und Remote-Brute-Force, aber ein Echtzeit-Phishing-Proxy kann eine gefälschte Anmeldung zeigen, den aktuellen Code abgreifen und ihn im selben 30-Sekunden-Fenster an die echte Website weiterreichen. Passkeys und FIDO2 sind hier die phishing-resistente Verbesserung, weil sie die Anmeldedaten an die Origin der Website binden.

Ist TOTP sicherer als SMS-2FA?

Ja. SMS-Codes wandern über das Mobilfunknetz, lassen sich per SIM-Swap- oder SS7-Angriff abfangen und hängen von der Sicherheit Ihres Mobilfunkanbieters ab. TOTP hat keinen Telefonnummern-Kanal und überträgt den Code nie, also lässt sich unterwegs nichts abfangen. Das Geheimnis wird ein einziges Mal beim Einrichten ausgetauscht.

Was passiert, wenn ich mein Telefon oder meine Authenticator-App verliere?

Sie brauchen ein im Voraus eingerichtetes Backup. Die Optionen sind Wiederherstellungscodes, die Sie beim Einrichten von 2FA gespeichert haben, ein zweites mit demselben Geheimnis registriertes Gerät oder das ursprüngliche Base32-Geheimnis, das an einem sicheren Ort aufbewahrt wird. Ohne eine davon bedeutet der Verlust des Geräts, dass Sie aus dem Konto ausgesperrt sind.

Wie prüft ein Server einen TOTP-Code?

Er berechnet aus dem gemeinsamen Geheimnis und der aktuellen Zeit den erwarteten Code neu und prüft dann den eingereichten Code gegen den aktuellen Zeitschritt und je einen Schritt auf beiden Seiten, um Uhrenabweichungen zuzulassen. Er erfasst außerdem, welcher Schritt gepasst hat, damit derselbe Code nicht wiederholt werden kann, und begrenzt die Versuchsrate, um Raten zu blockieren.

Warum erneuern sich TOTP-Codes alle 30 Sekunden?

Dreißig Sekunden ist die Standardperiode von RFC 6238: lang genug, um den Code bequem zu lesen und einzutippen, kurz genug, dass ein abgefangener Code fast sofort abläuft. Manche Systeme verwenden 60 Sekunden, was die otpauth://-URI festhält, damit der Prüfer es übernimmt.

Können sich zwei Geräte ein TOTP-Geheimnis teilen?

Ja. Jedes Gerät mit demselben Base32-Geheimnis und einer synchronisierten Uhr erzeugt identische Codes, denn der Algorithmus ist deterministisch. So funktionieren Authenticator-Backups über mehrere Geräte, und deshalb muss das Geheimnis privat bleiben: Wer es kopiert, kann jeden künftigen Code erzeugen.

Ist TOTP dasselbe wie Google Authenticator?

Nein. TOTP ist der offene Algorithmus aus RFC 6238. Google Authenticator, Authy und 1Password sind Apps, die ihn implementieren. Weil der Standard derselbe ist, funktioniert jede konforme App mit jedem Dienst, der TOTP verwendet; an einen bestimmten Anbieter ist nichts gebunden.

Fazit

Die Kernideen sind kurz genug, um sie im Kopf zu behalten:

  • TOTP macht aus einem gemeinsamen Geheimnis und der aktuellen Zeit per HMAC und Kürzung einen Code.
  • Beide Seiten berechnen den Code getrennt; über das Netzwerk geht er nie.
  • Prüfen Sie mit einem ±1-Schritt-Fenster und dazu Replay-Schutz und Ratenbegrenzung.
  • Es verhindert Passwortangriffe, aber kein Echtzeit-Phishing; diese Lücke schließen Passkeys.
  • Halten Sie die Serveruhren per NTP synchron und das Geheimnis verschlüsselt und privat.

Wollen Sie sehen, wie der Algorithmus echte Zahlen erzeugt, und Ihr eigenes Prüffenster ausprobieren? Im TOTP-/2FA-Generator berechnen, richten und prüfen Sie Codes vollständig in Ihrem Browser, und das Geheimnis verlässt Ihr Gerät dabei nie.

Tags: Two-Factor Authentication TOTP Security Authentication 2FA

Verwandte Artikel

Alle Artikel anzeigen