💻 Aplikovaná kryptografia · Hĺbkový inžiniersky rozbor

Ako funguje TOTP: Implementácia algoritmu RFC 6238 v Pythone, JavaScripte a Go

2F Bezpečnostné výskumné laboratórium 2FA-Tool · Aktualizované: október 2026 · 16 min čítania (viac ako 2 700 slov)
💡 Architektonický abstrakt a inžiniersky manifest

Medzi základnými kryptografickými protokolmi modernej webovej bezpečnosti vyniká RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) svojou matematickou jednoduchosťou. Nevyžaduje žiadne interaktívne sieťové handshake protokoly, žiadnu centralizovanú infraštruktúru verejných kľúčov (PKI) ani volania do cloudu. Iba so zdieľaným tajným kľúčom Base32 a neustálym plynutím fyzického unixového času klient a server každých 30 sekúnd vypočítajú identické, efemérne 6-ciferné autentifikačné tokeny so 100 % matematickou istotou.

V tomto komplexnom sprievodcovi obídeme externé knižnice fungujúce ako čierne skrinky (ako pyotp alebo otplib) a vytvoríme kompletný kryptografický reťazec RFC 6238 od nuly v Pythone, JavaScripte a Go, pričom každú implementáciu overíme voči oficiálnym testovacím vektorom IETF.

01. Elegancia autentifikácie riadenej časom

Aby sme pochopili TOTP, musíme sa najprv pozrieť na jeho predchodcu: RFC 4226 HOTP (HMAC-Based One-Time Password).

V HOTP klient a server zdieľajú počítadlo udalostí $C$. Zakaždým, keď používateľ vygeneruje token, počítadlo sa zvýši ($C = C + 1$) a token sa odvodí z $\text{HMAC-SHA-1}(K, C)$. Hoci bol tento prístup matematicky spoľahlivý, trpel prevádzkovou slabinou: desynchronizáciou počítadla. Ak používateľ nechtiac stlačil tlačidlo na hardvérovom tokene päťkrát vo vrecku, počítadlo klienta dosiahlo $C=6$, zatiaľ čo autentifikačný server zostal na hodnote $C=1$, čo viedlo k trvalému odmietaniu prihlásenia.

⏱️ Prielom RFC 6238: Nahradenie počítadla udalostí časom epochy

Štandard RFC 6238, ktorý v máji 2011 publikovali N. M'Raihi, S. Machani, M. Pei a J. Rydell, vyriešil problém desynchronizácie počítadla nahradením prírastkového celého čísla štandardizovaným unixovým časom epochy. Keďže fyzický čas plynie na celom svete rovnomerne, klient aj server posúvajú svoje interné počítadlá súčasne bez odoslania jediného sieťového paketu.

02. 5 kryptografických matematických krokov TOTP

Transformácia 16-znakového semienka Base32 (napr. JBSWY3DPEHPK3PXP) na 6-ciferný rotujúci kód (napr. 284915) zahŕňa päť presných kryptografických fáz:

1 Krok 1: Dekódovanie tajného kľúča Base32

Registračné kľúče z burzy či služby sú kódované v štandarde RFC 4648 Base32 pomocou abecedy A-Z a 2-7 (s vynechaním nejednoznačných znakov ako 0, 1, 8, 9). Algoritmus najprv tento reťazec normalizuje, odstráni medzery a doplní zarovnanie (=), aby bola jeho dĺžka násobkom 8, a následne ho dekóduje na surové binárne bajty kľúča $K$.

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

Aktuálna unixová časová pečiatka (počet sekúnd uplynutých od 1. januára 1970 UTC) sa mapuje na celočíselné počítadlo $T$:

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

So štandardnými parametrami $T_0 = 0$ a trvaním kroku $X = 30$ sekúnd. Toto celé číslo $T$ sa následne zakóduje ako 8-bajtové celé číslo bez znamienka vo formáte Big-Endian (64-bit unsigned integer).

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

So surovými bajtmi kľúča $K$ ako tajným kľúčom vypočítame digest HMAC-SHA1 cez 8-bajtový big-endian časový buffer, čím získame 20-bajtový (160-bitový) autentifikačný hash správy $HS$:

HS = HMAC-SHA-1(Key, T)
4 Krok 4: Dynamické orezanie (Dynamic Truncation - DT)

Na konverziu 20 surových bajtov na celé číslo definuje RFC 4226 dynamické orezanie (Dynamic Truncation):

  • Extrakcia posunu (Offset): Skontrolujte posledný bajt $HS[19]$ a aplikujte masku 0x0F na extrahovanie jeho spodných 4 bitov, čo poskytne hodnotu posunu medzi 0 a 15: Offset = HS[19] & 0x0F;
  • Výrez 4 bajtov: Extrahujte 4 po sebe idúce bajty začínajúce na Offset: $P = HS[Offset \dots Offset+3]$;
  • Maskovanie znamienkového bitu: Maskujte najvýznamnejší bit (MSB) hodnotou 0x7F, aby sa predišlo nejednoznačnosti pretečenia celého čísla so znamienkom:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Krok 5: Redukcia modulo na $N$ číslic

