💻 Aplikovaná kryptografie · Detailní inženýrský rozbor

Jak funguje TOTP: Implementace algoritmu RFC 6238 v Pythonu, JavaScriptu a Go

2F Bezpečnostní výzkumná laboratoř 2FA-Tool · Aktualizováno: říjen 2026 · 15 min čtení (více než 2 700 slov)
💡 Architektonický abstrakt a inženýrský manifest

Mezi základními kryptografickými protokoly moderní webové bezpečnosti vyniká RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) svou matematickou elegancí a přímočarostí. Nevyžaduje žádné interaktivní síťové výměny, žádnou centralizovanou infrastrukturu veřejných klíčů (PKI) ani komunikaci s cloudem. Pouze se sdíleným tajným klíčem v kódování Base32 a neúprosným tokem fyzického unixového času dokáže klient i server se stoprocentní matematickou jistotou vypočítat identické, krátkodobé 6místné autentizační tokeny každých 30 sekund.

V tomto komplexním průvodci odložíme knihovny třetích stran (jako pyotp nebo otplib), které fungují jako černé skříňky, a sestavíme kompletní kryptografickou pipeline podle RFC 6238 od základu v Pythonu, JavaScriptu a Go, přičemž každou implementaci ověříme proti oficiálním testovacím vektorům IETF.

01. Elegance autentizace řízené časem

Abychom plně pochopili TOTP, musíme se nejprve vrátit k jeho přímému předchůdci: RFC 4226 HOTP (HMAC-Based One-Time Password).

V HOTP klient a server sdílejí čítač událostí $C$. Pokaždé, když uživatel vygeneruje token, čítač se inkrementuje ($C = C + 1$) a token se odvodí z $\text{HMAC-SHA-1}(K, C)$. Ačkoli byl HOTP matematicky bezchybný, trpěl zásadní provozní nevýhodou: desynchronizací čítače. Pokud uživatel v kapse nechtěně stiskl tlačítko na svém hardwarovém tokenu pětkrát, čítač v zařízení dosáhl hodnoty $C=6$, zatímco autentizační server zůstal na $C=1$, což vedlo k trvalému selhání přihlašování.

⏱️ Průlom RFC 6238: Nahrazení čítače událostí epochálním časem

Standard RFC 6238, který v květnu 2011 publikovali N. M'Raihi, S. Machani, M. Pei a J. Rydell, vyřešil problém s desynchronizací čítače tím, že rostoucí celé číslo nahradil standardizovaným unixovým časem (Unix epoch). Vzhledem k tomu, že fyzický čas plyne rovnoměrně po celé planetě, klient i server posouvají své interní stavy synchronně a současně, aniž by si museli vyměnit jediný síťový paket.

02. 5 kryptografických kroků algoritmu TOTP

Převod 16znakového Base32 seedu (např. JBSWY3DPEHPK3PXP) na 6místný rotující kód (např. 284915) zahrnuje pět přesných kryptografických fází:

1 Krok 1: Dekódování tajného klíče z Base32

Registrační klíče jsou kódovány v RFC 4648 Base32 pomocí abecedy A-Z a 2-7 (s vynecháním snadno zaměnitelných znaků jako 0, 1, 8, 9). Algoritmus nejprve tento řetězec normalizuje, odstraní mezery a doplní zarovnání (=), aby délka byla násobkem 8, a poté jej dekóduje na surové binární bajty klíče $K$.

2 Krok 2: Výpočet časového kroku (Čítač $T$)

Aktuální unixové časové razítko (počet sekund uplynulých od 1. ledna 1970 UTC) je namapováno na celočíselný čítač $T$:

T = floor( (CurrentTime - T0) / X )

Se standardními parametry $T_0 = 0$ a délkou časového okna $X = 30$ sekund. Toto celé číslo $T$ je následně serializováno jako 8bajtové bezznaménkové 64bitové číslo ve formátu big-endian.

3 Krok 3: Výpočet hashe HMAC-SHA1

S použitím surových bajtů klíče $K$ jako tajného klíče spočítáme HMAC-SHA1 otisk přes 8bajtový big-endian časový buffer, čímž vznikne 20bajtový (160bitový) autentizační otisk $HS$:

HS = HMAC-SHA-1(Key, T)
4 Krok 4: Dynamické zkrácení (Dynamic Truncation - DT)

