💻 Lietišķā kriptogrāfija · Padziļināta inženiertehniskā analīze

Kā darbojas TOTP: RFC 6238 algoritma ieviešana Python, JavaScript un Go

2F 2FA-Tool Drošības pētījumu laboratorija · Atjaunināts: 2026. gada oktobris · 16 min lasīšana (2 700+ vārdi)
💡 Arhitektūras kopsavilkums un inženierijas manifests

Starp fundamentālajiem kriptogrāfiskajiem protokoliem mūsdienu tīmekļa drošībā RFC 6238 (TOTP: Laika bāzēts vienreizējas paroles algoritms) izceļas ar savu matemātisko vienkāršību. Tam nav nepieciešama interaktīva tīkla savienojuma saskaņošana (handshake), centralizēta publiskās atslēgas infrastruktūra (PKI) vai mākoņpakalpojumu pieprasījumi. Izmantojot tikai kopīgotu Base32 slepeno atslēgu un nepārtraukto fiziskā Unix laika ritējumu, klients un serveris ik pēc 30 sekundēm ar 100% matemātisku precizitāti neatkarīgi aprēķina identiskus, īslaicīgus 6 ciparu autentifikācijas kodus.

Šajā visaptverošajā rokasgrāmatā mēs apietam trešo pušu bibliotēkas (piemēram, pyotp vai otplib), lai no pamatiem izveidotu pilnu RFC 6238 kriptogrāfisko konveijeru valodās Python, JavaScript un Go, pārbaudot katru implementāciju pret oficiālajiem IETF testa vektoriem.

01. Laika vadītas autentifikācijas elegance

Lai izprastu TOTP, vispirms jāaplūko tā priekštecis: RFC 4226 HOTP (HMAC bāzēta vienreizējā parole).

HOTP protokolā klients un serveris koplieto notikumu skaitītāju $C$. Katru reizi, kad lietotājs ģenerē kodu, skaitītājs tiek palielināts ($C = C + 1$), un kods tiek atvasināts no $\text{HMAC-SHA-1}(K, C)$. Lai gan matemātiski drošs, HOTP saskārās ar darbības vājo vietu: skaitītāja desinhronizāciju. Ja lietotājs kabatā nejauši piecas reizes nospieda aparatūras žetona pogu, klienta skaitītājs sasniedza $C=6$, kamēr autentifikācijas serveris palika pie $C=1$, izraisot pastāvīgas atteices pieteikšanās laikā.

⏱️ RFC 6238 izrāviens: Notikumu skaitītāju aizstāšana ar Unix laikmeta laiku

RFC 6238, ko 2011. gada maijā publicēja N. M'Raihi, S. Machani, M. Pei un J. Rydell, atrisināja skaitītāja desinhronizācijas problēmu, aizstājot augošo veselo skaitli ar standartizētu Unix laikmeta (epoch) laiku. Tā kā fiziskais laiks vienmērīgi rit visā pasaulē, gan klients, gan serveris vienlaicīgi palielina savus iekšējos skaitītājus, nenosūtot nevienu tīkla paketi.

02. TOTP 5 kriptogrāfiskie matemātikas soļi

16 rakstzīmju Base32 sēklas (piemēram, JBSWY3DPEHPK3PXP) pārveidošana mainīgā 6 ciparu kodā (piemēram, 284915) ietver piecus precīzus kriptogrāfiskos posmus:

1 1. solis: Base32 slepenās atslēgas dekodēšana

Reģistrācijas atslēgas tiek kodētas RFC 4648 Base32 formātā, izmantojot alfabētu A-Z un 2-7 (izslēdzot divdomīgas rakstzīmes, piemēram, 0, 1, 8, 9). Algoritms vispirms normalizē šo virkni, noņemot atstarpes un pievienojot aizpildījumu (=), lai tās garums būtu skaitļa 8 daudzkārtnis, un pēc tam dekodē to neapstrādātos binārajos atslēgas baitos $K$.

2 2. solis: Laika soļa aprēķins (skaitītājs $T$)

Pašreizējais Unix laika zīmogs (sekundes kopš 1970. gada 1. janvāra UTC) tiek kartēts vesela skaitļa skaitītājā $T$:

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

