💻 Rakenduslik krüptograafia · Tehniline süvaanalüüs

Kuidas TOTP töötab: RFC 6238 algoritmi teostus Pythonis, JavaScriptis ja Go-s

2F 2FA-Tooli turvauuringute labor · Uuendatud oktoobris 2026 · 16 min lugemist (2700+ sõna)
💡 Arhitektuurne kokkuvõte ja inseneri manifest

Kaasaegse veebiturbe fundamentaalsete krüptograafiliste protokollide hulgas paistab RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) silma oma matemaatilise lihtsuse poolest. See ei vaja interaktiivseid võrgukätlusi, tsentraliseeritud avaliku võtme infrastruktuuri (PKI) ega päringuid pilveteenustesse. Kasutades vaid ühist Base32 salajast võtit ja füüsilise Unixi aja vääramatut kulgemist, arvutavad klient ja server iga 30 sekundi järel 100% matemaatilise kindlusega identseid, lühiajalisi 6-kohalisi autentimiskoode.

Selles põhjalikus juhendis jätame kõrvale kolmandate osapoolte musta kasti teegid (nagu pyotp või otplib) ning ehitame kogu RFC 6238 krüptograafilise ahela nullist üles keeltes Python, JavaScript ja Go, kontrollides iga teostust IETF-i ametlike testvektorite abil.

01. Ajal põhineva autentimise elegants

TOTP olemuse mõistmiseks tuleb esmalt vaadata selle eelkäijat: RFC 4226 HOTP (HMAC-Based One-Time Password).

HOTP protokollis jagavad klient ja server sündmuste loendurit $C$. Iga kord, kui kasutaja genereerib koodi, loendur suureneb ($C = C + 1$) ja kood tuletatakse tehte $\text{HMAC-SHA-1}(K, C)$ abil. Kuigi matemaatiliselt usaldusväärne, tekitas HOTP praktilises töös tõsise puuduse: loenduri desünkroonimise. Kui kasutaja vajutas riistvaralise seadme nuppu taskus kogemata viis korda järjest, jõudis kliendi loendur väärtuseni $C=6$, samal ajal kui autentimisserver ootas väärtust $C=1$, mis tõi kaasa sisselogimise ebaõnnestumise.

⏱️ RFC 6238 läbimurre: sündmuste loenduri asendamine Unixi ajaga

2011. aasta mais N. M'Raihi, S. Machani, M. Pei ja J. Rydelli poolt avaldatud RFC 6238 lahendas loendurite desünkroonimise probleemi, asendades muutuva täisarvu standardse Unixi ajatempliga (Unix Epoch time). Kuna füüsiline aeg liigub ühtlaselt üle kogu maakera, suurendavad nii klient kui ka server oma sisemist loendurit üheaegselt ilma ainsatki võrgupaketti vahetamata.

02. TOTP 5 krüptograafilist matemaatilist sammu

16-märgilise Base32 algseeme (nt JBSWY3DPEHPK3PXP) teisendamine 6-kohaliseks rulluvaks koodiks (nt 284915) koosneb viiest täpsest krüptograafilisest etapist:

1 Samm 1: Salajase võtme Base32 dekodeerimine

Teenuse registreerimisel esitatavad võtmed kodeeritakse RFC 4648 Base32 formaadis, kasutades tähestikku A-Z ja 2-7 (jättes välja eksitavad märgid nagu 0, 1, 8, 9). Algoritm normaliseerib esmalt selle sõne, eemaldab tühikud ja lisab vajadusel polsterduse (=), et pikkus jaguks 8-ga, ning dekodeerib selle tooresteks binaarseteks võtmebaitideks $K$.

2 Samm 2: Ajasammu arvutamine (loendur $T$)

Hetke Unixi ajatempel (sekundid, mis on möödunud alates 1. jaanuarist 1970 UTC) teisendatakse täisarvuliseks loenduriks $T$:

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

Standardsete parameetrite puhul on $T_0 = 0$ ja sammu pikkus $X = 30$ sekundit. See täisarv $T$ kodeeritakse seejärel 8-baidiseks big-endian 64-bitiseks märgita täisarvuks.

3 Samm 3: HMAC-SHA1 räsi arvutamine

Kasutades salajase võtmena tooreid võtmebaite $K$, arvutatakse HMAC-SHA1 räsi üle 8-baidise big-endian ajapuhvri, andes tulemuseks 20-baidise (160-bitise) sõnumiautentimise räsi $HS$:

HS = HMAC-SHA-1(Key, T)
4 Samm 4: Dünaamiline kärpimine (Dynamic Truncation, DT)

20 toore baidi teisendamiseks täisarvuks määrab RFC 4226 dünaamilise kärpimise:

  • Nihke (Offset) eraldamine: Vaadeldakse viimast baiti $HS[19]$ ja maskitakse väärtusega 0x0F, et eraldada selle madalamad 4 bitti, saades nihke vahemikus 0 kuni 15: Offset = HS[19] & 0x0F;
  • 4 baidi väljavõtmine: Eraldatakse 4 järjestikust baiti alates nihkest: $P = HS[Offset \dots Offset+3]$;
  • Märgibiti maskimine: Kõige kõrgem bitt (MSB) maskitakse väärtusega 0x7F, et vältida märgiga täisarvu ületäitumise ebaselgust:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Samm 5: Modulo-vähendus $N$ numbrikohani