Pro převod 20 surových bajtů na celé číslo definuje specifikace RFC 4226 dynamické zkrácení:

  • Získání offsetu (posunu): Přečte se poslední bajt $HS[19]$ a provede se bitový součin s 0x0F pro extrakci jeho spodních 4 bitů, což dává hodnotu posunu od 0 do 15: Offset = HS[19] & 0x0F;
  • Vyříznutí 4 bajtů: Z otisku se vyjmou 4 po sobě jdoucí bajty počínaje daným posunem: $P = HS[Offset \dots Offset+3]$;
  • Vynulování znaménkového bitu: Nejvyšší platný bit (MSB) se maskuje pomocí 0x7F, aby se zabránilo nejednoznačnostem se znaménkovým přetečením:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Krok 5: Modulo redukce na $N$ číslic

Nakonec zredukujeme BinaryCode operací modulo $10^6$ (1 000 000) pro standardní 6místné tokeny. Výsledek doplníme úvodními nulami zleva, aby měl výstup vždy přesně 6 znaků:

OTP = BinaryCode mod 10^6

03. Spustitelný kód 1: Standardní knihovna Python 3

Tato kompletní implementace využívá vestavěné moduly Pythonu base64, struct, hmac a hashlib bez nutnosti instalovat jakékoli balíčky přes pip:

totp_rfc6238.py Python 3.8+ (Nulové externí závislosti)
import base64
import hashlib
import hmac
import struct
import time

def generate_totp(secret_base32: str, timestamp: int = None, step: int = 30, digits: int = 6) -> str: """ Vygeneruje TOTP token vyhovující standardu RFC 6238. :param secret_base32: Sdílený tajný klíč ve formátu Base32 (např. ‘JBSWY3DPEHPK3PXP’) :param timestamp: Unixové časové razítko v sekundách. Výchozí hodnota je time.time() :param step: Časový krok v sekundách (výchozí: 30) :param digits: Počet výstupních číslic (výchozí: 6) :return: 6místný OTP řetězec zarovnaný nulami (např. ‘045812’) """ if timestamp is None: timestamp = int(time.time())

# 1. Normalizace řetězce Base32 a doplnění znaků '='
normalized_secret = secret_base32.replace(" ", "").upper()
padding_needed = (8 - len(normalized_secret) % 8) % 8
normalized_secret += "=" * padding_needed
key_bytes = base64.b32decode(normalized_secret)

# 2. Výpočet 64bitového big-endian čítače časových kroků T
time_counter = timestamp // step
time_bytes = struct.pack(">Q", time_counter)  # 8 bajtů big-endian unsigned long long

# 3. Výpočet otisku HMAC-SHA1 (20 bajtů)
hmac_digest = hmac.new(key_bytes, time_bytes, hashlib.sha1).digest()

# 4. Provedení dynamického zkrácení (Dynamic Truncation)
offset = hmac_digest[-1] & 0x0F  # Spodní 4 bity 20. bajtu
# Rozbalení 4 bajtů na daném offsetu a vynulování znaménkového bitu (MSB)
binary_code = struct.unpack(">I", hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF

# 5. Modulo operace a doplnění nulami
otp_code = binary_code % (10 ** digits)
return str(otp_code).zfill(digits)

if name == “main”: test_secret = “JBSWY3DPEHPK3PXP” print(f”Vygenerovaný TOTP kód: {generate_totp(test_secret)}”)

04. Spustitelný kód 2: JavaScript a Node.js Crypto

Čistá implementace v JavaScriptu pro backendové běhové prostředí Node.js s využitím nativního modulu crypto.createHmac:

totp_node.js Node.js Nativní Crypto
const crypto = require('crypto');

// Dekodér RFC 4648 Base32 v čistém JavaScriptu function base32Decode(base32Str) { const alphabet = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ234567’; const cleaned = base32Str.replace(/=+$/, ”).toUpperCase().replace(/\s+/g, ”); let bits = 0, value = 0, index = 0; const output = new Uint8Array(Math.floor((cleaned.length * 5) / 8));

for (let i = 0; i < cleaned.length; i++) { const val = alphabet.indexOf(cleaned[i]); if (val === -1) continue; value = (value << 5) | val; bits += 5; if (bits >= 8) { output[index++] = (value >>> (bits - 8)) & 255; bits -= 8; } } return Buffer.from(output.buffer, 0, index); }

function generateTOTP(secretBase32, timestamp = Math.floor(Date.now() / 1000), step = 30, digits = 6) { const key = base32Decode(secretBase32); const counter = Math.floor(timestamp / step);

// Alokace 8bajtového bufferu ve formátu Big-Endian const buffer = Buffer.alloc(8); buffer.writeBigUInt64BE(BigInt(counter), 0);

// Výpočet HMAC-SHA1 const hmac = crypto.createHmac(‘sha1’, key).update(buffer).digest();

// Dynamické zkrácení (Dynamic Truncation) const offset = hmac[hmac.length - 1] & 0x0f; const binary = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | (hmac[offset + 3] & 0xff);

const otp = binary % (10 ** digits); return otp.toString().padStart(digits, ‘0’); }