Ar standarta parametriem $T_0 = 0$ un soļa ilgumu $X = 30$ sekundes. Šis veselais skaitlis $T$ pēc tam tiek kodēts kā 8 baitu lielā gala (big-endian) 64 bitu bezsvara vesels skaitlis.

3 3. solis: HMAC-SHA1 jaukšanas aprēķins

Izmantojot neapstrādātos atslēgas baitus $K$ kā slepeno atslēgu, mēs aprēķinām HMAC-SHA1 kopsavilkumu pār 8 baitu lielo gala laika buferi, iegūstot 20 baitu (160 bitu) ziņojuma autentifikācijas jaucējkodu $HS$:

HS = HMAC-SHA-1(Key, T)
4 4. solis: Dinamiskā apcirpšana (DT)

Lai 20 neapstrādātus baitus pārvērstu veselā skaitlī, RFC 4226 nosaka dinamisko apcirpšanu:

  • Izvilkt nobīdi: Pārbaudiet pēdējo baitu $HS[19]$ un maskējiet ar 0x0F, lai iegūtu tā zemākos 4 bitus, kas dod nobīdes vērtību no 0 līdz 15: Offset = HS[19] & 0x0F;
  • Sagriezt 4 baitus: Izgrieziet 4 secīgus baitus, sākot no Offset: $P = HS[Offset \dots Offset+3]$;
  • Maskēt zīmes bitu: Maskējiet visnozīmīgāko bitu (MSB) ar 0x7F, lai novērstu zīmju pārplūdes neskaidrību:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 5. solis: Modulo redukcija līdz $N$ cipariem

Visbeidzot, samaziniet BinaryCode modulo $10^6$ (1 000 000) standarta 6 ciparu marķieriem. Papildiniet rezultātu no kreisās puses ar nullēm, lai nodrošinātu nemainīgu 6 rakstzīmju izvades virkni:

OTP = BinaryCode mod 10^6

03. Izpildāms kods 1: Python 3 standarta bibliotēka

Šī pilnīgā implementācija izmanto Python iebūvētos base64, struct, hmac un hashlib moduļus, neprasot nekādas ārējas pip pakotnes:

totp_rfc6238.py Python 3.8+ (Zero Dependencies)
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. Izpildāms kods 2: JavaScript un Node.js Crypto

Tīra JavaScript implementācija Node.js vidēm, izmantojot iebūvēto 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”));

W3C Web Crypto API izpilde pārlūkprogrammā:

Mūsdienīgām tīmekļa lietotnēm, kas prasa 100% bezsaistes drošību (tieši šī arhitektūra darbina 2FA-Tool), W3C Web Crypto API nodrošina asinhronas, aparatūras paātrinātas kriptogrāfijas funkcijas tieši pārlūkprogrammas smilškastē bez trešo pušu npm pakotnēm:

webcrypto_totp.js Browser 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. Izpildāms kods 3: Produkcijas Golang (Go)

Augstas veiktspējas mikropakalpojumi gūst labumu no Go pārbaudītās standarta bibliotēkas:

totp.go Go 1.20+ Standard Library
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. RFC 6238 B pielikuma testa vektoru pārbaude

Lai pierādītu atbilstību standartam, katra kriptogrāfiskā implementācija ir jāpārbauda pret oficiālajiem testa vektoriem, kas publicēti IETF RFC 6238 B pielikumā.

Oficiālajā testa komplektā tiek izmantots ASCII noslēpums "12345678901234567890" (20 baiti), kas Base32 formātā tiek kodēts kā:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (novērtēts 8 ciparu izvadei ar 30 sekunžu soļiem):

Unix laikmeta laika zīmogs UTC datums un laiks RFC oficiālais sagaidāmais kods Implementācijas rezultāts
59 1970-01-01 00:00:59 94287082 ✓ NOKĀRTOTS (Precīza sakritība)
1111111109 2005-03-18 01:58:29 07081804 ✓ NOKĀRTOTS (Precīza sakritība)
1111111111 2005-03-18 01:58:31 14050471 ✓ NOKĀRTOTS (Precīza sakritība)
1234567890 2009-02-13 23:31:30 89005924 ✓ NOKĀRTOTS (Precīza sakritība)
2000000000 2033-05-18 03:33:20 69279037 ✓ NOKĀRTOTS (Precīza sakritība)

07. Produkcijas inženierija: laika nobīde, atkārtošana un ierobežojumi

