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, 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:
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$.
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$:
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.
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$:
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)
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:
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:
import base64 import hashlib import hmac import struct import timedef 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:
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:
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:
package mainimport ( “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:
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.
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.
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.
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.