2FA-Tool Logo
2FA-Tool.com Ασφαλής γεννήτρια κωδικών 2FA στο πρόγραμμα περιήγησης
💻 Εφαρμοσμένη Κρυπτογραφία · Ενδελεχής Μηχανική Ανάλυση

Πώς λειτουργεί το TOTP: Υλοποίηση του αλγορίθμου RFC 6238 σε Python, JavaScript & Go

2F Εργαστήριο Έρευνας Ασφαλείας 2FA-Tool · Ενημερώθηκε τον Οκτώβριο του 2026 · 16 λεπτά ανάγνωσης (2.700+ λέξεις)
💡 Αρχιτεκτονική σύνοψη & Μανιφέστο μηχανικής

Ανάμεσα στα θεμελιώδη κρυπτογραφικά πρωτόκολλα της σύγχρονης ασφάλειας ιστού, το RFC 6238 (TOTP: Time-Based One-Time Password Algorithm) ξεχωρίζει για τη μαθηματική του απλότητα. Δεν απαιτεί διαδραστικές χειραψίες δικτύου, καμία κεντρική υποδομή δημοσίου κλειδιού (PKI) και κανέναν κύκλο επικοινωνίας με το cloud. Με ένα μόνο κοινόχρηστο μυστικό κλειδί Base32 και τη συνεχή εμπρόσθια πορεία του φυσικού χρόνου Unix, ο client και ο διακομιστής υπολογίζουν πανομοιότυπους, εφήμερους 6-ψήφιους κωδικούς ταυτοποίησης κάθε 30 δευτερόλεπτα με 100% μαθηματική βεβαιότητα.

Σε αυτόν τον περιεκτικό οδηγό, παρακάμπτουμε τις βιβλιοθήκες τρίτων τύπου "μαύρου κουτιού" (όπως το pyotp ή το otplib) για να κατασκευάσουμε ολόκληρο τον κρυπτογραφικό αγωγό του RFC 6238 από το μηδέν σε Python, JavaScript και Go, επαληθεύοντας κάθε υλοποίηση έναντι των επίσημων διανυσμάτων δοκιμής του IETF.

01. Η κομψότητα του ελέγχου ταυτότητας βάσει χρόνου

Για να κατανοήσουμε το TOTP, πρέπει πρώτα να ανατρέξουμε στον προκάτοχό του: RFC 4226 HOTP (HMAC-Based One-Time Password).

Στο HOTP, ο client και ο διακομιστής μοιράζονται έναν μετρητή συμβάντων $C$. Κάθε φορά που ο χρήστης παράγει έναν κωδικό, ο μετρητής αυξάνεται κατά ένα ($C = C + 1$), και ο κωδικός προκύπτει από το $\text{HMAC-SHA-1}(K, C)$. Παρόλο που είναι μαθηματικά άρτιο, το HOTP υπέφερε από μια λειτουργική αδυναμία: τον αποσυγχρονισμό του μετρητή. Εάν ένας χρήστης πατούσε ακούσια το κουμπί στο φυσικό του dongle πέντε φορές στην τσέπη του, ο μετρητής του client έφτανε στο $C=6$ ενώ ο διακομιστής ελέγχου ταυτότητας παρέμενε στο $C=1$, οδηγώντας σε επίμονες απορρίψεις σύνδεσης.

⏱️ Η καινοτομία του RFC 6238: Αντικατάσταση των μετρητών συμβάντων με τον χρόνο Epoch

Δημοσιευμένο τον Μάιο του 2011 από τους N. M'Raihi, S. Machani, M. Pei και J. Rydell, το RFC 6238 έλυσε το πρόβλημα αποσυγχρονισμού του μετρητή αντικαθιστώντας τον αυξανόμενο ακέραιο με τον τυποποιημένο χρόνο εποχής Unix (Unix epoch time). Επειδή ο φυσικός χρόνος προχωρά ομοιόμορφα σε ολόκληρο τον πλανήτη, τόσο ο client όσο και ο διακομιστής προωθούν τους εσωτερικούς τους μετρητές ταυτόχρονα, χωρίς να στείλουν ούτε ένα πακέτο δικτύου.

02. Τα 5 κρυπτογραφικά μαθηματικά βήματα του TOTP