Lõpuks rakendatakse väärtusele BinaryCode modulo $10^6$ (1 000 000) standardsete 6-kohaliste koodide saamiseks. Tulemusele lisatakse vajadusel vasakule nullid, et tagada täpselt 6-kohaline väljundstring:

OTP = BinaryCode mod 10^6

03. Töötav kood 1: Python 3 standardteek

See täielik teostus kasutab Pythoni sisseehitatud mooduleid base64, struct, hmac ja hashlib ilma väliste pip-sõltuvusteta:

totp_rfc6238.py Python 3.8+ (null sõltuvust)
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: """ Genereeri RFC 6238 nõuetele vastav TOTP-kood. :param secret_base32: Jagatud salavõti Base32 vormingus (nt ‘JBSWY3DPEHPK3PXP’) :param timestamp: Unixi ajatempel sekundites. Vaikimisi time.time() :param step: Ajasammu pikkus sekundites (vaikimisi: 30) :param digits: Väljundkoodi numbrite arv (vaikimisi: 6) :return: Nullidega täidetud OTP sõne (nt ‘045812’) """ if timestamp is None: timestamp = int(time.time())

# 1. Normaliseeri Base32 sõne ja lisa vajadusel '=' polsterdus
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. Arvuta 64-bitine big-endian ajasammu loendur T
time_counter = timestamp // step
time_bytes = struct.pack(">Q", time_counter)  # 8 baiti, big-endian unsigned long long

# 3. Arvuta HMAC-SHA1 räsi (20 baiti)
hmac_digest = hmac.new(key_bytes, time_bytes, hashlib.sha1).digest()

# 4. Teosta dünaamiline kärpimine (Dynamic Truncation)
offset = hmac_digest[-1] & 0x0F  # 20. baidi madalamad 4 bitti
# Loeme 4 baiti nihkest ja eemaldame MSB märgibiti
binary_code = struct.unpack(">I", hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF

# 5. Modulo-arvutus ja nullidega täitmine
otp_code = binary_code % (10 ** digits)
return str(otp_code).zfill(digits)

if name == “main”: test_secret = “JBSWY3DPEHPK3PXP” print(f”Genereeritud TOTP-kood: {generate_totp(test_secret)}”)

04. Töötav kood 2: JavaScript ja Node.js Crypto

Puhas JavaScripti teostus Node.js serverikeskkondadele natiivse crypto.createHmac mooduliga:

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

// RFC 4648 Base32 dekodeerija puhtas JavaScriptis 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);

// Eraldame 8-baidise puhvri Big-Endian vormingus const buffer = Buffer.alloc(8); buffer.writeBigUInt64BE(BigInt(counter), 0);

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

// Dünaamiline kärpimine 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(“Arvutatud TOTP:”, generateTOTP(“JBSWY3DPEHPK3PXP”));

Kliendipoolne käivitamine brauseris W3C Web Crypto API abil:

Kaasaegsetele veebirakendustele, mis taotlevad 100% võrguühenduseta turvalisust (täpselt selline arhitektuur paneb tööle 2FA-Tooli), pakub W3C Web Crypto API asünkroonseid, riistvaraliselt kiirendatud krüptograafilisi primitiive otse brauseri liivakastis ilma väliste npm-pakettideta:

webcrypto_totp.js Brauseri 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. Impordi toores salavõti SubtleCrypto sisse const cryptoKey = await window.crypto.subtle.importKey( “raw”, keyBytes, { name: “HMAC”, hash: { name: “SHA-1” } }, false, [“sign”] );

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

// 3. Teosta krüptograafiline HMAC allkirjastamine const signature = await window.crypto.subtle.sign(“HMAC”, cryptoKey, buffer); const hmacResult = new Uint8Array(signature);

// 4. Dünaamiline kärpimine 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. Töötav kood 3: Töökindel Golang (Go)

Suure koormusega mikroteenused saavad otsest kasu Go lahingutes karastunud standardteegist:

totp.go Go 1.20+ standardteek
package main

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

// GenerateTOTP arvutab standardse RFC 6238 ajapõhise koodi func GenerateTOTP(secretBase32 string, timestamp int64, step int64, digits int) (string, error) { // 1. Puhasta ja dekodeeri Base32 algseeme 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. Kodeeri 64-bitine Big-Endian ajasammu loendur
counter := uint64(timestamp / step)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)

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

// 4. Dünaamiline kärpimine
offset := hash[len(hash)-1] & 0x0f
binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7fffffff

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

06. RFC 6238 Lisa B testvektorite kontrollimine

Standardile vastavuse tõendamiseks tuleb iga krüptograafilist teostust kontrollida IETF RFC 6238 Lisa B ametlike testvektorite alusel.

