💻 Kriptografi Gunaan · Analisis Mendalam Kejuruteraan

Cara TOTP Berfungsi: Melaksanakan Algoritma RFC 6238 dalam Python, JavaScript dan Go

2F Makmal Penyelidikan Keselamatan 2FA-Tool · Dikemas kini Oktober 2026 · 16 min bacaan (2,700+ patah perkataan)
💡 Abstrak Seni Bina & Manifesto Kejuruteraan

Antara protokol kriptografi asas dalam keselamatan web moden, RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) menonjol kerana kesederhanaan matematiknya. Ia tidak memerlukan jabat tangan rangkaian interaktif, tiada infrastruktur kunci awam (PKI) berpusat, dan tiada pusingan pergi balik ke awan. Hanya dengan kunci rahsia Base32 yang dikongsi dan perarakan masa Unix fizikal yang berterusan ke hadapan, klien dan pelayan mengira token pengesahan 6 digit sementara yang serupa setiap 30 saat dengan kepastian matematik 100%.

Dalam panduan komprehensif ini, kami memintas pustaka pihak ketiga kotak hitam (seperti pyotp atau otplib) untuk membina saluran paip kriptografi RFC 6238 yang lengkap dari awal dalam Python, JavaScript, dan Go, mengesahkan setiap pelaksanaan terhadap vektor ujian rasmi IETF.

01. Keanggunan Pengesahan Dipacu Masa

Untuk memahami TOTP, kita mesti terlebih dahulu meneliti pendahulunya: RFC 4226 HOTP (HMAC-Based One-Time Password).

Dalam HOTP, klien dan pelayan berkongsi pembilang peristiwa $C$. Setiap kali pengguna menjana token, pembilang bertambah ($C = C + 1$), dan token diperoleh daripada $\text{HMAC-SHA-1}(K, C)$. Walaupun kukuh dari segi matematik, HOTP mengalami kelemahan operasi yang ketara: penyahsegerakan pembilang (counter desynchronization). Jika pengguna secara tidak sengaja menekan butang pada peranti perkakasan mereka sebanyak lima kali semasa berada di dalam poket, pembilang klien mencapai $C=6$ manakala pelayan pengesahan kekal pada $C=1$, mengakibatkan penolakan log masuk berterusan.

⏱️ Kejayaan đột phá RFC 6238: Menggantikan Pembilang Peristiwa dengan Waktu Epoch

Diterbitkan pada Mei 2011 oleh N. M'Raihi, S. Machani, M. Pei, dan J. Rydell, RFC 6238 menyelesaikan masalah penyahsegerakan pembilang dengan menggantikan integer yang bertambah dengan waktu Unix epoch piawai. Kerana masa fizikal bergerak ke hadapan secara seragam di seluruh dunia, kedua-dua klien dan pelayan memajukan pembilang dalaman mereka secara serentak tanpa menghantar satu paket rangkaian pun.

02. 5 Langkah Matematik Kriptografi TOTP

Mengubah benih Base32 16 aksara (cth., JBSWY3DPEHPK3PXP) menjadi kod bergolek 6 digit (cth., 284915) melibatkan lima fasa kriptografi yang tepat:

1 Langkah 1: Penyahkodan Kunci Rahsia Base32

Kunci pendaftaran dikodkan dalam format RFC 4648 Base32 menggunakan abjad A-Z dan 2-7 (menyingkirkan aksara yang mengelirukan seperti 0, 1, 8, 9). Algoritma ini terlebih dahulu menormalkan rentetan ini, menanggalkan ruang putih dan menggunakan pengisian padding (=) supaya panjangnya merupakan gandaan 8, sebelum menyahkodnya menjadi bait kunci binari mentah $K$.

2 Langkah 2: Pengiraan Langkah Masa (Pembilang $T$)

Cap waktu Unix semasa (saat yang berlalu sejak 1 Jan 1970 UTC) dipetakan kepada pembilang integer $T$:

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

Dengan parameter standard $T_0 = 0$ dan tempoh langkah $X = 30$ saat. Integer $T$ ini kemudiannya dikodkan sebagai integer tanpa tanda 64-bit big-endian 8-bait.

3 Langkah 3: Pengiraan Cincangan HMAC-SHA1

Menggunakan bait kunci mentah $K$ sebagai kunci rahsia, kami mengira ringkasan HMAC-SHA1 ke atas penimbal masa big-endian 8-bait, menghasilkan cincangan pengesahan mesej 20-bait (160-bit) $HS$:

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

Untuk menukar 20 bait mentah kepada nilai integer, RFC 4226 menetapkan Pemotongan Dinamik:

  • Ekstrak Ofset: Periksa bait terakhir $HS[19]$ dan topengkan dengan 0x0F untuk mengekstrak 4 bit rendahnya, menghasilkan nilai ofset antara 0 dan 15: Offset = HS[19] & 0x0F;
  • Hiris 4 Bait: Ekstrak 4 bait berturutan bermula pada Ofset: $P = HS[Offset \dots Offset+3]$;
  • Topeng Bit Tanda: Topengkan bit paling bererti (MSB) dengan 0x7F untuk mengelakkan kekaburan limpahan integer bertanda:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Langkah 5: Pengurangan Modulo kepada $N$ Digit