Ieviešot TOTP verifikāciju augstas pieejamības uzņēmuma autentifikācijas sistēmās, ir jāievēro trīs drošības prasības:

1. noteikums Pulksteņa nobīdes pielaides logs (Clock Drift Tolerance Window)

Mobilo ierīču pulksteņu nelielu nobīžu dēļ serveriem jānovērtē logs [T - 1, T, T + 1] (pārbaudot iepriekšējās 30 s, pašreizējās 30 s un nākamās 30 s). Nekad nepaplašiniet logu tālāk par $\pm 1$ soli, jo katrs papildu solis paplašina atkārtošanas uzbrukuma (replay attack) virsmu par 30 sekundēm.

2. noteikums Vienreizējas lietošanas marķiera prasība (aizsardzība pret atkārtošanu)

Pēc veiksmīgas verifikācijas kods nekavējoties jāpadara nederīgs. Saglabājiet Last_Verified_Time_Step atmiņas kešatmiņā vai Redis. Ja klients mēģina iesniegt to pašu kodu tajā pašā 30 sekunžu logā, noraidiet pieprasījumu, lai neitralizētu starpnieka (man-in-the-middle) atkārtošanas uzbrukumus.

3. noteikums Rupja spēka uzbrukumu ātruma ierobežošana un nemainīga laika salīdzināšana

Tā kā 6 ciparu kodi aptver tikai 1 000 000 permutāciju, neaizsargātus galapunktus var ātri uzlauzt ar rupja spēka metodi. Ieviesiet progresīvu aizkavi un konta bloķēšanu pēc 3 secīgām kļūmēm. Kodā veiciet salīdzināšanu, izmantojot nemainīga laika vienlīdzības funkcijas (piemēram, crypto/subtle.ConstantTimeCompare valodā Go vai crypto.timingSafeEqual vidē Node.js), lai novērstu sānu kanālu laika analīzes uzbrukumus.

08. Bieži uzdotie jautājumi (BUJ)

1. jautājums: Tā kā SHA-1 ir kriptogrāfiski uzlauzts, kāpēc TOTP joprojām pēc noklusējuma izmanto HMAC-SHA1? ▼

Teorētiskie kolīziju uzbrukumi pret neapstrādātu SHA-1 nesagrauj HMAC konstrukciju (RFC 2104). HMAC mehānismā pamatā esošā jaukšanas funkcija ir iekapsulēta iekšējās un ārējās slepenajās atslēgās, kas neitralizē uz kolīzijām balstītus uzbrukumus. Līdz šim HMAC-SHA1 joprojām ir drošs 2FA vajadzībām. Turklāt RFC 6238 pilnībā atbalsta HMAC-SHA256 un HMAC-SHA512, ja nepieciešama augstāka drošības pakāpe.

2. jautājums: Vai laika joslas vai vasaras laiks (DST) ietekmē TOTP sinhronizāciju? ▼

Nē. Unix laikmeta laiks tiek definēts kā pagājušās sekundes kopš 1970-01-01 00:00:00 UTC. Tas ir pilnīgi neatkarīgs no laika joslām, lēciena sekundēm un vasaras laika pārejām. Jebkurā precīzā laika momentā serverim Tokijā un viedtālrunim Kalifornijā ir tieši viens un tas pats veselā skaitļa laika zīmogs.

3. jautājums: Kā Steam Guard 5 burtu autentifikācija atšķiras no RFC 6238? ▼

Steam Guard ir patentēts variants, kura pamatā ir RFC 6238. Tas izmanto tieši tādu pašu 30 sekunžu laika soli un HMAC-SHA1 aprēķinu, taču pēdējā samazināšanas fāzē tas piesaista iegūto veselo skaitli pielāgotam 26 rakstzīmju burtciparu masīvam (23456789BCDFGHJKMNPQRTVWXY), lai iegūtu 5 rakstzīmju virkni.

Saderīgs ar RFC 6238 · Klienta puses skaitļošana ar W3C Web Crypto API

Izmēģiniet 100% klienta puses TOTP koda ģeneratoru tiešraidē

2FA-Tool ievieš šo precīzo kriptogrāfisko konveijeru tieši jūsu pārlūkprogrammā, izmantojot W3C Web Crypto API. Nulle servera pārsūtīšanas, nulle marķieru glabāšanas, tūlītēja validācija.