Ametlik testikomplekt kasutab ASCII salavõtit "12345678901234567890" (20 baiti), mis kodeeritakse Base32 kujul järgmiselt:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (hinnatud 8-kohalise väljundiga 30-sekundiliste sammudega):

Unixi ajatempel (Epoch) UTC kuupäev ja kellaaeg RFC ametlik oodatud kood Rakenduse tulemus
59 1970-01-01 00:00:59 94287082 ✓ LÄBITUD (Täpne vaste)
1111111109 2005-03-18 01:58:29 07081804 ✓ LÄBITUD (Täpne vaste)
1111111111 2005-03-18 01:58:31 14050471 ✓ LÄBITUD (Täpne vaste)
1234567890 2009-02-13 23:31:30 89005924 ✓ LÄBITUD (Täpne vaste)
2000000000 2033-05-18 03:33:20 69279037 ✓ LÄBITUD (Täpne vaste)

07. Tootmistaseme turvalisus: ajanihe, kordusrünnakud ja limiidid

TOTP-kinnituse juurutamisel ettevõtte kõrge käideldavusega autentimissüsteemides tuleb järgida kolme ranget turvanõuet:

Reegel 1 Kella nihke tolerantsiaken

Mobiilseadmete võimalike väikeste kellanihete tõttu peaksid serverid hindama akent [T - 1, T, T + 1] (kontrollides eelnevat 30 s, praegust 30 s ja järgmist 30 s). Ärge kunagi laiendage akent üle $\pm 1$ sammu, kuna iga täiendav samm laiendab kordusrünnaku (replay attack) ründepinda 30 sekundi võrra.

Reegel 2 Ühekordselt kasutatava koodi jõustamine (kordusrünnakute vastane kaitse)

Kood tuleb pärast edukat kasutamist viivitamatult kehtetuks tunnistada. Salvestage Last_Verified_Time_Step Redisesse või mälupõhisesse vahemällu. Kui klient üritab esitada sama koodi uuesti sama 30-sekundilise akna jooksul, lükake päring tagasi, et neutraliseerida vahendajarünnakud (man-in-the-middle).

Reegel 3 Toore jõu rünnakute piiramine ja konstantse ajaga võrdlus

Kuna 6-kohalisel koodil on vaid 1 000 000 kombinatsiooni, saab piiranguteta otspunkte toore jõuga (brute-force) kiiresti murda. Rakendage progresseeruvaid viivitusi ja konto lukustamist pärast 3 järjestikust ebaõnnestumist. Koodis teostage võrdlusi konstantse ajaga funktsioonide abil (nt crypto/subtle.ConstantTimeCompare Go-s või crypto.timingSafeEqual Node.js-is), et tõkestada külgkanali ajastusrünnakuid (timing attacks).

08. Korduma kippuvad küsimused (KKK)

1. küsimus: Kuna SHA-1 on krüptograafiliselt murtud, miks kasutab TOTP endiselt vaikimisi HMAC-SHA1 algoritmi? ▼

Toore SHA-1 vastu leitud teoreetilised kollisionirünnakud ei murra HMAC-konstruktsiooni (RFC 2104). HMAC-is on aluseks olev räsifunktsioon ümbritsetud sisemiste ja väliste salavõtmetega, mis neutraliseerib kollisionipõhised rünnakud. Tänaseni püsib HMAC-SHA1 2FA jaoks täiesti turvalisena. Lisaks toetab RFC 6238 täielikult ka algoritme HMAC-SHA256 ja HMAC-SHA512, kui nõutakse rangemat turvataset.

2. küsimus: Kas ajavööndid või suveaeg mõjutavad TOTP sünkroonimist? ▼

Ei mõjuta. Unixi ajatempel on määratletud möödunud sekunditena alates 1970-01-01 00:00:00 UTC. See on rangelt sõltumatu ajavöönditest, liigsekunditest ja suveajale üleminekutest. Igal konkreetsel ajahetkel on serveril Tokyos ja nutitelefonil Californias täpselt sama täisarvuline ajatempel.

3. küsimus: Kuidas erineb Steam Guardi 5-täheline autentimine RFC 6238 standardist? ▼

Steam Guard on RFC 6238-l põhinev patenteeritud lahendus. See kasutab täpselt sama 30-sekundilist ajasammu ja HMAC-SHA1 arvutust, kuid viimases modulo-vähenduse faasis teisendab see saadud täisarvu kohandatud 26-märgilise alfanumbreelise massiivi abil (23456789BCDFGHJKMNPQRTVWXY), et genereerida 5-kohaline string.

RFC 6238 standardile vastav · W3C Web Crypto API kliendipoolne arvutus

Koge 100% kliendipoolset TOTP-koodide genereerimist reaalajas

2FA-Tool rakendab täpselt seda krüptograafilist ahelat otse sinu veebibrauseris, kasutades W3C Web Crypto API-t. Puudub serverisse saatmine, puudub koodide salvestamine, kohene valideerimine.