Nakoniec redukujte BinaryCode modulo $10^6$ (1 000 000) pre štandardné 6-ciferné tokeny. Výsledok zarovnajte zľava nulami, aby sa zabezpečil konzistentný 6-znakový výstupný reťazec:

OTP = BinaryCode mod 10^6

03. Spustiteľný kód 1: Štandardná knižnica Python 3

Táto kompletná implementácia využíva vstavané moduly Pythonu base64, struct, hmac a hashlib bez nutnosti inštalovať akékoľvek balíky tretích strán cez pip:

totp_rfc6238.py Python 3.8+ (Nulové 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: """ Generate an RFC 6238 compliant TOTP token. :param secret_base32: Shared secret key in Base32 format (e.g., ‘JBSWY3DPEHPK3PXP’) :param timestamp: Unix epoch timestamp in seconds. Defaults to time.time() :param step: Time-step window in seconds (default: 30) :param digits: Number of output digits (default: 6) :return: Left-padded OTP string (e.g., ‘045812’) """ if timestamp is None: timestamp = int(time.time())

# 1. Normalize Base32 string and append '=' padding characters
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. Compute 64-bit big-endian time-step counter T
time_counter = timestamp // step
time_bytes = struct.pack(">Q", time_counter)  # 8 bytes big-endian unsigned long long

# 3. Calculate HMAC-SHA1 digest (20 bytes)
hmac_digest = hmac.new(key_bytes, time_bytes, hashlib.sha1).digest()

# 4. Perform Dynamic Truncation
offset = hmac_digest[-1] & 0x0F  # Lower 4 bits of the 20th byte
# Unpack 4 bytes at offset and clear MSB sign bit
binary_code = struct.unpack(">I", hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF

# 5. Modulo arithmetic and zero-fill formatting
otp_code = binary_code % (10 ** digits)
return str(otp_code).zfill(digits)

if name == “main”: test_secret = “JBSWY3DPEHPK3PXP” print(f”Generated TOTP Code: {generate_totp(test_secret)}”)

04. Spustiteľný kód 2: JavaScript a Node.js Crypto

Čistá implementácia v JavaScripte pre runtime Node.js na backende s využitím natívneho crypto.createHmac:

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

// RFC 4648 Base32 Decoder in Pure JavaScript 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);

// Allocate 8-byte buffer in Big-Endian format const buffer = Buffer.alloc(8); buffer.writeBigUInt64BE(BigInt(counter), 0);

// Compute HMAC-SHA1 const hmac = crypto.createHmac(‘sha1’, key).update(buffer).digest();

// 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(“Calculated TOTP:”, generateTOTP(“JBSWY3DPEHPK3PXP”));

Klientske vykonávanie v prehliadači cez W3C Web Crypto API:

Pre moderné webové aplikácie vyžadujúce 100 % offline bezpečnosť (presne taká architektúra poháňa 2FA-Tool) poskytuje W3C Web Crypto API asynchrónne, hardvérovo akcelerované kryptografické primitíva priamo v sandboxe prehliadača bez externých balíkov z npm:

webcrypto_totp.js Prehliadač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 raw secret key into SubtleCrypto const cryptoKey = await window.crypto.subtle.importKey( “raw”, keyBytes, { name: “HMAC”, hash: { name: “SHA-1” } }, false, [“sign”] );

// 2. Encode 8-byte Big-Endian counter buffer const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigUint64(0, BigInt(counter), false); // false = Big-Endian

// 3. Perform cryptographic HMAC sign const signature = await window.crypto.subtle.sign(“HMAC”, cryptoKey, buffer); const hmacResult = new Uint8Array(signature);

// 4. 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. Spustiteľný kód 3: Produkčný Golang (Go)

Vysoko priepustné mikroslužby ťažia z rokmi overenej štandardnej knižnice jazyka Go:

totp.go Štandardná knižnica Go 1.20+
package main

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

// GenerateTOTP computes standard RFC 6238 time-based tokens func GenerateTOTP(secretBase32 string, timestamp int64, step int64, digits int) (string, error) { // 1. Sanitize Base32 seed and decode 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. Encode 64-bit Big-Endian time-step counter
counter := uint64(timestamp / step)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)

// 3. Compute HMAC-SHA1
mac := hmac.New(sha1.New, key)
mac.Write(buf)
hash := mac.Sum(nil)

// 4. Dynamic Truncation
offset := hash[len(hash)-1] & 0x0f
binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7fffffff

// 5. Modulo arithmetic
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(“Golang Computed TOTP: %s\n”, code) }

06. Overenie testovacích vektorov RFC 6238 Príloha B

Na preukázanie zhody so štandardom musí byť každá kryptografická implementácia overená voči oficiálnym testovacím vektorom publikovaným v IETF RFC 6238 Príloha B.

