TOTP nasıl çalışır: kimlik doğrulayıcı kodlarınızın arkasındaki algoritma
Haftada birkaç kez kimlik doğrulayıcı uygulamanızdan 6 haneli bir kod girersiniz ve aynı kod hem telefonunuzda görünür hem de sunucunun beklediğiyle eşleşir, üstelik ikisi birbiriyle hiç konuşmadan. Peki TOTP nasıl çalışır? Aynı paylaşılan gizli anahtar her 30 saniyede bir yeni bir sayı üretir; bunun nedeni, her iki tarafın da bağımsız olarak çalıştırdığı küçük, deterministik bir algoritmadır. Ağ üzerinden hiçbir kod gitmez, sayıyı dağıtan bir merkezî sunucu da yoktur.
RFC 6238 ile tanımlanan TOTP (Time-based One-Time Password), paylaşılan bir gizli anahtar ile geçerli zamanı alır, zaman üzerinde bir HMAC çalıştırır ve sonucu kısaltarak kısa bir sayısal kod elde eder. İki faktörlü kimlik doğrulama (2FA), iki tarafın bu değeri birbirine göndermeden aynısını hesaplamasına dayanır, dolayısıyla güven tümüyle algoritmaya yaslanır.
Aşağıda algoritmayı somut sayılarla baştan sona izliyoruz; ardından çoğu anlatımın atladığı yarıya geçiyoruz: bir sunucu kodu gerçekte nasıl doğrular ve 2FA neyi durdurur, neyi durdurmaz. Okurken TOTP üreticimizle canlı bir kod da hesaplayabilirsiniz.
TOTP gerçekte nedir?
RFC 6238 ile tanımlanan TOTP (Time-based One-Time Password), paylaşılan bir gizli anahtarı geçerli zamanla birleştirip sabit aralıklarla dönen kısa bir kod üreten algoritmadır. Hem kimlik doğrulayıcı uygulama hem de sunucu aynı gizli anahtarı tutar, aynı saati okur, aynı matematiği çalıştırır ve kodu birbirine hiç göndermeden aynı sonuca varır.
Asıl önemli olan o son nokta. Kurulumda kod değil, yalnızca gizli anahtar gönderilir; ondan sonra her taraf kodları kendi başına türetir. Hat üzerinde ele geçirilecek tek şey, kayıt sırasındaki gizli anahtar ile kullanıcının girişte yazdığı 6 hanedir. İşin özünde üç girdi tek bir çıktıya iner:
| Girdi | Rolü | Tipik değer |
|---|---|---|
| Paylaşılan gizli anahtar | Kayıt sırasında bir kez üzerinde anlaşılan, uzun ömürlü anahtar | JBSWY3DPEHPK3PXP (Base32) |
| Zaman adımı | İleriye doğru ilerleyen sayaç | 30 saniyelik pencere |
| Çıktı | İkisinden türetilen kısa kod | 324550 |
Gizli anahtar neredeyse her zaman Base32 (A–Z harfleri ve 2–7 rakamları) ile yazılır; çünkü bu alfabe büyük-küçük harfe duyarsızdır ve yazdırmaya, elle girmeye ya da bir QR koduna sığdırmaya elverişlidir. Bir gizli anahtarı, kimlik doğrulayıcı QR’ı olarak da üretebileceğiniz bir otpauth:// URI’sini tarayarak veya Base32 dizisini elle yazarak kaydedersiniz.
TOTP, HOTP, SMS ve Passkey karşılaştırması: 2FA manzarası
TOTP, birkaç seçenekten yalnızca biridir; iyi bir seçim için diğerlerini de tanımak gerekir. Akılda tutulacak tek cümlelik bağ şu: TOTP, sayaç yerine Unix epoch’tan bu yana geçen zaman adımı sayısının konduğu HOTP’dir. Geri kalanı, kimlik avına direnç, kullanım kolaylığı ve gereken altyapı arasında bir denge meselesidir.
| Mekanizma | İtici güç | Kod ömrü | Kimlik avına dirençli mi? | Ağ gerekir mi? | Tipik kullanım |
|---|---|---|---|---|---|
| HOTP (RFC 4226) | Artan sayaç | Kullanılana kadar | Hayır | Hayır | Donanım token’ları, eski sistemler |
| TOTP (RFC 6238) | Geçerli zaman | ~30 saniye | Hayır | Hayır (kayıttan sonra) | Kimlik doğrulayıcı uygulamalar |
| SMS OTP | Sunucu bir kod gönderir | Birkaç dakika | Hayır | Evet (hücresel) | Tüketici yedeği |
| Push onayı | Bir cihaza sunucu istemi | İstek başına | Kısmen | Evet | Uygulama tabanlı 2FA |
| Passkey / FIDO2 | Açık anahtarlı meydan okuma | İstek başına | Evet (köken bağlı) | Evet | Modern hesaplar |
Tabloda asıl bakılması gereken örüntü şu. TOTP ve HOTP bir kez kaydedildikten sonra çevrimdışı çalışır; bu da onları dayanıklı ve gizli yapar, ama ikisi de tek başına kimlik avına dirençli değildir: inandırıcı bir sahte sayfa kodu isteyip gerçek siteye iletebilir. SMS, kendi saldırı yüzeyini getiren bir ağ kanalı ekler. Passkey’ler kimlik bilgisini sitenin kökenine bağlayarak kimlik avı açığını kapatır; sektörün bu yöne gitmesinin nedeni de bu. TOTP ise hem güçlü hem her yerde bulunabilir hem de ücretsizdir; bu kadar yaygın kalmasının asıl sebebi de bu.
TOTP algoritması adım adım nasıl çalışır?
Algoritmanın tamamı dört adımdan oluşur. Her adımı, RFC test gizli anahtarı JBSWY3DPEHPK3PXP ve sabit bir Unix zamanı 1700000000 ile çalıştıracağız; böylece çıkan her sayıyı kendiniz de yeniden üretebilirsiniz.
- Base32 gizli anahtarını çözün ve ham anahtar baytlarına dönüştürün.
- Zaman adımı sayacını hesaplayın geçerli Unix zamanından.
- Sayaca HMAC uygulayın gizli anahtarla.
- Özeti kısaltın 6 haneli bir koda.
Adım 1 — Base32 gizli anahtarını baytlara çözün
Base32, her karaktere 5 bit sığdırır. Çözücü bu karakterleri yeniden 8 bitlik baytlar hâlinde toplar. JBSWY3DPEHPK3PXP gizli anahtarı 10 ham bayta çözülür: 48 65 6c 6c 6f 21 de ad be ef. HMAC’in kullandığı anahtar, yazdırılabilir dize değil, işte bu bayt dizisidir.
Adım 2 — Zaman adımı sayacını hesaplayın
Sayaç, bir başlangıç noktasından bu yana geçen tam zaman adımı sayısıdır: T = floor((unixTime − T0) / period). RFC varsayılanları T0 = 0 (Unix epoch) ve period = 30’dur. unixTime = 1700000000 için bu T = floor(1700000000 / 30) = 56666666 verir. Sonra bu tam sayı 8 baytlık big-endian bir değere kodlanır: 00 00 00 00 03 60 aa 2a. Sayaç ancak yeni bir 30 saniyelik pencere başlayınca değiştiği için, her kod pencere boyunca sabit kalır, pencere bitince bir sonrakine atlar.
Adım 3 — Sayaca gizli anahtarla HMAC uygulayın
Algoritma, gizli anahtar baytlarını anahtar olarak alıp 8 baytlık sayaç üzerinde HMAC-SHA1 çalıştırır. HMAC, anahtarlı ve tek yönlü bir fonksiyondur: gizli anahtar olmadan özeti geri çeviremez ya da geçerli bir özet üretemezsiniz, kodu taklit edilemez yapan da budur. Bizim girdilerimizde özet şu 20 bayttır: 1d 70 6e 94 1a c7 6b 6d 4a 46 dd 6f af a4 5f e3 35 11 bf 86.
Adım 4 — 6 haneli bir koda dinamik kısaltma (RFC 4226)
20 baytlık özet, yazmak için fazla uzundur; bu yüzden RFC 4226’nın dinamik kısaltması ondan bir sayı çekip çıkarır. Son baytın düşük nibble’ını ofset olarak alın: son bayt 0x86, düşük nibble’ı 6, yani ofset 6. O ofsetten itibaren 4 bayt okuyun (6b 6d 4a 46) ve sayının pozitif kalması için ilkinin en üst bitini maskeleyin; sonuç 1802324550 tam sayısıdır. Bunu 10^6 moduna indirip sıfırlarla doldurun: 1802324550 % 1000000 = 324550. Uygulamanızın bu gizli anahtar için şu an gösterdiği kod budur.
Aşağıda algoritmanın JavaScript karşılığı var: tarayıcının yerel Web Crypto API’siyle, bağımlılıksız. Her yorum satırı, ilgili bloğu yukarıdaki dört adımdan birine bağlar:
// TOTP per RFC 6238 — SHA-1, 6 digits, 30s period (the defaults).
async function generateTotp(base32Secret, unixTime = Date.now() / 1000) {
// Step 1: decode the Base32 secret (A-Z, 2-7) to raw key bytes.
const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let bits = '';
for (const ch of base32Secret.replace(/=+$/, '').toUpperCase()) {
bits += alpha.indexOf(ch).toString(2).padStart(5, '0');
}
const keyBytes = new Uint8Array(
bits.match(/.{8}/g).map((b) => parseInt(b, 2)));
// Step 2: counter = number of 30s steps since the epoch (8-byte big-endian).
let counter = Math.floor(unixTime / 30);
const msg = new Uint8Array(8);
for (let i = 7; i >= 0; i--) {
msg[i] = counter & 0xff;
counter = Math.floor(counter / 256);
}
// Step 3: HMAC-SHA1 the counter with the secret key.
const key = await crypto.subtle.importKey(
'raw', keyBytes, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
const hmac = new Uint8Array(await crypto.subtle.sign('HMAC', key, msg));
// Step 4: dynamic truncation (RFC 4226) -> 6-digit code.
const offset = hmac[hmac.length - 1] & 0x0f;
const binary = ((hmac[offset] & 0x7f) << 24) | (hmac[offset + 1] << 16) |
(hmac[offset + 2] << 8) | hmac[offset + 3];
return (binary % 1_000_000).toString().padStart(6, '0');
}
const code = await generateTotp('JBSWY3DPEHPK3PXP', 1700000000);
console.log(code); // -> "324550"
Aynı algoritmanın Python karşılığı; yalnızca standart kütüphaneyi (hmac ve struct) kullanır:
import base64, hmac, hashlib, struct, time
def totp(secret, for_time=None, period=30, digits=6, digest='sha1'):
if for_time is None:
for_time = time.time()
# Step 1: Base32-decode the secret to raw key bytes.
key = base64.b32decode(secret.upper())
# Step 2: counter = number of time steps since the epoch (8-byte big-endian).
counter = int(for_time // period)
msg = struct.pack(">Q", counter)
# Step 3: HMAC the counter with the secret.
h = hmac.new(key, msg, digest).digest()
# Step 4: dynamic truncation (RFC 4226) -> N-digit code.
offset = h[-1] & 0x0f
binary = ((h[offset] & 0x7f) << 24 |
(h[offset + 1] & 0xff) << 16 |
(h[offset + 2] & 0xff) << 8 |
(h[offset + 3] & 0xff))
return str(binary % (10 ** digits)).zfill(digits)
print(totp('JBSWY3DPEHPK3PXP', 1700000000)) # -> 324550
Her iki uygulama da sabit zamanımız için 324550 yazdırır ve resmî RFC 6238 test vektörlerini yeniden üretir (örneğin T = 59’daki SHA-1 vektörü 94287082 verir). SHA-1 yerine SHA-256 ya da SHA-512 kullanırsanız veya hane sayısını değiştirirseniz, karşı taraftaki doğrulayıcı aynı seçimleri birebir kullanmak zorundadır; yoksa kodlar hiçbir zaman tutmaz.
TOTP kodunu sunucu tarafında doğrulama
Kod üretmek işin yarısı. Diğer yarısı, sunucunun kullanıcının az önce girdiği 6 haneyi kabul edip etmeyeceğine karar vermesi; güvenlik açısından kritik dengelerin toplandığı adım da bu.
Sunucu kodları saklamaz; gizli anahtarı saklar. Giriş anında beklenen kodu bu gizli anahtar ile geçerli zamandan yeniden hesaplar, sonra karşılaştırır. Buradaki püf nokta saat kayması: kullanıcının cihazıyla sunucu saniyesi saniyesine örtüşmez, bu yüzden katı bir eşitlik kontrolü pencere sınırına yakın kodları reddeder. Çözüm küçük bir doğrulama penceresidir. Geçerli adımı ve iki yandaki birer adımı kabul edin; yani T−1, T ve T+1 sayaçları için kodları kontrol edin. Pencereyi genişletmek daha fazla kaymaya tolerans tanır ama tahmin yüzeyini büyütür; bu yüzden pencere 1 (±30 saniyelik tolerans) yaygın dengedir. Aracın Doğrula sekmesinde gördüğünüz ±1 adımlık tolerans da tam olarak budur.
import { createHmac, timingSafeEqual } from 'crypto';
function verifyTotp(secret, code, { window = 1, period = 30, digits = 6 } = {}) {
const counter = Math.floor(Date.now() / 1000 / period);
const submitted = Buffer.from(code);
// Check the current step and ±window steps for clock drift.
for (let i = -window; i <= window; i++) {
const expected = Buffer.from(totpAt(secret, counter + i, digits));
// Constant-time compare so timing can't leak a partial match.
if (expected.length === submitted.length &&
timingSafeEqual(expected, submitted)) {
return counter + i; // matched step — store it to block replay
}
}
return false;
}
İki ayrıntı daha bunu “çalışır” hâlinden “güvenli” hâline taşır. İlki, yeniden oynatma önleme: her kullanıcı için kabul ettiğiniz son sayacı saklayın ve o seviyedeki ya da altındaki bir adımdan gelen kodları reddedin; böylece bir kez ele geçirilen kod aynı pencere içinde tekrar kullanılamaz. verifyTotp’un düz bir true yerine eşleşen adımı döndürmesinin nedeni de bu. İkincisi, hız sınırlama: 6 haneli bir kod, bir milyon değerden yalnızca biridir ve ±1’lik pencere bunlardan üçünü her an geçerli tutar; dolayısıyla hiçbir kısıt yoksa bir saldırgan bu uzayı kaba kuvvetle tarayabilir. Birkaç başarısız denemeden sonra hesabı kilitleyin ya da bekleme süresi ekleyin. Son olarak gizli anahtar uzun ömürlü bir anahtardır; onu durağan hâlde şifreleyin, kaynak kontrolünün dışında tutun ve tıpkı bir parola gibi koruyun. Bir cihazın kaybolacağı güne hazırlık olarak yanında güçlü kurtarma kodları üretin.
TOTP neye karşı korur, neye karşı korumaz
TOTP, sadece parolaya kıyasla gerçek bir iyileştirmedir ama sihir değildir; pazarlama sayfaları da boşlukların üstünü örtme eğilimindedir. Aşağıda durum olduğu gibi.
| TOTP’nin durdurdukları | TOTP’nin durdurmadıkları |
|---|---|
| Sızdırılan veya yeniden kullanılan parolalar | Gerçek zamanlı kimlik avı / aradaki düşman |
| Kimlik bilgisi doldurma | Gizli anahtarı bir cihazdan okuyan kötü amaçlı yazılım |
| Uzaktan parola kaba kuvvet | 2FA’yı atlayan zayıf hesap kurtarma akışları |
| Yalnızca parola özetlerini açığa çıkaran bir veritabanı ihlali | (bunlar başka savunmalar gerektirir) |
Kazanımlar gerçek. Giriş artık yalnızca gizli anahtarın üretebileceği bir kod istediğinden, sızdırılmış bir parola tek başına işe yaramaz; bu da kimlik bilgisi doldurmayı ve uzaktan kaba kuvveti doğrudan keser. Veritabanınız sızsa bile, TOTP gizli anahtarları durağan hâlde şifreliyse saldırgan yine kod üretemez.
Boşluklar da en az o kadar gerçek. Gerçek zamanlı bir kimlik avı vekili (aradaki düşman sayfası) kullanıcıya kusursuz bir kopya gösterebilir, canlı kodu yakalayabilir ve aynı pencere içinde gerçek siteye iletebilir; TOTP, kodun yanlış yere yazıldığını fark edemez. Gizli anahtarı dışarı sızdıran bir cihaz zararlısı korumayı tümüyle çökertir, özensiz bir “2FA’mı unuttum” kurtarma akışı da onu baştan baypas edebilir. Sık karışan bir noktayı netleştirelim: SIM takas saldırıları SMS tek seferlik kodlarını çökertir, TOTP’yi değil; çünkü TOTP’nin telefon numarası kanalı yoktur, dolayısıyla saldırganın yönlendirebileceği bir şey de yoktur.
Peki sonrası? Passkey’ler ve FIDO2/WebAuthn köken bağlıdır, bu yüzden tasarımı gereği kimlik avına dirençlidir: kimlik bilgisi, yanlış alan adına kimlik doğrulamayı reddeder. TOTP’yi parolanın üstüne eklenen güçlü ve her yerde bulunabilir bir kat olarak görün; varış noktası olarak değil. Geri kalan kimlik doğrulama yığınınızla rahatça uyuşur: doğrulanmış bir girişin üzerine oturan oturum token katmanı için JWT güvenliği en iyi uygulamalarına, 2FA’nın tamamladığı durağan parola katmanı için parola özetleme (bcrypt’e karşı Argon2) yazısına bakın.
TOTP uygularken sık karşılaşılan tuzaklar
TOTP hatalarının çoğu, RFC’nin sabitlediği algoritmada değil, etrafındaki bağlantı noktalarında çıkar. Uygulayıcıları en çok ısıran tuzaklar şunlar.
- Sunucu saat kayması. Sunucu NTP çalıştırmıyorsa “şimdi” anlayışı kullanıcının cihazından uzaklaşır ve kodlar herkes için tutmamaya başlar. Her ana makinede ağ saat senkronizasyonunu açın.
- Düz metin ya da commit’lenmiş gizli anahtarlar. Git’e commit’lenmiş bir yapılandırma dosyasındaki gizli anahtar, kalıcı bir arka kapıdır. Onu bir gizli anahtar yöneticisinde şifreli tutun, asla kaynak kontrolünde değil.
- Yeniden oynatma koruması yok. Bir kodu, eşleştiği adımı kaydetmeden kabul ederseniz aynı kod kendi penceresi içinde tekrar çalışır. Kullanıcı başına son kullanılan adımı kalıcı tutun ve yeniden kullanımı reddedin.
- Pencerenin çok geniş ya da çok dar olması. Çok geniş tutarsanız geçerli kod sayısı katlanır ve güvenlik zayıflar; çok dar tutarsanız küçük bir kaymada meşru kullanıcıları reddedersiniz. Alışılmış denge pencere 1’dir.
- Parametre uyuşmazlığı. Kayıt
otpauth://URI’si SHA-256 ve 8 hane kodluyor ama doğrulayıcı SHA-1 ve 6 hane varsayıyorsa hiçbir kod doğrulanmaz. Algoritmayı, hane sayısını ve dönemi URI’den okuyup her iki tarafta da aynısını kullanın. - Yedek ya da kurtarma kodu yok. Bir telefon kaybolduğunda geri dönmenin tek yolu kurtarmadır. Kurulumda kurtarma kodları verin ve onları hesabın hak ettiği kadar güçlü tutun; parola entropisi yazısındaki mantık kurtarma gizli anahtarları için de geçerlidir.
SSS
TOTP kimlik avına karşı dayanıklı mı?
Hayır. TOTP, sızdırılan parolaları ve uzaktan kaba kuvveti durdurur ama gerçek zamanlı bir kimlik avı vekili sahte bir giriş gösterip canlı kodu yakalayabilir ve aynı 30 saniyelik pencere içinde gerçek siteye iletebilir. Kimlik bilgisini sitenin kökenine bağladıkları için passkey’ler ve FIDO2 bu konuda kimlik avına dirençli iyileştirmedir.
TOTP, SMS 2FA’dan daha mı güvenli?
Evet. SMS kodları hücresel ağ üzerinden gider ve SIM takas ya da SS7 saldırılarıyla ele geçirilebilir; üstelik operatörünüzün güvenliğine bağımlıdır. TOTP’nin telefon numarası kanalı yoktur ve kodu hiç iletmez, dolayısıyla aktarım sırasında ele geçirilecek bir şey de yoktur. Gizli anahtar yalnızca bir kez, kurulumda paylaşılır.
Telefonumu veya kimlik doğrulayıcı uygulamamı kaybedersem ne olur?
Önceden hazırlanmış bir yedeğe ihtiyacınız olur. Seçenekleriniz: 2FA’yı kurarken aldığınız kurtarma kodları, aynı gizli anahtarla kaydedilmiş ikinci bir cihaz ya da güvenli bir yerde sakladığınız orijinal Base32 gizli anahtarı. Bunlardan biri yoksa, cihazı kaybetmek hesaptan kilitlenip dışarıda kalmak demektir.
Bir sunucu bir TOTP kodunu nasıl doğrular?
Beklenen kodu paylaşılan gizli anahtar ile geçerli zamandan yeniden hesaplar, sonra gönderilen kodu geçerli zaman adımına ve saat kayması için iki yandaki birer adıma karşı kontrol eder. Aynı kodun yeniden oynatılmasını engellemek için hangi adımın eşleştiğini kaydeder ve tahmin denemelerini hızla sınırlar.
TOTP kodları neden her 30 saniyede bir yenilenir?
Otuz saniye, RFC 6238’in varsayılan dönemidir: kodu rahatça okuyup yazacak kadar uzun, yakalanan bir kodun neredeyse hemen geçersiz kalacağı kadar kısa. Bazı sistemler 60 saniyelik dönem kullanır; doğrulayıcının eşleşebilmesi için bu süre otpauth:// URI’sinde kayıtlıdır.
İki cihaz tek bir TOTP gizli anahtarını paylaşabilir mi?
Evet. Saati senkron olan ve aynı Base32 gizli anahtarını tutan her cihaz, algoritma deterministik olduğu için aynı kodları üretir. Çok cihazlı kimlik doğrulayıcı yedeklemeleri tam olarak böyle çalışır; gizli anahtarın gizli kalması gerekmesinin nedeni de bu: onu kopyalayan herkes ileride çıkacak her kodu üretebilir.
TOTP, Google Authenticator ile aynı şey mi?
Hayır. TOTP, RFC 6238’de tanımlanan açık algoritmadır. Google Authenticator, Authy ve 1Password ise onu uygulayan uygulamalardır. Standart ortak olduğu için, uyumlu herhangi bir uygulama TOTP kullanan herhangi bir hizmetle çalışır; belli bir satıcıya bağlı kalmazsınız.
Sonuç
Akılda kalacak kadar kısa olan ana fikirler:
- TOTP, paylaşılan bir gizli anahtarı ve geçerli zamanı HMAC ve kısaltma yoluyla bir koda çevirir.
- İki taraf da kodu bağımsız hesaplar; kod ağ üzerinden hiç gönderilmez.
- Doğrulamayı ±1 adımlık bir pencereyle, yeniden oynatma koruması ve hız sınırlamayla yapın.
- Parola saldırılarını durdurur ama gerçek zamanlı kimlik avını durdurmaz; o açığı passkey’ler kapatır.
- Sunucu saatlerini NTP ile senkron tutun, gizli anahtarı şifreli ve gizli saklayın.
Algoritmanın gerçek sayılar üretişini görmek ve kendi doğrulama pencerenizi denemek isterseniz, TOTP / 2FA üreticisini açın; kodları hesaplama, kurma ve doğrulama tamamen tarayıcınızda olur, gizli anahtar cihazınızdan hiç ayrılmaz.