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.
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:
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$.
Ang kasalukuyang Unix timestamp (mga segundong lumipas mula noong Jan 1, 1970 UTC) ay iminamapa sa integer counter $T$:
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.
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$:
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
0x0Fupang 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
0x7Fupang maiwasan ang signed integer overflow ambiguity:
BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
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:
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:
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. Runnable Code 2: JavaScript at Node.js Crypto
Purong pagpapatupad sa JavaScript para sa backend Node.js runtimes gamit ang katutubong 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”));
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:
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:
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. 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:
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.
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.
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.
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.