console.log(“Vypočítaný TOTP kód:”, generateTOTP(“JBSWY3DPEHPK3PXP”));

Spuštění v prohlížeči přes standardní W3C Web Crypto API:

Pro moderní webové aplikace vyžadující 100% offline bezpečnost (přesně taková architektura pohání nástroj 2FA-Tool) nabízí W3C Web Crypto API asynchronní, hardwarově akcelerované kryptografické funkce přímo v sandboxu prohlížeče bez nutnosti stahovat externí npm balíčky:

webcrypto_totp.js Prohlížečové W3C Web Crypto API
async function generateBrowserTOTP(secretBase32, timestamp = Math.floor(Date.now() / 1000)) {
  const keyBytes = base32Decode(secretBase32);
  const counter = Math.floor(timestamp / 30);

// 1. Import surových bajtů klíče do SubtleCrypto const cryptoKey = await window.crypto.subtle.importKey( “raw”, keyBytes, { name: “HMAC”, hash: { name: “SHA-1” } }, false, [“sign”] );

// 2. Kódování 8bajtového big-endian čítače const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigUint64(0, BigInt(counter), false); // false = Big-Endian

// 3. Kryptografický výpočet HMAC podpisu const signature = await window.crypto.subtle.sign(“HMAC”, cryptoKey, buffer); const hmacResult = new Uint8Array(signature);

// 4. Dynamické zkrácení (Dynamic Truncation) const offset = hmacResult[hmacResult.length - 1] & 0x0f; const binary = ((hmacResult[offset] & 0x7f) << 24) | ((hmacResult[offset + 1] & 0xff) << 16) | ((hmacResult[offset + 2] & 0xff) << 8) | (hmacResult[offset + 3] & 0xff);

const otp = binary % 1000000; return otp.toString().padStart(6, ‘0’); }

05. Spustitelný kód 3: Produkční kód v Go (Golang)

Vysoce propustné mikroslužby těží z prověřené a efektivní standardní knihovny jazyka Go:

totp.go Standardní knihovna Go 1.20+
package main

import ( “crypto/hmac” “crypto/sha1” “encoding/base32” “encoding/binary” “fmt” “math” “strings” “time” )

// GenerateTOTP vypočítá standardní časové tokeny dle RFC 6238 func GenerateTOTP(secretBase32 string, timestamp int64, step int64, digits int) (string, error) { // 1. Očištění Base32 řetězce a dekódování cleaned := strings.ToUpper(strings.ReplaceAll(secretBase32, ” ”, "")) key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(cleaned) if err != nil { key, err = base32.StdEncoding.DecodeString(cleaned) if err != nil { return "", err } }

// 2. Kódování 64bitového big-endian čítače časového kroku
counter := uint64(timestamp / step)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)

// 3. Výpočet HMAC-SHA1
mac := hmac.New(sha1.New, key)
mac.Write(buf)
hash := mac.Sum(nil)

// 4. Dynamické zkrácení (Dynamic Truncation)
offset := hash[len(hash)-1] &amp; 0x0f
binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) &amp; 0x7fffffff

// 5. Modulo aritmetika
mod := uint32(math.Pow10(digits))
otp := binaryCode % mod

format := fmt.Sprintf("%%0%dd", digits)
return fmt.Sprintf(format, otp), nil

}

func main() { code, err := GenerateTOTP(“JBSWY3DPEHPK3PXP”, time.Now().Unix(), 30, 6) if err != nil { panic(err) } fmt.Printf(“Golangem vypočítaný TOTP kód: %s\n”, code) }

06. Ověření testovacích vektorů podle RFC 6238 Příloha B

K prokázání plné shody se standardem musí být každá kryptografická implementace ověřena proti oficiálním testovacím vektorům publikovaným v IETF RFC 6238 Příloha B.

Oficiální sada testů používá ASCII tajný klíč "12345678901234567890" (20 bajtů), který má v Base32 následující podobu:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (testováno pro 8místné výstupy s 30sekundovými kroky):

