💻 Примењена криптографија · Инжењерски детаљни водич

Како ради TOTP: Имплементација RFC 6238 алгоритма у Python, JavaScript и Go језицима

2F 2FA-Tool Лабораторија за истраживање безбедности · Ажурирано у октобру 2026. · 16 мин читања (преко 2.700 речи)
💡 Архитектонски апстракт и инжењерски манифест

Међу темељним криптографским протоколима у савременој веб безбедности, RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) се истиче својом математичком једноставношћу. За његов рад нису потребна интерактивна мрежна руковања, централизована инфраструктура јавних кључева (PKI), нити комуникација са серверима у облаку. Уз само дељени Base32 тајни кључ и неумољиво физичко напредовање Unix времена, клијент и сервер израчунавају идентичне, краткотрајне 6-цифрене аутентификационе токене на сваких 30 секунди са 100% математичком извесношћу.

У овом свеобухватном водичу заобилазимо готове спољне библиотеке (као што су pyotp или otplib) како бисмо изградили комплетан криптографски цевовод према стандарду RFC 6238 од нуле у Python-у, JavaScript-у и Go-у, верификујући сваку имплементацију према званичним IETF тест векторима.

01. Елеганција аутентификације вођене временом

Да бисмо разумели TOTP, морамо се најпре осврнути на његовог претходника: RFC 4226 HOTP (HMAC-Based One-Time Password).

Код HOTP-а, клијент и сервер деле бројач догађаја $C$. Сваки пут када корисник генерише токен, бројач се увећава ($C = C + 1$), а токен се изводи из $\text{HMAC-SHA-1}(K, C)$. Иако математички исправан, HOTP је патио од оперативне слабости: десинхронизације бројача. Ако би корисник нехотице притиснуо дугме на хардверском токену пет пута у џепу, бројач клијента би достигао $C=6$, док би аутентификациони сервер остао на $C=1$, што би узроковало стално одбијање пријаве.

⏱️ Прекретница стандарда RFC 6238: Замена бројача догађаја временом Unix епохе

Објављен у мају 2011. од стране аутора N. M'Raihi, S. Machani, M. Pei и J. Rydell, RFC 6238 је решио проблем десинхронизације бројача тако што је инкрементални цео број заменио стандардизованим временом Unix епохе (Unix epoch time). Пошто физичко време тече униформно широм планете, и клијент и сервер истовремено померају своје унутрашње бројаче без слања иједног мрежног пакета.

02. 5 криптографских математичких корака TOTP-а

Трансформација Base32 семена од 16 карактера (нпр. JBSWY3DPEHPK3PXP) у шестоцифрени код који се мења (нпр. 284915) одвија се кроз пет прецизних криптографских фаза:

1 Корак 1: Декодирање Base32 тајног кључа

Кључеви за регистрацију се кодирају у RFC 4648 Base32 формату користећи алфабет A-Z и 2-7 (изостављајући двосмислене карактере као што су 0, 1, 8, 9). Алгоритам најпре нормализује овај стринг, уклањајући размаке и примењујући допуну (=) како би дужина била садржалац броја 8, пре него што га декодира у сирове бинарне бајтове кључа $K$.

2 Корак 2: Израчунавање временског корака (бројач $T$)

Тренутна Unix временска ознака (број секунди протеклих од 1. јануара 1970. UTC) пресликава се у целобројни бројач $T$:

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

Уз стандардне параметре $T_0 = 0$ и трајање корака $X = 30$ секунди. Овај цео број $T$ се затим кодира као 8-бајтни big-endian 64-битни неозначени цео број.

3 Корак 3: Израчунавање HMAC-SHA1 хеша

Користећи сирове бајтове кључа $K$ као тајни кључ, рачунамо HMAC-SHA1 дајџест над 8-бајтним big-endian временским бафером, што даје 20-бајтни (160-битни) хеш за аутентификацију поруке $HS$:

HS = HMAC-SHA-1(Key, T)
4 Корак 4: Динамичко одсецање (Dynamic Truncation - DT)

За претварање 20 сирових бајтова у цео број, RFC 4226 дефинише Dynamic Truncation:

  • Издвајање помака (Offset): Прегледа се последњи бајт $HS[19]$ и маскира са 0x0F ради издвајања нижа 4 бита, што даје вредност помака између 0 и 15: Offset = HS[19] & 0x0F;
  • Исецање 4 бајта: Извлаче се 4 узастопна бајта почевши од помака Offset: $P = HS[Offset \dots Offset+3]$;
  • Маскирање бита знака: Маскира се највиши бит (MSB) са 0x7F ради спречавања двосмислености прекорачења означеног целог броја:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Корак 5: Модуло редукција на $N$ цифара

На крају, скраћујемо BinaryCode по модулу $10^6$ (1.000.000) за стандардне 6-цифрене токене. Додају се водеће нуле с леве стране како би се обезбедио стандардни излазни стринг од 6 карактера:

OTP = BinaryCode mod 10^6

03. Извршни код 1: Python 3 стандардна библиотека

Ова комплетна имплементација користи уграђене Python модуле base64, struct, hmac и hashlib, без потребе за додатним pip пакетима:

totp_rfc6238.py Python 3.8+ (Нулта зависност)
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. Извршни код 2: JavaScript и Node.js Crypto

