💻 Applied Cryptography · Malalimang Pagsusuri sa Inhinyeriya

Paano Gumagana ang TOTP: Pagpapatupad ng RFC 6238 Algorithm sa Python, JavaScript at Go

2F Koponan ng Inhinyeriya sa Seguridad ng 2FA-Tool · Na-update noong Oktubre 2026 · 16 min na pagbasa (2,700+ salita)
💡 Abstrak ng Arkitektura at Manipesto ng Inhinyeriya

Sa mga pundasyong cryptographic protocol sa modernong web security, namumukod-tangi ang RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) dahil sa pagiging payak nito sa matematika. Hindi ito nangangailangan ng mga interactive na network handshake, walang sentralisadong public key infrastructure (PKI), at walang mga round-trip sa cloud. Gamit lamang ang isang shared na Base32 secret key at ang patuloy na pasulong na martsa ng pisikal na oras ng Unix, ang isang client at server ay nagkakalkula ng magkapareho at pansamantalang 6-digit authentication tokens bawat 30 segundo na may 100% katiyakan sa matematika.

Sa komprehensibong gabay na ito, nilalampasan natin ang mga black-box na third-party library (tulad ng pyotp o otplib) upang buuin ang kumpletong pipeline ng cryptography ng RFC 6238 mula sa simula sa Python, JavaScript, at Go, na nagpapatunay sa bawat pagpapatupad laban sa mga opisyal na test vector ng IETF.

01. Ang Kagandahan ng Time-Driven Authentication

Upang maunawaan ang TOTP, kailangan muna nating balikan ang nauna rito: RFC 4226 HOTP (HMAC-Based One-Time Password).

Sa HOTP, nagbabahagi ang client at server ng isang event counter $C$. Sa bawat oras na bumuo ang user ng token, tumataas ang counter ($C = C + 1$), at ang token ay kinukuha mula sa $\text{HMAC-SHA-1}(K, C)$. Bagama't matatag sa matematika, dumanas ang HOTP ng kahinaan sa operasyon: desynchronization ng counter. Kung aksidenteng napindot ng user ang button sa kanilang hardware dongle nang limang beses habang nasa bulsa, ang counter ng client ay umabot sa $C=6$ habang ang authentication server ay nanatili sa $C=1$, na nagreresulta sa patuloy na pagtanggi sa pag-login.

⏱️ Ang Tagumpay ng RFC 6238: Pagpapalit sa Mga Event Counter Gamit ang Epoch Time

Inilathala noong Mayo 2011 nina N. M'Raihi, S. Machani, M. Pei, at J. Rydell, nilutas ng RFC 6238 ang problema sa desync ng counter sa pamamagitan ng pagpapalit sa tumataas na integer ng standardized Unix epoch time. Dahil ang pisikal na oras ay sumusulong nang pantay-pantay sa buong mundo, ang client at server ay sabay na sumusulong sa kanilang panloob na mga counter nang hindi nagpapadala ng kahit isang network packet.

02. Ang 5 Cryptographic Math Steps ng TOTP

Ang pagbabago ng isang 16-character Base32 seed (hal., JBSWY3DPEHPK3PXP) patungo sa isang 6-digit rolling code (hal., 284915) ay nagsasangkot ng limang tumpak na yugto ng cryptography:

1 Hakbang 1: Pag-decode ng Base32 Secret Key

Ang mga exchange enrollment key ay naka-encode sa RFC 4648 Base32 gamit ang alpabetong A-Z at 2-7 (inaalis ang mga malalabong karakter tulad ng 0, 1, 8, 9). Unang ginagawang normal ng algorithm ang string na ito, inaalis ang whitespace at naglalapat ng padding (=) upang ang haba nito ay maging multiple ng 8, bago ito i-decode patungo sa raw binary key bytes $K$.

2 Hakbang 2: Pagkalkula ng Time-Step (Counter $T$)

Ang kasalukuyang Unix timestamp (mga segundong lumipas mula noong Jan 1, 1970 UTC) ay iminamapa sa integer counter $T$:

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