Akhir sekali, kurangkan BinaryCode modulo $10^6$ (1,000,000) untuk token standard 6 digit. Tambahkan sifar pendahulu pada hasil untuk memastikan rentetan output 6 aksara yang konsisten:

OTP = BinaryCode mod 10^6

03. Kod Sedia Jalan 1: Pustaka Standard Python 3

Pelaksanaan lengkap ini menggunakan modul terbina dalam Python base64, struct, hmac, dan hashlib tanpa memerlukan sebarang pakej pip luaran:

totp_rfc6238.py Python 3.8+ (Sifar Kebergantungan Luaran)
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: """ Menjana token TOTP yang mematuhi piawaian RFC 6238. :param secret_base32: Kunci rahsia kongsi dalam format Base32 (cth., ‘JBSWY3DPEHPK3PXP’) :param timestamp: Cap waktu Unix epoch dalam saat. Lalai kepada time.time() :param step: Tetingkap langkah masa dalam saat (lalai: 30) :param digits: Bilangan digit output (lalai: 6) :return: Rentetan OTP dengan sifar pendahulu (cth., ‘045812’) """ if timestamp is None: timestamp = int(time.time())

# 1. Normalkan rentetan Base32 dan tambahkan aksara padding '='
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. Kira pembilang langkah masa 64-bit big-endian T
time_counter = timestamp // step
time_bytes = struct.pack(">Q", time_counter)  # 8 bait big-endian unsigned long long

# 3. Kira ringkasan HMAC-SHA1 (20 bait)
hmac_digest = hmac.new(key_bytes, time_bytes, hashlib.sha1).digest()

# 4. Laksanakan Pemotongan Dinamik (Dynamic Truncation)
offset = hmac_digest[-1] & 0x0F  # 4 bit rendah bagi bait ke-20
# Bongkar 4 bait pada ofset dan kosongkan bit tanda MSB
binary_code = struct.unpack(">I", hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF

# 5. Aritmetik modulo dan pemformatan pengisian sifar
otp_code = binary_code % (10 ** digits)
return str(otp_code).zfill(digits)

if name == “main”: test_secret = “JBSWY3DPEHPK3PXP” print(f”Kod TOTP Dijana: {generate_totp(test_secret)}”)

04. Kod Sedia Jalan 2: Kripto JavaScript & Node.js

Pelaksanaan JavaScript tulen untuk persekitaran bahagian belakang Node.js menggunakan crypto.createHmac bawaan:

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

// Penyahkod Base32 RFC 4648 dalam JavaScript Tulen 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);

// Peruntukkan penimbal 8-bait dalam format Big-Endian const buffer = Buffer.alloc(8); buffer.writeBigUInt64BE(BigInt(counter), 0);

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

// Pemotongan Dinamik (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(“TOTP Dikira:”, generateTOTP(“JBSWY3DPEHPK3PXP”));

Pelaksanaan Pelayar Bahagian Klien melalui W3C Web Crypto API:

Bagi aplikasi web moden yang memerlukan keselamatan 100% luar talian (seni bina tepat yang menggerakkan 2FA-Tool), API Web Crypto W3C menyediakan primitif kriptografi tak segerak yang dipercepatkan oleh perkakasan secara terus dalam kotak pasir pelayar tanpa sebarang ikatan 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 kunci rahsia mentah ke dalam SubtleCrypto const cryptoKey = await window.crypto.subtle.importKey( “raw”, keyBytes, { name: “HMAC”, hash: { name: “SHA-1” } }, false, [“sign”] );

// 2. Kodkan penimbal pembilang 8-bait Big-Endian const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setBigUint64(0, BigInt(counter), false); // false = Big-Endian

// 3. Laksanakan tandatangan kriptografi HMAC const signature = await window.crypto.subtle.sign(“HMAC”, cryptoKey, buffer); const hmacResult = new Uint8Array(signature);

// 4. Pemotongan Dinamik (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. Kod Sedia Jalan 3: Golang (Go) Pengeluaran

Perkhidmatan mikro pemprosesan tinggi mendapat manfaat daripada pustaka standard Go yang terbukti mantap:

totp.go Pustaka Standard Go 1.20+
package main

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

// GenerateTOTP mengira token berasaskan masa piawai RFC 6238 func GenerateTOTP(secretBase32 string, timestamp int64, step int64, digits int) (string, error) { // 1. Bersihkan benih Base32 dan nyahkod 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. Kodkan pembilang langkah masa 64-bit Big-Endian
counter := uint64(timestamp / step)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)

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

// 4. Pemotongan Dinamik (Dynamic Truncation)
offset := hash[len(hash)-1] & 0x0f
binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7fffffff

// 5. Aritmetik modulo
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 Dikira TOTP: %s\n”, code) }

06. Pengesahan Vektor Ujian Lampiran B RFC 6238

Untuk membuktikan pematuhan piawaian, setiap pelaksanaan kriptografi mesti disahkan terhadap vektor ujian rasmi yang diterbitkan dalam Lampiran B IETF RFC 6238.