Η μετατροπή ενός αρχικού μυστικού κλειδιού Base32 16 χαρακτήρων (π.χ. JBSWY3DPEHPK3PXP) σε έναν κυλιόμενο κωδικό 6 ψηφίων (π.χ. 284915) περιλαμβάνει πέντε ακριβείς κρυπτογραφικές φάσεις:

1 Βήμα 1: Αποκωδικοποίηση μυστικού κλειδιού Base32

Τα κλειδιά εγγραφής ανταλλακτηρίων και υπηρεσιών κωδικοποιούνται κατά το RFC 4648 Base32 χρησιμοποιώντας το αλφάβητο A-Z και 2-7 (παραλείποντας ασαφείς χαρακτήρες όπως 0, 1, 8, 9). Ο αλγόριθμος πρώτα κανονικοποιεί αυτή τη συμβολοσειρά, αφαιρώντας τα κενά διαστήματα και εφαρμόζοντας χαρακτήρες συμπλήρωσης (padding με =) ώστε το μήκος της να είναι πολλαπλάσιο του 8, προτού την αποκωδικοποιήσει στα αρχικά δυαδικά byte κλειδιού $K$.

2 Βήμα 2: Υπολογισμός χρονικού βήματος (Μετρητής $T$)

Η τρέχουσα χρονική σήμανση Unix (δευτερόλεπτα που έχουν παρέλθει από την 1η Ιανουαρίου 1970 UTC) αντιστοιχίζεται στον ακέραιο μετρητή $T$:

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

Με τυπικές παραμέτρους $T_0 = 0$ και διάρκεια βήματος $X = 30$ δευτερόλεπτα. Αυτός ο ακέραιος $T$ στη συνέχεια κωδικοποιείται ως ένας ανυπόγραφος ακέραιος 64-bit big-endian 8 byte.

3 Βήμα 3: Υπολογισμός κατακερματισμού HMAC-SHA1

Χρησιμοποιώντας τα raw bytes του κλειδιού $K$ ως μυστικό κλειδί, υπολογίζουμε τη σύνοψη HMAC-SHA1 πάνω στο buffer χρόνου των 8 byte big-endian, παράγοντας έναν κατακερματισμό ελέγχου ταυτότητας μηνύματος $HS$ μήκους 20 byte (160 bit):

HS = HMAC-SHA-1(Key, T)
4 Βήμα 4: Δυναμική περικοπή (Dynamic Truncation - DT)

Για τη μετατροπή των 20 raw byte σε έναν ακέραιο, το RFC 4226 καθορίζει τη Δυναμική Περικοπή (Dynamic Truncation):

  • Εξαγωγή μετατόπισης (Offset): Εξετάστε το τελευταίο byte $HS[19]$ και εφαρμόστε μάσκα με 0x0F για να εξαγάγετε τα 4 κατώτερα bit του, δίνοντας μια τιμή μετατόπισης μεταξύ 0 και 15: Offset = HS[19] & 0x0F;
  • Απόσπαση 4 Byte: Εξαγάγετε 4 διαδοχικά byte ξεκινώντας από το Offset: $P = HS[Offset \dots Offset+3]$;
  • Μάσκα bit προσήμου: Εφαρμόστε μάσκα στο πιο σημαντικό bit (MSB) με 0x7F για να αποτρέψετε την ασάφεια υπερχείλισης προσημασμένου ακεραίου:
    BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
5 Βήμα 5: Αναγωγή Modulo σε $N$ ψηφία

Τέλος, εφαρμόστε αναγωγή του BinaryCode modulo $10^6$ (1.000.000) για τυπικούς κωδικούς 6 ψηφίων. Συμπληρώστε το αποτέλεσμα με μηδενικά στα αριστερά για να εξασφαλίσετε μια σταθερή συμβολοσειρά εξόδου 6 χαρακτήρων:

OTP = BinaryCode mod 10^6

03. Εκτελέσιμος κώδικας 1: Τυπική βιβλιοθήκη Python 3