Имплементација у чистом JavaScript-у за Node.js бекенд окружење уз коришћење изворног crypto.createHmac:

totp_node.js Node.js изворни 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-ја:

За савремене веб апликације које захтевају 100% офлајн безбедност (архитектура која покреће 2FA-Tool), W3C Web Crypto API пружа асинхроне, хардверски убрзане криптографске примитиве директно унутар sandbox окружења прегледача, без спољних npm библиотечких пакета:

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. Извршни код 3: Продукциони Golang (Go)

Микросервисима са високим захтевима за пропусношћу савршено одговара стандардна библиотека језика Go, проверена у продукцији:

totp.go 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. Верификација тест вектора из RFC 6238 Додатка B

Да би се доказала усклађеност са стандардом, свака криптографска имплементација мора бити верификована према званичним тест векторима објављеним у IETF RFC 6238 Appendix B.

Званични тест користи ASCII тајну "12345678901234567890" (20 бајтова), која се у Base32 формату кодира као:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (вредновано за 8-цифрене излазе са кораком од 30 секунди):

Unix Epoch временска ознака UTC датум и време Очекивани званични RFC код Резултат имплементације
59 1970-01-01 00:00:59 94287082 ✓ УСПЕШНО (Тачно поклапање)
1111111109 2005-03-18 01:58:29 07081804 ✓ УСПЕШНО (Тачно поклапање)
1111111111 2005-03-18 01:58:31 14050471 ✓ УСПЕШНО (Тачно поклапање)
1234567890 2009-02-13 23:31:30 89005924 ✓ УСПЕШНО (Тачно поклапање)
2000000000 2033-05-18 03:33:20 69279037 ✓ УСПЕШНО (Тачно поклапање)

07. Продукцијско инжењерство: одступање сата, репродукција и лимити

Приликом примене TOTP верификације у пословним системима високе доступности, неопходно је применити три безбедносна правила:

Правило 1 Прозор толеранције одступања сата (Clock Drift)

Због мањих нетачности часовника на мобилним уређајима, сервери треба да проверавају прозор [T - 1, T, T + 1] (проверавајући претходних 30s, тренутних 30s и наредних 30s). Никада не ширите прозор преко $\pm 1$ корака, јер сваки додатни корак проширује површину за replay нападе за још 30 секунди.

Правило 2 Једнократна употреба токена (Заштита од репродукције / Anti-Replay)

Токен се мора поништити одмах након успешне употребе. Сачувајте Last_Verified_Time_Step у Redis-у или у меморијском кешу. Ако клијент покуша да пошаље исти токен унутар истог 30-секундног прозора, одбијте захтев како бисте неутралисали man-in-the-middle replay нападе.

Правило 3 Ограничавање стопе покушаја (Rate Limiting) и поређење у константном времену

Пошто шестоцифрени кодови обухватају само 1.000.000 пермутација, крајње тачке (endpoints) без ограничења брзине могу се брзо пробити brute-force нападом. Примените прогресивна кашњења и закључавање налога након 3 узастопна неуспешна покушаја. У коду вршите поређења користећи функције једнакости у константном времену (нпр. crypto/subtle.ConstantTimeCompare у Go-у или crypto.timingSafeEqual у Node.js-у) како бисте спречили нападе мерењем времена (side-channel timing attacks).

08. Често постављана питања (FAQ)

П1: Будући да је SHA-1 криптографски компромитован, зашто TOTP и даље подразумевано користи HMAC-SHA1? ▼

Теоријски напади колизијом на сирови SHA-1 алгоритам не нарушавају безбедност HMAC конструкције (RFC 2104). У HMAC-у, основна хеш функција је уграђена унутар унутрашњих и спољашњих тајних кључева, што неутралише нападе засноване на колизијама. До данас, HMAC-SHA1 остаје безбедан за 2FA. Поред тога, RFC 6238 у потпуности подржава HMAC-SHA256 и HMAC-SHA512 уколико је потребан виши ниво заштите.

П2: Да ли временске зоне или летње рачунање времена (DST) утичу на TOTP синхронизацију? ▼

Не. Unix епоха дефинисана је као број секунди протеклих од 1970-01-01 00:00:00 UTC. Она је потпуно агностична у односу на временске зоне, преступне секунде и летње померање сата. У било ком тачном тренутку, сервер у Токију и паметни телефон у Калифорнији имају потпуно исту целобројну временску ознаку.

П3: Како се аутентификација од 5 слова код Steam Guard-а разликује од RFC 6238? ▼

Steam Guard је власничка варијанта заснована на RFC 6238. Користи потпуно исти временски корак од 30 секунди и HMAC-SHA1 израчунавање, али у завршној фази редукције пресликава издвојени цео број кроз прилагођени низ од 26 алфанумеричких карактера (23456789BCDFGHJKMNPQRTVWXY) да би генерисао стринг од 5 карактера.

Усклађено са RFC 6238 · Рачунање на страни клијента уз W3C Web Crypto API

Испробајте 100% генерисање TOTP кодова на страни клијента уживо

2FA-Tool имплементира овај прецизан криптографски цевовод директно у вашем веб прегледачу користећи W3C Web Crypto API. Без преноса података на сервер, без чувања токена, тренутна верификација.