💻 Tillämpad kryptografi · Teknisk djupdykning
✓ Verifierad för 2026

Hur TOTP fungerar: Implementering av RFC 6238-algoritmen i Python, JavaScript & Go

2F 2FA-Tool Säkerhetsforskningslabb · Uppdaterad oktober 2026 · 16 min läsning (2 700+ ord)
💡 Arkitektoniskt sammandrag & tekniskt manifest

Bland grundläggande kryptografiska protokoll i modern webbsäkerhet utmärker sig RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) för sin matematiska enkelhet. Det kräver inga interaktiva nätverkshandskakningar, ingen centraliserad infrastruktur för publika nycklar (PKI) och inga anrop mot molnet. Med endast en delad hemlig Base32-nyckel och den konstanta framåtrörelsen hos fysisk Unix-tid beräknar klient och server identiska, efemära 6-siffriga autentiseringstoken var 30:e sekund med 100 % matematisk säkerhet.

I denna omfattande guide förbigår vi svarta lådan-tredjepartsbibliotek (såsom pyotp eller otplib) för att bygga hela den kryptografiska pipelinen för RFC 6238 från grunden i Python, JavaScript och Go, och verifierar varje implementering mot officiella IETF-testvektorer.

01. Elegansen i tidsstyrd autentisering

För att förstå TOTP måste vi först spåra dess föregångare: RFC 4226 HOTP (HMAC-Based One-Time Password).

I HOTP delar klienten och servern en händelseräknare $C$. Varje gång användaren genererar en token ökas räknaren ($C = C + 1$), och token härleds från $\text{HMAC-SHA-1}(K, C)$. Även om HOTP var matematiskt sunt led det av en operationell svaghet: desynkronisering av räknaren. Om en användare av misstag tryckte på knappen på sin hårdvarudosa fem gånger i fickan nådde klientens räknare $C=6$ medan autentiseringsservern stod kvar på $C=1$, vilket resulterade i ihållande inloggningsavvisningar.

⏱️ Genombrottet i RFC 6238: Ersätta händelseräknare med epoktid

Publicerad i maj 2011 av N. M'Raihi, S. Machani, M. Pei och J. Rydell, löste RFC 6238 problemet med räknardesynkronisering genom att ersätta det ökande heltalet med standardiserad Unix-epoktid. Eftersom fysisk tid rör sig enhetligt över hela jorden flyttar både klient och server fram sina interna räknare simultant utan att skicka ett enda nätverkspaket.

02. De 5 kryptografiska matematiska stegen i TOTP

Att transformera ett 16-teckens Base32-seed (t.ex. JBSWY3DPEHPK3PXP) till en 6-siffrig rullande kod (t.ex. 284915) sker i fem precisa kryptografiska faser:

1 Steg 1: Avkodning av hemlig Base32-nyckel

Registreringsnycklar kodas i RFC 4648 Base32 med alfabetet A-Z och 2-7 (där tvetydiga tecken som 0, 1, 8, 9 utelämnas). Algoritmen normaliserar först denna sträng genom att ta bort blanksteg och lägga till utfyllnad (=) så att dess längd är en multipel av 8, innan den avkodas till råa binära nyckelbytes $K$.

2 Steg 2: Beräkning av tidssteg (Räknare $T$)

Den aktuella Unix-tidsstämpeln (sekunder sedan 1 januari 1970 UTC) mappas till heltalsräknaren $T$:

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

Med standardparametrarna $T_0 = 0$ och steglängden $X = 30$ sekunder. Detta heltal $T$ kodas sedan som ett 8-byte big-endian 64-bitars osignerat heltal.

3 Steg 3: Beräkning av HMAC-SHA1-hash

Med råa nyckelbytes $K$ som hemlig nyckel beräknas HMAC-SHA1-sammandraget över den 8-byte stora big-endian tidsbufferten, vilket ger en 20-byte (160-bitars) meddelandeautentiseringshash $HS$:

HS = HMAC-SHA-1(Key, T)
4 Steg 4: Dynamisk trunkering (Dynamic Truncation, DT)

För att omvandla 20 råa bytes till ett heltal specificerar RFC 4226 dynamisk trunkering:

  • Extrahera offset: Inspektera den sista byten $HS[19]$ och maskera med 0x0F för att extrahera dess lägre 4 bitar, vilket ger ett offsetvärde mellan 0 och 15: Offset = HS[19] & 0x0F;
  • Dela 4 bytes: Extrahera 4 på varandra följande bytes med början vid Offset: $P = HS[Offset \dots Offset+3]$;
  • Maskera teckenbit: Maskera den mest signifikanta biten (MSB) med 0x7F för att förhindra tvetydighet vid heltalsspill för teckenförsedda tal:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Steg 5: Moduloreduktion till $N$ siffror