Gamit ang karaniwang mga parameter na $T_0 = 0$ at tagal ng step na $X = 30$ segundo. Ang integer na ito $T$ ay ini-encode pagkatapos bilang isang 8-byte big-endian 64-bit unsigned integer.

3 Hakbang 3: Pagkalkula ng HMAC-SHA1 Hash

Gamit ang raw key bytes $K$ bilang secret key, kinakalkula natin ang HMAC-SHA1 digest sa ibabaw ng 8-byte big-endian time buffer, na nagbibigay ng isang 20-byte (160-bit) message authentication hash $HS$:

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

Upang i-convert ang 20 raw bytes sa isang integer, tinutukoy ng RFC 4226 ang Dynamic Truncation:

  • I-extract ang Offset: Suriin ang huling byte $HS[19]$ at i-mask gamit ang 0x0F upang makuha ang lower 4 bits nito, na nagbibigay ng offset value sa pagitan ng 0 at 15: Offset = HS[19] & 0x0F;
  • I-slice ang 4 Bytes: Kumuha ng 4 magkakasunod na byte simula sa Offset: $P = HS[Offset \dots Offset+3]$;
  • I-mask ang Sign Bit: I-mask ang most significant bit (MSB) gamit ang 0x7F upang maiwasan ang signed integer overflow ambiguity:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Hakbang 5: Modulo Reduction sa $N$ Digits

Panghuli, bawasan ang BinaryCode modulo $10^6$ (1,000,000) para sa karaniwang 6-digit tokens. I-left-pad ang resulta ng mga zero upang matiyak ang pare-parehong 6-character output string:

OTP = BinaryCode mod 10^6

03. Runnable Code 1: Python 3 Standard Library

Ang kumpletong pagpapatupad na ito ay gumagamit ng mga built-in module ng Python na base64, struct, hmac, at hashlib nang walang kinakailangang mga pip package:

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. Runnable Code 2: JavaScript at Node.js Crypto

Purong pagpapatupad sa JavaScript para sa backend Node.js runtimes gamit ang katutubong 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”));

Pagpapatakbo sa Browser ng Client-Side sa Pamamagitan ng W3C Web Crypto API:

Para sa mga modernong web application na naghahanap ng 100% offline security (ang eksaktong arkitektura na nagpapagana sa 2FA-Tool), ang W3C Web Crypto API ay nagbibigay ng asynchronous at hardware-accelerated cryptographic primitives nang direkta sa sandbox ng browser nang walang mga third-party npm bundle:

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. Runnable Code 3: Production Golang (Go)

Ang mga high-throughput microservice ay nakikinabang mula sa subok na standard library ng Go:

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. Pagpapatunay sa RFC 6238 Appendix B Test Vectors

Upang mapatunayan ang pagsunod sa pamantayan, ang bawat pagpapatupad ng cryptography ay dapat ma-verify laban sa mga opisyal na test vector na inilathala sa IETF RFC 6238 Appendix B.

Ginagamit ng opisyal na test suite ang ASCII secret na "12345678901234567890" (20 bytes), na nag-e-encode sa Base32 bilang:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (sinuri para sa 8-digit outputs na may 30-segundong mga hakbang):

Unix Epoch Timestamp Petsa at Oras sa UTC Opisyal na Inaasahang Code ng RFC Resulta ng Pagpapatupad
59 1970-01-01 00:00:59 94287082 ✓ PASSED (Eksaktong Tugma)
1111111109 2005-03-18 01:58:29 07081804 ✓ PASSED (Eksaktong Tugma)
1111111111 2005-03-18 01:58:31 14050471 ✓ PASSED (Eksaktong Tugma)
1234567890 2009-02-13 23:31:30 89005924 ✓ PASSED (Eksaktong Tugma)
2000000000 2033-05-18 03:33:20 69279037 ✓ PASSED (Eksaktong Tugma)

07. Production Engineering: Skew, Replay at Rate Limits

Kapag nagde-deploy ng TOTP verification sa mga high-availability enterprise authentication system, tatlong kinakailangan sa seguridad ang dapat ipatupad:

Panuntunan 1 Clock Drift Tolerance Window

Dahil sa maliliit na kamalian sa orasan ng mobile, dapat suriin ng mga server ang window na [T - 1, T, T + 1] (pagsuri sa naunang 30s, kasalukuyang 30s, at susunod na 30s). Huwag kailanman palawakin ang window nang lampas sa $\pm 1$ step, dahil ang bawat karagdagang hakbang ay nagpapalawak sa attack surface ng replay attack nang 30 segundo.

Panuntunan 2 Pagpapatupad ng Single-Use Token (Anti-Replay)

Ang isang token ay dapat mapawalang-bisa kaagad sa matagumpay na paggamit. Iimbak ang Last_Verified_Time_Step sa Redis o isang in-memory cache. Kung susubukan ng isang client na isumite ang parehong token sa loob ng parehong 30-segundong window, tanggihan ang kahilingan upang ma-neutralize ang mga man-in-the-middle replay attack.

Panuntunan 3 Brute-Force Rate Limiting at Constant-Time Comparison

Dahil ang 6-digit codes ay sumasaklaw lamang sa 1,000,000 permutasyon, ang mga endpoint na walang throttle ay mabilis na maaaring ma-brute force. Magpatupad ng mga progresibong pagkaantala at pag-lockout ng account pagkatapos ng 3 magkakasunod na kabiguan. Sa code, magsagawa ng mga paghahambing gamit ang mga constant-time equality function (hal., crypto/subtle.ConstantTimeCompare sa Go o crypto.timingSafeEqual sa Node.js) upang hadlangan ang side-channel timing analysis.

08. Mga Madalas Itanong (FAQ)

T1: Dahil ang SHA-1 ay cryptographically broken, bakit naka-default pa rin ang TOTP sa HMAC-SHA1? ▼

Ang mga teoretikal na collision attack laban sa raw SHA-1 ay hindi sumisira sa konstruksiyon ng HMAC (RFC 2104). Sa HMAC, ang pinagbabatayang hash function ay nakapaloob sa loob ng panloob at panlabas na secret key, na nagpapawalang-bisa sa mga collision-based attack. Sa kasalukuyan, nananatiling ligtas ang HMAC-SHA1 para sa 2FA. Bukod dito, ganap na sinusuportahan ng RFC 6238 ang HMAC-SHA256 at HMAC-SHA512 kung kailangan ng mas mataas na antas ng seguridad.

T2: Nakaaapekto ba ang mga time zone o Daylight Saving Time (DST) sa pag-synchronize ng TOTP? ▼

Hindi. Ang Unix epoch time ay tinukoy bilang lumipas na mga segundo mula noong 1970-01-01 00:00:00 UTC. Ito ay mahigpit na independiyente sa mga time zone, leap seconds, at daylight saving shifts. Sa anumang eksaktong sandali sa oras, ang isang server sa Tokyo at isang smartphone sa California ay may parehong integer timestamp.

T3: Paano naiiba ang Steam Guard 5-letter authentication mula sa RFC 6238? ▼

Ang Steam Guard ay isang proprietary variant batay sa RFC 6238. Gumagamit ito ng eksaktong parehong 30-segundong time-step at pagkalkula ng HMAC-SHA1, ngunit sa huling yugto ng pagbawas (reduction phase), iminamapa nito ang na-extract na integer sa pamamagitan ng isang custom na 26-character alphanumeric character array (23456789BCDFGHJKMNPQRTVWXY) upang makagawa ng 5-character string.

Sumusunod sa RFC 6238 · Pagkalkula sa Client-Side Gamit ang W3C Web Crypto API

Maranasan ang 100% Client-Side TOTP Code Generation nang Live

Ipinapatupad ng 2FA-Tool ang eksaktong cryptographic pipeline na ito nang direkta sa loob ng iyong browser gamit ang W3C Web Crypto API. Walang pagpapadala sa server, walang pag-iimbak ng token, agarang pagpapatunay.