💻 Kriptografi Terapan · Analisis Rekayasa Mendalam

Cara Kerja TOTP: Mengimplementasikan Algoritma RFC 6238 dalam Python, JavaScript & Go

2F Laboratorium Riset Keamanan 2FA-Tool · Diperbarui Oktober 2026 · 16 menit membaca (2.700+ kata)
💡 Abstrak Arsitektur & Manifes Rekayasa

Di antara protokol kriptografi fundamental dalam keamanan web modern, RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) menonjol karena kesederhanaan matematisnya. Protokol ini tidak memerlukan jabat tangan (handshake) jaringan interaktif, tanpa infrastruktur kunci publik (PKI) terpusat, dan tanpa komunikasi bolak-balik ke cloud. Hanya dengan kunci rahasia Base32 bersama dan pergerakan maju waktu fisik Unix yang konstan, klien dan server menghitung token autentikasi 6 digit fana yang identik setiap 30 detik dengan kepastian matematis 100%.

Dalam panduan komprehensif ini, kita melewati pustaka pihak ketiga kotak hitam (seperti pyotp atau otplib) untuk membangun seluruh alur kerja kriptografi RFC 6238 dari awal menggunakan Python, JavaScript, dan Go, serta memverifikasi setiap implementasi terhadap vektor uji resmi IETF.

01. Keanggunan Autentikasi Berbasis Waktu

Untuk memahami TOTP, pertama-tama kita harus menelusuri pendahulunya: RFC 4226 HOTP (HMAC-Based One-Time Password).

Pada HOTP, klien dan server berbagi pencacah peristiwa (counter) $C$. Setiap kali pengguna menghasilkan token, pencacah bertambah ($C = C + 1$), dan token diturunkan dari $\text{HMAC-SHA-1}(K, C)$. Meskipun secara matematis kokoh, HOTP memiliki kelemahan operasional: desinkronisasi pencacah. Jika pengguna tidak sengaja menekan tombol pada dongle perangkat keras mereka sebanyak lima kali di dalam saku, pencacah klien mencapai $C=6$ sedangkan server autentikasi tetap pada $C=1$, yang mengakibatkan penolakan login secara terus-menerus.

⏱️ Terobosan RFC 6238: Menggantikan Pencacah Peristiwa dengan Waktu Epoch

Diterbitkan pada Mei 2011 oleh N. M'Raihi, S. Machani, M. Pei, dan J. Rydell, RFC 6238 memecahkan masalah desinkronisasi pencacah dengan mengganti bilangan bulat yang bertambah dengan waktu epoch Unix standar. Karena waktu fisik berjalan seragam di seluruh dunia, baik klien maupun server memajukan pencacah internal mereka secara bersamaan tanpa mengirimkan satu pun paket jaringan.

02. 5 Langkah Matematika Kriptografi TOTP

Mengubah seed Base32 16 karakter (misalnya, JBSWY3DPEHPK3PXP) menjadi kode bergulir 6 digit (misalnya, 284915) melibatkan lima fase kriptografi yang presisi:

1 Langkah 1: Penguraian Kunci Rahasia Base32 (Base32 Secret Key Decoding)

Kunci pendaftaran akun dienkode dalam RFC 4648 Base32 menggunakan alfabet A-Z dan 2-7 (mengabaikan karakter ambigu seperti 0, 1, 8, 9). Algoritma ini terlebih dahulu menormalisasi string ini, menghapus spasi dan menambahkan padding (=) sehingga panjangnya merupakan kelipatan 8, sebelum menguraikannya menjadi byte kunci biner mentah $K$.

2 Langkah 2: Perhitungan Langkah Waktu (Pencacah $T$)

Timestamp Unix saat ini (detik yang telah berlalu sejak 1 Januari 1970 UTC) dipetakan ke pencacah integer $T$:

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

Dengan parameter standar $T_0 = 0$ dan durasi langkah $X = 30$ detik. Integer $T$ ini kemudian dienkode sebagai unsigned integer 64-bit big-endian sepanjang 8 byte.