Αυτή η πλήρης υλοποίηση χρησιμοποιεί τα ενσωματωμένα αρθρώματα base64, struct, hmac και hashlib της Python, χωρίς να απαιτείται καμία εξωτερική βιβλιοθήκη (zero dependencies):

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: """ Δημιουργία κωδικού TOTP συμβατού με το RFC 6238. :param secret_base32: Κοινόχρηστο μυστικό κλειδί σε μορφή Base32 (π.χ. ‘JBSWY3DPEHPK3PXP’) :param timestamp: Χρονική σήμανση εποχής Unix σε δευτερόλεπτα. Προεπιλογή σε time.time() :param step: Παράθυρο χρονικού βήματος σε δευτερόλεπτα (προεπιλογή: 30) :param digits: Αριθμός ψηφίων εξόδου (προεπιλογή: 6) :return: Συμπληρωμένη με μηδενικά συμβολοσειρά OTP (π.χ. ‘045812’) """ if timestamp is None: timestamp = int(time.time())

# 1. Κανονικοποίηση συμβολοσειράς Base32 και προσθήκη χαρακτήρων συμπλήρωσης '='
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. Υπολογισμός 64-bit big-endian μετρητή χρονικού βήματος T
time_counter = timestamp // step
time_bytes = struct.pack(">Q", time_counter)  # 8 bytes big-endian unsigned long long

# 3. Υπολογισμός σύνοψης HMAC-SHA1 (20 bytes)
hmac_digest = hmac.new(key_bytes, time_bytes, hashlib.sha1).digest()

# 4. Εκτέλεση δυναμικής περικοπής (Dynamic Truncation)
offset = hmac_digest[-1] & 0x0F  # Χαμηλότερα 4 bits του 20ού byte
# Εξαγωγή 4 bytes στο offset και απαλοιφή του MSB sign bit
binary_code = struct.unpack(">I", hmac_digest[offset : offset + 4])[0] & 0x7FFFFFFF

# 5. Αριθμητική modulo και μορφοποίηση με συμπλήρωση μηδενικών
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 για περιβάλλοντα backend Node.js με χρήση του εγγενούς 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”));

Εκτέλεση στον browser (Client-Side) μέσω του W3C Web Crypto API:

Για σύγχρονες εφαρμογές ιστού που απαιτούν 100% ασφάλεια εκτός σύνδεσης (offline) - την ακριβή αρχιτεκτονική που τροφοδοτεί το 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: Go (Golang) για περιβάλλον παραγωγής

Τα microservices υψηλής απόδοσης επωφελούνται από τη δοκιμασμένη σε συνθήκες παραγωγής τυπική βιβλιοθήκη της 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. Επαλήθευση επίσημων διανυσμάτων δοκιμής RFC 6238 Παράρτημα B

Για να αποδειχθεί η πλήρης συμμόρφωση με το πρότυπο, κάθε κρυπτογραφική υλοποίηση πρέπει να επαληθεύεται έναντι των επίσημων διανυσμάτων δοκιμής (test vectors) που δημοσιεύονται στο IETF RFC 6238 Παράρτημα B.

Η επίσημη σουίτα δοκιμών χρησιμοποιεί το μυστικό ASCII "12345678901234567890" (20 bytes), το οποίο κωδικοποιείται σε 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] (ελέγχοντας τα προηγούμενα 30 δευτερόλεπτα, τα τρέχοντα 30 δευτερόλεπτα και τα επόμενα 30 δευτερόλεπτα). Μην διευρύνετε ποτέ το παράθυρο πέρα από $\pm 1$ βήμα, καθώς κάθε επιπλέον βήμα διευρύνει την επιφάνεια επίθεσης επανάληψης (replay attack) κατά 30 δευτερόλεπτα.

Κανόνας 2 Επιβολή κωδικού μίας χρήσης (Anti-Replay)

Ένας κωδικός πρέπει να ακυρώνεται αμέσως μετά την επιτυχή χρήση του. Αποθηκεύστε το Last_Verified_Time_Step στο Redis ή σε μια in-memory προσωρινή μνήμη (cache). Εάν ένας client επιχειρήσει να υποβάλει τον ίδιο κωδικό μέσα στο ίδιο χρονικό παράθυρο 30 δευτερολέπτων, απορρίψτε το αίτημα για να εξουδετερώσετε επιθέσεις επανάληψης τύπου man-in-the-middle.