Oficiálna testovacia sada používa ASCII tajomstvo "12345678901234567890" (20 bajtov), ktoré sa v Base32 kóduje ako:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (vyhodnotené pre 8-ciferné výstupy s 30-sekundovými krokmi):

Unixová časová pečiatka epochy Dátum a čas UTC Oficiálny očakávaný kód RFC Výsledok implementácie
59 1970-01-01 00:00:59 94287082 ✓ SPLNENÉ (Presná zhoda)
1111111109 2005-03-18 01:58:29 07081804 ✓ SPLNENÉ (Presná zhoda)
1111111111 2005-03-18 01:58:31 14050471 ✓ SPLNENÉ (Presná zhoda)
1234567890 2009-02-13 23:31:30 89005924 ✓ SPLNENÉ (Presná zhoda)
2000000000 2033-05-18 03:33:20 69279037 ✓ SPLNENÉ (Presná zhoda)

07. Produkčné inžinierstvo: Časový posun, opakované útoky a limity

Pri nasadzovaní overovania TOTP do podnikových autentifikačných systémov s vysokou dostupnosťou je potrebné presadzovať tri bezpečnostné pravidlá:

Pravidlo 1 Tolerančné okno pre časový posun hodín (Clock Drift)

Vzhľadom na drobné nepresnosti hodín mobilných zariadení by servery mali vyhodnocovať okno [T - 1, T, T + 1] (kontrola predchádzajúcich 30 s, aktuálnych 30 s a nasledujúcich 30 s). Nikdy nerozširujte okno nad rámec $\pm 1$ kroku, pretože každý ďalší krok zväčšuje útočnú plochu pre útok opakovaním (replay attack) o 30 sekúnd.

Pravidlo 2 Vynútenie jednorazového použitia tokenu (Anti-Replay)

Token musí byť po úspešnom overení okamžite zneplatnený. Uložte Last_Verified_Time_Step do Redis alebo do vyrovnávacej pamäte v pamäti (in-memory cache). Ak sa klient pokúsi odoslať rovnaký token v rámci toho istého 30-sekundového okna, požiadavku odmietnite, aby ste zmarili man-in-the-middle replay útoky.

Pravidlo 3 Obmedzenie rýchlosti pri útokoch hrubou silou a porovnávanie v konštantnom čase

Pretože 6-ciferné kódy pokrývajú len 1 000 000 permutácií, neobmedzené koncové body (endpoints) možno rýchlo prelomiť hrubou silou. Zaveďte progresívne oneskorenia a uzamknutie účtu po 3 po sebe idúcich neúspešných pokusoch. V kóde vykonávajte porovnávania pomocou funkcií s konštantným časom (napr. crypto/subtle.ConstantTimeCompare v Go alebo crypto.timingSafeEqual v Node.js), aby ste zabránili analýze časovania cez postranné kanály (side-channel timing attacks).

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

Otázka 1: Keďže SHA-1 je kryptograficky prelomený, prečo TOTP stále predvolene používa HMAC-SHA1? ▼

Teoretické kolízne útoky proti samotnému SHA-1 nenarúšajú konštrukciu HMAC (RFC 2104). V HMAC je základná hashovacia funkcia vnorená v rámci interných a externých tajných kľúčov, čo neutralizuje útoky založené na kolíziách. Dodnes zostáva HMAC-SHA1 plne bezpečný pre 2FA. Okrem toho RFC 6238 plne podporuje HMAC-SHA256 a HMAC-SHA512, ak sa vyžaduje vyššia úroveň záruky.

Otázka 2: Ovplyvňujú časové pásma alebo letný čas (DST) synchronizáciu TOTP? ▼

Nie. Unixový čas epochy je definovaný ako počet uplynutých sekúnd od 1970-01-01 00:00:00 UTC. Je striktne nezávislý od časových pásiem, prestupných sekúnd a posunov letného času. V ktoromkoľvek presnom časovom okamihu majú server v Tokiu a smartfón v Kalifornii úplne rovnakú celočíselnú časovú pečiatku.

Otázka 3: Ako sa 5-znaková autentifikácia Steam Guard líši od RFC 6238? ▼

Steam Guard je proprietárny variant založený na RFC 6238. Používa úplne rovnaký 30-sekundový časový krok a výpočet HMAC-SHA1, ale v konečnej fáze redukcie mapuje extrahované celé číslo cez vlastné 26-znakové alfanumerické pole znakov (23456789BCDFGHJKMNPQRTVWXY), čím vytvára 5-znakový alfanumerický reťazec.

V súlade s RFC 6238 · Klientske výpočty cez W3C Web Crypto API

Vyskúšajte si generovanie kódov TOTP čisto na strane klienta naživo

2FA-Tool implementuje tento presný kryptografický reťazec priamo vo vašom prehliadači pomocou W3C Web Crypto API. Žiadne odosielanie na server, žiadne ukladanie tokenov, okamžité overenie.