Unixové časové razítko UTC Datum a čas Očekávaný kód dle RFC Výsledek implementace
59 1970-01-01 00:00:59 94287082 ✓ ÚSPĚŠNĚ OVĚŘENO (Přesná shoda)
1111111109 2005-03-18 01:58:29 07081804 ✓ ÚSPĚŠNĚ OVĚŘENO (Přesná shoda)
1111111111 2005-03-18 01:58:31 14050471 ✓ ÚSPĚŠNĚ OVĚŘENO (Přesná shoda)
1234567890 2009-02-13 23:31:30 89005924 ✓ ÚSPĚŠNĚ OVĚŘENO (Přesná shoda)
2000000000 2033-05-18 03:33:20 69279037 ✓ ÚSPĚŠNĚ OVĚŘENO (Přesná shoda)

07. Produkční inženýrství: Posun, replay a limity

Při nasazování ověřování TOTP ve vysoce dostupných podnikových autentizačních systémech je nutné striktně vymáhat tři základní bezpečnostní pravidla:

Pravidlo 1 Tolerance časového posunu (Clock Drift Tolerance)

V důsledku drobných nepřesností hodin na mobilních zařízeních by servery měly vyhodnocovat okno [T - 1, T, T + 1] (tedy kontrolovat předchozích 30 s, aktuálních 30 s a následujících 30 s). Nikdy nerozšiřujte okno za hranici $\pm 1$ kroku, protože každý další krok rozšiřuje prostor pro útoky přehráním (replay attack) o dalších 30 sekund.

Pravidlo 2 Vynucení jednorázového použití tokenu (Anti-Replay ochrana)

Jakmile je token úspěšně ověřen, musí být okamžitě zneplatněn. Uložte Last_Verified_Time_Step do mezipaměti Redis nebo do paměti aplikace. Pokud se klient pokusí odeslat stejný token znovu ve stejném 30sekundovém okně, požadavek okamžitě zamítněte, což neutralizuje útoky typu man-in-the-middle replay.

Pravidlo 3 Omezení rychlosti (Rate Limiting) a porovnávání v konstantním čase

Jelikož 6místné kódy pokrývají pouze 1 000 000 možností, nechráněné koncové body lze velmi rychle prolomit útokem hrubou silou. Zaveďte progresivní zpoždění a dočasné zablokování účtu po 3 po sobě jdoucích neúspěšných pokusech. V kódu provádějte porovnání řetězců pomocí funkcí běžících v konstantním čase (např. crypto/subtle.ConstantTimeCompare v Go nebo crypto.timingSafeEqual v Node.js), abyste zabránili postranním časovým útokům (timing attacks).

08. Často kladené otázky (FAQ)

Otázka 1: Proč TOTP stále používá HMAC-SHA1, když je samostatný hash SHA-1 kryptograficky prolomený? ▼

Teoretické kolizní útoky proti samostatnému hashi SHA-1 nenarušují konstrukci HMAC (RFC 2104). V HMAC je podkladová hashovací funkce zanořena mezi interní a externí tajné klíče, což zcela neutralizuje útoky založené na kolizích. Dodnes zůstává HMAC-SHA1 pro účely 2FA plně bezpečný. Navíc standard RFC 6238 plně podporuje i HMAC-SHA256 a HMAC-SHA512, pokud jsou vyžadovány vyšší bezpečnostní záruky.

Otázka 2: Mají časová pásma nebo letní čas (DST) vliv na synchronizaci TOTP? ▼

Nikoli. Unixový čas (Epoch Time) je definován jako počet sekund uplynulých od 1970-01-01 00:00:00 UTC. Je zcela nezávislý na časových pásmech, přestupných sekundách i posunech letního času. V libovolném okamžiku mají server v Tokiu i smartphone v Praze naprosto identické celočíselné časové razítko.

Otázka 3: V čem se liší pětimístné ověřování Steam Guard od standardu RFC 6238? ▼

Steam Guard je proprietární varianta založená na RFC 6238. Využívá naprosto stejný 30sekundový časový krok i výpočet HMAC-SHA1, ale v závěrečné fázi redukce mapuje vyextrahované celé číslo pomocí vlastní 26znakové alfanumerické abecedy (23456789BCDFGHJKMNPQRTVWXY), čímž vytváří pětimístný textový kód.

Kompatibilní s RFC 6238 · Výpočty na straně klienta přes W3C Web Crypto API

Vyzkoušejte si generování kódů TOTP čistě na straně klienta naživo

2FA-Tool implementuje tuto přesnou kryptografickou pipeline přímo ve vašem prohlížeči pomocí W3C Web Crypto API. Žádné odesílání dat na server, nulové ukládání tokenů, okamžité ověření.