Κανόνας 3 Περιορισμός ρυθμού επιθέσεων ωμής βίας & σύγκριση σταθερού χρόνου

Επειδή οι 6-ψήφιοι κωδικοί καλύπτουν μόνο 1.000.000 πιθανούς συνδυασμούς, τα τελικά σημεία (endpoints) χωρίς περιορισμό ρυθμού αιτημάτων μπορούν να παραβιαστούν ταχύτατα με επιθέσεις brute-force. Επιβάλετε προοδευτικές καθυστερήσεις και κλειδώματα λογαριασμού μετά από 3 διαδοχικές αποτυχίες. Στον κώδικα, εκτελείτε συγκρίσεις χρησιμοποιώντας συναρτήσεις ισότητας σταθερού χρόνου (π.χ. crypto/subtle.ConstantTimeCompare στην Go ή crypto.timingSafeEqual στο Node.js) για να αποτρέψετε την ανάλυση χρονισμού πλευρικού καναλιού (timing attacks).

08. Συχνές Ερωτήσεις (FAQ)

Ε1: Εφόσον το SHA-1 θεωρείται κρυπτογραφικά ευάλωτο, γιατί το TOTP εξακολουθεί να χρησιμοποιεί προεπιλεγμένα το HMAC-SHA1; ▼

Οι θεωρητικές επιθέσεις σύγκρουσης έναντι του απλού SHA-1 δεν παραβιάζουν τη δομή HMAC (RFC 2104). Στο HMAC, η υποκείμενη συνάρτηση κατακερματισμού είναι φωλιασμένη μέσα σε εσωτερικά και εξωτερικά μυστικά κλειδιά, γεγονός που εξουδετερώνει τις επιθέσεις που βασίζονται σε συγκρούσεις (collision attacks). Μέχρι σήμερα, το HMAC-SHA1 παραμένει ασφαλές για 2FA. Επιπλέον, το RFC 6238 υποστηρίζει πλήρως τα HMAC-SHA256 και HMAC-SHA512 εάν απαιτούνται υψηλότερα επίπεδα διασφάλισης.

Ε2: Επηρεάζουν οι ζώνες ώρας ή η θερινή ώρα (DST) τον συγχρονισμό του TOTP; ▼

Όχι. Ο χρόνος εποχής Unix (Unix epoch time) ορίζεται ως τα δευτερόλεπτα που έχουν παρέλθει από τις 1970-01-01 00:00:00 UTC. Είναι αυστηρά ανεξάρτητος από ζώνες ώρας, άλματα δευτερολέπτων και μεταβάσεις θερινής ώρας. Σε οποιαδήποτε ακριβή χρονική στιγμή, ένας διακομιστής στο Τόκιο και ένα smartphone στην Καλιφόρνια έχουν την ίδια ακριβώς ακέραια χρονική σήμανση.

Ε3: Σε τι διαφέρει ο έλεγχος ταυτότητας 5 γραμμάτων του Steam Guard από το RFC 6238; ▼

Το Steam Guard είναι μια ιδιόκτητη παραλλαγή που βασίζεται στο RFC 6238. Χρησιμοποιεί το ίδιο ακριβώς χρονικό βήμα 30 δευτερολέπτων και τον ίδιο υπολογισμό HMAC-SHA1, αλλά στην τελική φάση αναγωγής, αντιστοιχίζει τον εξαγόμενο ακέραιο μέσω ενός προσαρμοσμένου αλφαριθμητικού πίνακα 26 χαρακτήρων (23456789BCDFGHJKMNPQRTVWXY) για να δημιουργήσει μια συμβολοσειρά 5 χαρακτήρων.

Συμβατό με RFC 6238 · Υπολογισμοί Client-Side μέσω W3C Web Crypto API

Δοκιμάστε ζωντανά την 100% Client-Side παραγωγή κωδικών TOTP

Το 2FA-Tool υλοποιεί αυτήν ακριβώς την κρυπτογραφική ροή απευθείας μέσα στο πρόγραμμα περιήγησής σας χρησιμοποιώντας το W3C Web Crypto API. Μηδενική μετάδοση σε διακομιστές, μηδενική αποθήκευση κωδικών, άμεση επικύρωση.