Suite ujian rasmi menggunakan rahsia ASCII "12345678901234567890" (20 bait), yang dikodkan dalam Base32 sebagai:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (dinilai untuk output 8 digit dengan langkah 30 saat):

Cap Waktu Unix Epoch Tarikh & Masa UTC Kod Jangkaan Rasmi RFC Keputusan Pelaksanaan
59 1970-01-01 00:00:59 94287082 ✓ LULUS (Padanan Tepat)
1111111109 2005-03-18 01:58:29 07081804 ✓ LULUS (Padanan Tepat)
1111111111 2005-03-18 01:58:31 14050471 ✓ LULUS (Padanan Tepat)
1234567890 2009-02-13 23:31:30 89005924 ✓ LULUS (Padanan Tepat)
2000000000 2033-05-18 03:33:20 69279037 ✓ LULUS (Padanan Tepat)

07. Kejuruteraan Pengeluaran: Sisihan, Ulang Tayang & Had Kadar

Apabila menggunakan pengesahan TOTP dalam sistem pengesahan perusahaan berketersediaan tinggi, tiga keperluan keselamatan mesti dikuatkuasakan:

Peraturan 1 Tetingkap Toleransi Pelesapan Jam

Disebabkan oleh ketidaktepatan kecil jam mudah alih, pelayan harus menilai tetingkap [T - 1, T, T + 1] (menyemak 30s sebelumnya, 30s semasa, dan 30s seterusnya). Jangan sekali-kali melebarkan tetingkap melebihi $\pm 1$ langkah, kerana setiap langkah tambahan meluaskan permukaan serangan ulang tayang sebanyak 30 saat.

Peraturan 2 Penguatkuasaan Token Sekali Guna (Anti-Ulang Tayang)

Token mesti dibatalkan serta-merta selepas berjaya digunakan. Simpan Last_Verified_Time_Step dalam Redis atau cache dalam memori. Jika klien cuba menghantar token yang sama dalam tetingkap 30 saat yang sama, tolak permintaan tersebut untuk meneutralkan serangan ulang tayang perantara (man-in-the-middle).

Peraturan 3 Pengehadan Had Kadar Kekerasan Kasar & Perbandingan Masa Malar

Kerana kod 6 digit merangkumi hanya 1,000,000 gabungan, titik akhir yang tidak dihadkan boleh diserang secara kekerasan kasar dengan pantas. Kuat kuasakan kelewatan progresif dan kunci akaun selepas 3 percubaan gagal berturut-turut. Dalam kod, lakukan perbandingan menggunakan fungsi kesaksamaan masa malar (cth., crypto/subtle.ConstantTimeCompare dalam Go atau crypto.timingSafeEqual dalam Node.js) untuk mematahkan analisis pemasaan saluran sisi.

08. Soalan Lazim (FAQ)

S1: Memandangkan SHA-1 rosak secara kriptografi, mengapakah TOTP masih menggunakan HMAC-SHA1 secara lalai? ▼

Serangan perlanggaran teori terhadap SHA-1 mentah tidak mematahkan pembinaan HMAC (RFC 2104). Dalam HMAC, fungsi cincangan yang mendasari disarangkan dalam kunci rahsia dalaman dan luaran, yang meneutralkan serangan berasaskan perlanggaran. Sehingga kini, HMAC-SHA1 kekal selamat untuk 2FA. Tambahan pula, RFC 6238 menyokong sepenuhnya HMAC-SHA256 dan HMAC-SHA512 jika jaminan yang lebih tinggi diperlukan.

S2: Adakah zon masa atau Waktu Jimat Siang (Daylight Saving Time - DST) menjejaskan penyegerakan TOTP? ▼

Tidak. Waktu Unix epoch ditakrifkan sebagai saat yang berlalu sejak 1970-01-01 00:00:00 UTC. Ia bebas sepenuhnya daripada zon masa, saat lompat, dan peralihan waktu jimat siang. Pada bila-bila masa yang tepat, pelayan di Tokyo dan telefon pintar di California mempunyai cap waktu integer yang serupa.

S3: Bagaimanakah pengesahan 5 huruf Steam Guard berbeza daripada RFC 6238? ▼

Steam Guard ialah varian hak milik berdasarkan RFC 6238. Ia menggunakan langkah masa 30 saat dan pengiraan HMAC-SHA1 yang serupa, tetapi dalam fasa pengurangan akhir, ia memetakan integer yang diekstrak melalui tatasusunan aksara alfanumerik 26 aksara tersuai (23456789BCDFGHJKMNPQRTVWXY) untuk menghasilkan rentetan 5 aksara.

Patuh RFC 6238 · Pengiraan Bahagian Klien W3C Web Crypto API

Rasai Penjanaan Kod TOTP 100% Bahagian Klien Secara Langsung

2FA-Tool melaksanakan saluran paip kriptografi yang tepat ini terus di dalam pelayar anda menggunakan API Web Crypto W3C. Sifar penghantaran ke pelayan, sifar penyimpanan token, pengesahan serta-merta.