Modern web güvenliğindeki temel kriptografik protokoller arasında, RFC 6238 (TOTP: Zamana Dayalı Tek Kullanımlık Şifre Algoritması) matematiksel sadeliğiyle öne çıkar. Etkileşimli ağ el sıkışmaları, merkezi açık anahtar altyapısı (PKI) ve bulut gidiş-dönüşleri gerektirmez. Yalnızca paylaşılan bir Base32 gizli anahtarı ve fiziksel Unix zamanının sabit ilerleyişi ile istemci ve sunucu, %100 matematiksel kesinlikle her 30 saniyede bir aynı, geçici 6 haneli kimlik doğrulama belirteçlerini hesaplar.
Bu kapsamlı rehberde, üçüncü taraf kapalı kutu kütüphaneleri (pyotp veya otplib gibi) atlayarak eksiksiz RFC 6238 kriptografik iş hattını Python, JavaScript ve Go dillerinde sıfırdan inşa ediyor ve her uygulamayı resmi IETF test vektörlerine karşı doğruluyoruz.
01. Zaman Odaklı Kimlik Doğrulamanın Zarafeti
TOTP'yi anlamak için öncelikle selefini incelememiz gerekir: RFC 4226 HOTP (HMAC Tabanlı Tek Kullanımlık Şifre).
HOTP'de istemci ve sunucu bir olay sayacı $C$ paylaşır. Kullanıcı her belirteç oluşturduğunda sayaç artar ($C = C + 1$) ve belirteç $\text{HMAC-SHA-1}(K, C)$ üzerinden türetilir. Matematiksel olarak sağlam olsa da HOTP operasyonel bir zayıflıktan muzdaripti: sayaç senkronizasyonunun bozulması. Bir kullanıcı cebindeki donanım anahtarının düğmesine yanlışlıkla beş kez basarsa, istemci sayacı $C=6$ değerine ulaşırken kimlik doğrulama sunucusu $C=1$ değerinde kalır ve bu da sürekli oturum açma reddine yol açardı.
Mayıs 2011'de N. M'Raihi, S. Machani, M. Pei ve J. Rydell tarafından yayımlanan RFC 6238, artan tam sayıyı standartlaştırılmış Unix epoch zamanı ile değiştirerek sayaç senkronizasyon sorununu çözdü. Fiziksel zaman dünya genelinde eşit şekilde ilerlediğinden, hem istemci hem de sunucu tek bir ağ paketi bile göndermeden dahili sayaçlarını aynı anda ilerletir.
02. TOTP'nin 5 Kriptografik Matematik Adımı
16 karakterlik bir Base32 tohumunu (ör. JBSWY3DPEHPK3PXP) 6 haneli dönen bir koda (ör. 284915) dönüştürmek beş hassas kriptografik aşama içerir:
Borsa kayıt anahtarları, A-Z ve 2-7 alfabesi kullanılarak (0, 1, 8, 9 gibi belirsiz karakterler hariç tutularak) RFC 4648 Base32 ile kodlanır. Algoritma önce bu dizeyi normalize eder, boşlukları temizler ve uzunluğu 8'in katı olacak şekilde dolgu (=) uygular, ardından onu ham ikili (binary) anahtar baytlarına $K$ çözer.
Geçerli Unix zaman damgası (1 Ocak 1970 UTC'den bu yana geçen saniye) tam sayı sayacı $T$'ye eşlenir:
Standart parametreler $T_0 = 0$ ve adım süresi $X = 30$ saniyedir. Bu tam sayı $T$ daha sonra 8 baytlık big-endian 64 bit işaretsiz tam sayı olarak kodlanır.
Ham anahtar baytları $K$ gizli anahtar olarak kullanılarak, 8 baytlık big-endian zaman arabelleği üzerinde HMAC-SHA1 özeti hesaplanır ve 20 baytlık (160 bit) bir mesaj kimlik doğrulama özeti $HS$ elde edilir:
20 ham baytı bir tam sayıya dönüştürmek için RFC 4226 Dinamik Kırpmayı tanımlar:
- Ofseti Çıkarın: Son bayt $HS[19]$'u inceleyin ve alt 4 bitini çıkarmak için
0x0File maskeleyin; bu, 0 ile 15 arasında bir ofset değeri verir:Offset = HS[19] & 0x0F; - 4 Baytı Dilimleyin: Ofsetten başlayarak ardışık 4 baytı çıkarın: $P = HS[Offset \dots Offset+3]$;
- İşaret Bitini Maskeleyin: İşaretli tam sayı taşması belirsizliğini önlemek için en anlamlı biti (MSB)
0x7File maskeleyin:
BinaryCode = ((P[0] & 0x7F) << 24) | ((P[1] & 0xFF) << 16) | ((P[2] & 0xFF) << 8) | (P[3] & 0xFF)
Son olarak, standart 6 haneli belirteçler için BinaryCode değerini modulo $10^6$ (1.000.000) ile indirgeyin. Tutarlı bir 6 karakterli çıktı dizesi sağlamak için sonucun solunu sıfırlarla doldurun:
03. Çalıştırılabilir Kod 1: Python 3 Standart Kütüphanesi
Bu eksiksiz uygulama, hiçbir pip paketi gerektirmeden Python'un yerleşik base64, struct, hmac ve hashlib modüllerini kullanır:
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. Çalıştırılabilir Kod 2: JavaScript ve Node.js Crypto
Yerel crypto.createHmac kullanarak arka uç Node.js çalışma zamanları için saf JavaScript uygulaması:
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”));
W3C Web Crypto API ile İstemci Taraflı Tarayıcı Yürütmesi:
%100 çevrimdışı güvenlik arayan modern web uygulamaları için (2FA-Tool'a güç veren mimarinin birebir aynısı), W3C Web Crypto API üçüncü taraf npm paketlerine ihtiyaç duymadan doğrudan tarayıcı sanal alanında (sandbox) eşzamansız, donanım hızlandırmalı kriptografik temel öğeler sağlar:
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. Çalıştırılabilir Kod 3: Üretim Düzeyi Golang (Go)
Yüksek işlem hacimli mikro servisler, Go'nun kendini kanıtlamış standart kütüphanesinden yararlanır:
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. RFC 6238 Ek B Test Vektörleri Doğrulaması
Standart uyumluluğunu kanıtlamak için her kriptografik uygulama, IETF RFC 6238 Ek B'de yayımlanan resmi test vektörlerine karşı doğrulanmalıdır.
Resmi test paketi, Base32'de şu şekilde kodlanan "12345678901234567890" (20 bayt) ASCII sırrını kullanır:
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ (30 saniyelik adımlarla 8 haneli çıktılar için değerlendirilmiştir):
| Unix Epoch Zaman Damgası | UTC Tarih ve Saat | RFC Resmi Beklenen Kod | Uygulama Sonucu |
|---|---|---|---|
| 59 | 1970-01-01 00:00:59 | 94287082 | ✓ BAŞARILI (Tam Eşleşme) |
| 1111111109 | 2005-03-18 01:58:29 | 07081804 | ✓ BAŞARILI (Tam Eşleşme) |
| 1111111111 | 2005-03-18 01:58:31 | 14050471 | ✓ BAŞARILI (Tam Eşleşme) |
| 1234567890 | 2009-02-13 23:31:30 | 89005924 | ✓ BAŞARILI (Tam Eşleşme) |
| 2000000000 | 2033-05-18 03:33:20 | 69279037 | ✓ BAŞARILI (Tam Eşleşme) |
07. Üretim Mühendisliği: Zaman Kayması, Tekrar Oynatma ve Hız Sınırları
Yüksek erişilebilirlikli kurumsal kimlik doğrulama sistemlerinde TOTP doğrulamasını devreye alırken üç güvenlik gereksinimi zorunlu kılınmalıdır:
Küçük mobil saat sapmaları nedeniyle sunucular [T - 1, T, T + 1] penceresini değerlendirmelidir (önceki 30 sn, geçerli 30 sn ve sonraki 30 sn kontrol edilir). Pencereyi asla $\pm 1$ adımın ötesine genişletmeyin; çünkü her ek adım, tekrar oynatma (replay) saldırı yüzeyini 30 saniye genişletir.
Bir belirteç başarıyla kullanıldıktan hemen sonra geçersiz kılınmalıdır. Last_Verified_Time_Step değerini Redis'te veya bellek içi önbellekte saklayın. Bir istemci aynı 30 saniyelik pencere içinde aynı belirteci göndermeye çalışırsa, ortadaki adam (MitM) tekrar oynatma saldırılarını etkisiz hale getirmek için isteği reddedin.
6 haneli kodlar yalnızca 1.000.000 permütasyonu kapsadığından, hız sınırı olmayan uç noktalar hızla kaba kuvvet (brute-force) saldırısına uğrayabilir. Ardışık 3 başarısızlığın ardından artan gecikmeler ve hesap kilitlemeleri uygulayın. Kod içinde, yan kanal zamanlama analizini engellemek için karşılaştırmaları sabit zamanlı eşitlik işlevleri (ör. Go'da crypto/subtle.ConstantTimeCompare veya Node.js'de crypto.timingSafeEqual) kullanarak gerçekleştirin.
08. Sıkça Sorulan Sorular (SSS)
S1: SHA-1 kriptografik olarak kırıldığı halde TOTP neden hala varsayılan olarak HMAC-SHA1 kullanıyor? ▼
Ham SHA-1'e yönelik teorik çakışma (collision) saldırıları HMAC yapısını (RFC 2104) kırmaz. HMAC'te, temel özet işlevi iç ve dış gizli anahtarların içine yerleştirilmiştir ve bu da çakışma tabanlı saldırıları etkisiz hale getirir. Günümüzde HMAC-SHA1, 2FA için güvenli olmaya devam etmektedir. Ayrıca daha yüksek güvence gerekiyorsa RFC 6238, HMAC-SHA256 ve HMAC-SHA512'yi tam olarak destekler.
S2: Saat dilimleri veya Yaz Saati Uygulaması (DST) TOTP senkronizasyonunu etkiler mi? ▼
Hayır. Unix epoch zamanı, 1970-01-01 00:00:00 UTC'den bu yana geçen saniye olarak tanımlanır. Saat dilimlerinden, artık saniyelerden ve yaz saati geçişlerinden tamamen bağımsızdır. Herhangi bir anda Tokyo'daki bir sunucu ile Kaliforniya'daki bir akıllı telefon birebir aynı tam sayı zaman damgasına sahiptir.
S3: Steam Guard 5 harfli kimlik doğrulaması RFC 6238'den nasıl farklıdır? ▼
Steam Guard, RFC 6238'e dayalı özel (tescilli) bir varyanttır. Birebir aynı 30 saniyelik zaman adımını ve HMAC-SHA1 hesaplamasını kullanır, ancak son indirgeme aşamasında çıkarılan tam sayıyı 5 karakterli bir dize üretmek üzere özel bir 26 karakterlik alfanümerik karakter dizisi (23456789BCDFGHJKMNPQRTVWXY) üzerinden eşler.
%100 İstemci Taraflı TOTP Kod Üretimini Canlı Deneyimleyin
2FA-Tool, W3C Web Crypto API kullanarak bu kriptografik iş hattını doğrudan tarayıcınızın içinde uygular. Sıfır sunucu iletimi, sıfır belirteç depolama, anında doğrulama.