Slutligen reduceras BinaryCode modulo $10^6$ (1 000 000) för vanliga 6-siffriga koder. Fyll ut resultatet till vänster med nollor för att garantera en konsekvent 6-teckens utdatasträng:

OTP = BinaryCode mod 10^6

03. Körbar kod 1: Python 3 standardbibliotek

Denna fullständiga implementation använder Pythons inbyggda moduler base64, struct, hmac och hashlib helt utan externa pip-beroenden:

totp_rfc6238.py Python 3.8+ (Noll beroenden)
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: """ Generera en RFC 6238-kompatibel TOTP-kod. :param secret_base32: Delad hemlig nyckel i Base32-format (t.ex. ‘JBSWY3DPEHPK3PXP’) :param timestamp: Unix-epoktidsstämpel i sekunder. Standard är time.time() :param step: Tidsfönster i sekunder (standard: 30) :param digits: Antal utdatasiffror (standard: 6) :return: Nollutfylld OTP-sträng (t.ex. ‘045812’) """ if timestamp is None: timestamp = int(time.time())

# 1. Normalisera Base32-strängen och lägg till '=' som utfyllnad
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. Beräkna 64-bitars big-endian tidsstegsräknare T
time_counter = timestamp // step
time_bytes = struct.pack(">Q", time_counter)  # 8 bytes big-endian unsigned long long

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

# 4. Utför dynamisk trunkering
offset = hmac_digest[-1] & 0x0F  # Lägre 4 bitar av 20:e byten
# Packa upp 4 bytes vid offset och rensa MSB-teckenbiten
binary_code = struct.unpack(">I", hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF

# 5. Modulo-aritmetik och formatering med nollutfyllnad
otp_code = binary_code % (10 ** digits)
return str(otp_code).zfill(digits)

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

04. Körbar kod 2: JavaScript & Node.js Crypto

Ren JavaScript-implementering för Node.js på serversidan med inbyggd crypto.createHmac:

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

// RFC 4648 Base32-avkodare i ren 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);

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

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

// Dynamisk trunkering 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(“Beräknad TOTP:”, generateTOTP(“JBSWY3DPEHPK3PXP”));

Exekvering på klientsidan i webbläsaren via W3C Web Crypto API:

För moderna webbapplikationer som eftersträvar 100 % offlinesäkerhet (den exakta arkitektur som driver 2FA-Tool) tillhandahåller W3C Web Crypto API asynkrona, hårdvaruaccelererade kryptografiska primitiver direkt i webbläsarsandlådan utan externa npm-paket:

webcrypto_totp.js Webbläsare 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. Importera rå hemlig nyckel till SubtleCrypto const cryptoKey = await window.crypto.subtle.importKey( “raw”, keyBytes, { name: “HMAC”, hash: { name: “SHA-1” } }, false, [“sign”] );

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

// 3. Utför kryptografisk HMAC-signering const signature = await window.crypto.subtle.sign(“HMAC”, cryptoKey, buffer); const hmacResult = new Uint8Array(signature);

// 4. Dynamisk trunkering 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. Körbar kod 3: Produktion i Golang (Go)

Mikrotjänster med krav på hög genomströmning drar stor nytta av Go:s vältestade standardbibliotek:

totp.go Go 1.20+ Standardbibliotek
package main

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

// GenerateTOTP beräknar standardiserade RFC 6238-tidsbaserade koder func GenerateTOTP(secretBase32 string, timestamp int64, step int64, digits int) (string, error) { // 1. Rensa Base32-nyckeln och avkoda 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. Koda 64-bitars Big-Endian tidsstegsräknare
counter := uint64(timestamp / step)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)

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

// 4. Dynamisk trunkering
offset := hash[len(hash)-1] &amp; 0x0f
binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) &amp; 0x7fffffff

// 5. Modulo-aritmetik
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 beräknad TOTP: %s\n”, code) }

06. Verifiering mot RFC 6238 Appendix B testvektorer

För att säkerställa standardefterlevnad måste varje kryptografisk implementering verifieras mot de officiella testvektorer som publicerats i IETF RFC 6238 Appendix B.