3 Langkah 3: Perhitungan Hash HMAC-SHA1

Menggunakan byte kunci mentah $K$ sebagai kunci rahasia, kita menghitung digest HMAC-SHA1 pada buffer waktu big-endian 8 byte, menghasilkan hash autentikasi pesan $HS$ sepanjang 20 byte (160 bit):

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

Untuk mengonversi 20 byte mentah menjadi bilangan bulat, RFC 4226 menetapkan Pemotongan Dinamis (Dynamic Truncation):

  • Ekstrak Offset: Periksa byte terakhir $HS[19]$ dan lakukan operasi masking dengan 0x0F untuk mengekstrak 4 bit terendahnya, menghasilkan nilai offset antara 0 dan 15: Offset = HS[19] & 0x0F;
  • Ambil Irisan 4 Byte: Ekstrak 4 byte berurutan mulai dari Offset: $P = HS[Offset \dots Offset+3]$;
  • Masking Bit Tanda (Sign Bit): Masking bit paling signifikan (MSB) dengan 0x7F untuk mencegah ambiguitas overflow bilangan bulat bertanda (signed integer):
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Langkah 5: Reduksi Modulo ke $N$ Digit

Terakhir, reduksi BinaryCode dengan modulo $10^6$ (1.000.000) untuk token standar 6 digit. Tambahkan padding nol di sebelah kiri untuk memastikan string output selalu konsisten 6 karakter:

OTP = BinaryCode mod 10^6

03. Kode Siap Pakai 1: Python 3 Standard Library

Implementasi lengkap ini menggunakan modul bawaan Python base64, struct, hmac, dan hashlib tanpa memerlukan paket pip pihak ketiga apa pun:

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. Kode Siap Pakai 2: JavaScript & Node.js Crypto

Implementasi JavaScript murni untuk lingkungan backend Node.js menggunakan crypto.createHmac bawaan:

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”));

Eksekusi Sisi Klien di Browser melalui W3C Web Crypto API:

Untuk aplikasi web modern yang membutuhkan keamanan 100% offline (arsitektur persis yang menggerakkan 2FA-Tool), W3C Web Crypto API menyediakan primitif kriptografi asinkron yang diakselerasi perangkat keras langsung di dalam sandbox browser tanpa bundel npm pihak ketiga:

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. Kode Siap Pakai 3: Golang (Go) Siap Produksi

Layanan mikro dengan throughput tinggi memanfaatkan pustaka standar Go yang telah teruji:

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. Verifikasi Vektor Uji RFC 6238 Lampiran B

Untuk membuktikan kepatuhan terhadap standar, setiap implementasi kriptografi harus diverifikasi terhadap vektor uji resmi yang diterbitkan dalam IETF RFC 6238 Lampiran B.

Rangkaian pengujian resmi menggunakan rahasia ASCII "12345678901234567890" (20 byte), yang dienkode dalam Base32 sebagai:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (dievaluasi untuk output 8 digit dengan langkah 30 detik):

Timestamp Unix Epoch Tanggal & Waktu UTC Kode Resmi yang Diharapkan RFC Hasil Implementasi
59 1970-01-01 00:00:59 94287082 ✓ LULUS (Kecocokan Persis)
1111111109 2005-03-18 01:58:29 07081804 ✓ LULUS (Kecocokan Persis)
1111111111 2005-03-18 01:58:31 14050471 ✓ LULUS (Kecocokan Persis)
1234567890 2009-02-13 23:31:30 89005924 ✓ LULUS (Kecocokan Persis)
2000000000 2033-05-18 03:33:20 69279037 ✓ LULUS (Kecocokan Persis)

07. Rekayasa Produksi: Skew, Replay & Batasan Laju (Rate Limits)

Saat menerapkan verifikasi TOTP dalam sistem autentikasi perusahaan dengan ketersediaan tinggi, tiga persyaratan keamanan harus ditegakkan:

Aturan 1 Jendela Toleransi Pergeseran Waktu (Clock Drift Tolerance Window)

Karena sedikit ketidakakuratan jam pada ponsel, server harus mengevaluasi jendela [T - 1, T, T + 1] (memeriksa 30 detik sebelumnya, 30 detik saat ini, dan 30 detik berikutnya). Jangan pernah memperlebar jendela melebihi $\pm 1$ langkah, karena setiap langkah tambahan memperluas permukaan serangan replay sebesar 30 detik.

Aturan 2 Penegakan Token Sekali Pakai (Anti-Replay)

Token harus segera dibatalkan setelah berhasil diverifikasi. Simpan Last_Verified_Time_Step di Redis atau cache dalam memori. Jika klien mencoba mengirimkan token yang sama dalam jendela 30 detik yang sama, tolak permintaan tersebut untuk menetralisir serangan replay man-in-the-middle.

Aturan 3 Pembatasan Laju Brute-Force & Perbandingan Waktu-Konstan

Karena kode 6 digit hanya mencakup 1.000.000 permutasi, endpoint tanpa pembatasan dapat ditebak secara brute-force dengan cepat. Terapkan penundaan progresif dan penguncian akun setelah 3 kali kegagalan berturut-turut. Dalam kode, lakukan perbandingan menggunakan fungsi kesetaraan waktu-konstan (misalnya, crypto/subtle.ConstantTimeCompare di Go atau crypto.timingSafeEqual di Node.js) untuk menggagalkan analisis timing saluran samping (side-channel).

08. Pertanyaan yang Sering Diajukan (FAQ)

Q1: Karena SHA-1 secara kriptografis telah rentan terhadap tabrakan, mengapa TOTP masih secara default menggunakan HMAC-SHA1? ▼

Serangan tabrakan (collision) teoretis terhadap SHA-1 murni tidak mematahkan konstruksi HMAC (RFC 2104). Dalam HMAC, fungsi hash yang mendasarinya disarangkan di dalam kunci rahasia internal dan eksternal, yang menetralisir serangan berbasis tabrakan. Hingga saat ini, HMAC-SHA1 tetap aman untuk 2FA. Selain itu, RFC 6238 sepenuhnya mendukung HMAC-SHA256 dan HMAC-SHA512 jika diperlukan tingkat jaminan keamanan yang lebih tinggi.

Q2: Apakah zona waktu atau Waktu Musim Panas (Daylight Saving Time - DST) memengaruhi sinkronisasi TOTP? ▼

Tidak. Waktu epoch Unix didefinisikan sebagai detik yang telah berlalu sejak 1970-01-01 00:00:00 UTC. Waktu ini sepenuhnya independen dari zona waktu, detik kabisat, dan perubahan waktu musim panas. Pada saat tertentu yang tepat, server di Tokyo dan ponsel pintar di California memiliki nilai timestamp integer yang sama persis.

Q3: Bagaimana perbedaan autentikasi 5 huruf Steam Guard dengan RFC 6238? ▼

Steam Guard adalah varian kepemilikan (proprietary) yang berbasis pada RFC 6238. Sistem ini menggunakan langkah waktu 30 detik dan perhitungan HMAC-SHA1 yang persis sama, namun pada fase reduksi akhir, sistem memetakan integer yang diekstrak melalui larik karakter alfanumerik 26 karakter kustom (23456789BCDFGHJKMNPQRTVWXY) untuk menghasilkan string 5 karakter.

Sesuai Standar RFC 6238 · Komputasi Sisi Klien W3C Web Crypto API

Rasakan Pembuatan Kode TOTP 100% Sisi Klien Secara Langsung

2FA-Tool mengimplementasikan alur kerja kriptografi ini secara langsung di dalam browser Anda menggunakan W3C Web Crypto API. Tanpa transmisi ke server, tanpa penyimpanan token, validasi instan.