Den officiella testsviten använder ASCII-hemligheten "12345678901234567890" (20 bytes), som kodas i Base32 som:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (utvärderad för 8-siffriga utdata med 30-sekunders tidsfönster):

Unix-epoktidsstämpel UTC-datum & tid Officiell förväntad kod i RFC Implementationsresultat
59 1970-01-01 00:00:59 94287082 ✓ GODKÄND (Exakt matchning)
1111111109 2005-03-18 01:58:29 07081804 ✓ GODKÄND (Exakt matchning)
1111111111 2005-03-18 01:58:31 14050471 ✓ GODKÄND (Exakt matchning)
1234567890 2009-02-13 23:31:30 89005924 ✓ GODKÄND (Exakt matchning)
2000000000 2033-05-18 03:33:20 69279037 ✓ GODKÄND (Exakt matchning)

07. Produktionsteknik: Tidsavvikelse, replay-skydd och hastighetsbegränsning

Vid distribution av TOTP-verifiering i storskaliga autentiseringssystem med höga tillgänglighetskrav måste tre kritiska säkerhetskrav uppfyllas:

Regel 1 Toleransfönster för klockdrift

På grund av mindre klockavvikelser i mobila enheter bör servrar utvärdera fönstret [T - 1, T, T + 1] (kontrollera föregående 30 s, aktuell 30 s och nästa 30 s). Bredda aldrig fönstret mer än $\pm 1$ steg, eftersom varje ytterligare steg ökar attackytan för replay-attacker med 30 sekunder.

Regel 2 Tvingande engångsanvändning av token (Anti-Replay)

En token måste ogiltigförklaras omedelbart efter framgångsrik användning. Spara Last_Verified_Time_Step i Redis eller ett minnescache. Om en klient försöker skicka samma token inom samma 30-sekundersfönster ska förfrågan avvisas för att neutralisera man-in-the-middle replay-attacker.

Regel 3 Hastighetsbegränsning mot brute force & konstanttidsjämförelse

Eftersom 6-siffriga koder endast har 1 000 000 kombinationer kan oskyddade ändpunkter snabbt knäckas via brute-force. Tillämpa progressiva fördröjningar och kontospärrar efter 3 misslyckade försök i rad. Utför jämförelser i koden med hjälp av funktioner med konstant exekveringstid (t.ex. crypto/subtle.ConstantTimeCompare i Go eller crypto.timingSafeEqual i Node.js) för att omintetgöra timingattacker via sidokanaler.

08. Vanliga frågor och svar (FAQ)

F1: Eftersom SHA-1 är kryptografiskt brutet, varför använder TOTP fortfarande HMAC-SHA1 som standard? ▼

De teoretiska kollisionsattackerna mot ren SHA-1 bryter inte HMAC-konstruktionen (RFC 2104). I HMAC kapslas den underliggande hashfunktionen in med interna och externa hemliga nycklar, vilket neutraliserar kollisionsbaserade attacker. Fram till idag förblir HMAC-SHA1 säkert för 2FA. Dessutom har RFC 6238 fullt stöd för HMAC-SHA256 och HMAC-SHA512 om högre säkerhetsmarginaler efterfrågas.

F2: Påverkar tidszoner eller sommartid (DST) TOTP-synkroniseringen? ▼

Nej. Unix-epoktid definieras som antalet förflutna sekunder sedan 1970-01-01 00:00:00 UTC. Den är strikt oberoende av tidszoner, skottsekunder och omställningar till sommartid. Vid varje exakt tidpunkt har en server i Tokyo och en smarttelefon i Stockholm exakt samma heltalsbaserade tidsstämpel.

F3: Hur skiljer sig Steam Guards 5-teckensautentisering från RFC 6238? ▼

Steam Guard är en proprietär variant baserad på RFC 6238. Den använder exakt samma 30-sekunders tidssteg och HMAC-SHA1-beräkning, men i den slutliga reduktionsfasen mappas det extraherade heltalet via en anpassad alfanumerisk teckenarray med 26 tecken (23456789BCDFGHJKMNPQRTVWXY) för att generera en sträng med 5 tecken.

RFC 6238-kompatibel · Klientsidesberäkning med W3C Web Crypto API

Upplev 100 % klientsidig generering av TOTP-koder live

2FA-Tool implementerar denna exakta kryptografiska pipeline direkt i din webbläsare med W3C Web Crypto API. Noll serveröverföringar, noll lagring av token, omedelbar validering.