İçeriğe geç
academia.sh

Ders 11 / 23

Çok Etmenli Doğrulama

Etmen sınıflarının bağımsızlığı, zaman tabanlı kodun türetilmesi ve saat kayması penceresinin bedeli, aynı kodun ikinci kez kullanılmasının engellenmesi ve kurtarma kodlarının tek kullanımlık saklanması.

İçindekiler

Önceki ders makine çağıranların tek kanıtını ele aldı: anahtarı elinde tutan çağırandır. İnsan kullanıcılarda tek kanıta dayanmak yetersizdir. Parola bir kez ele geçtiğinde — yeniden kullanılmış, oltalama sayfasına yazılmış ya da başka bir sızıntıdan gelmiş olabilir — hesabın tamamı açılır.

Çok etmenli doğrulama, girişi birden çok bağımsız kanıta bağlar. Kazancın kaynağı sayı değil bağımsızlıktır: iki kanıt aynı yoldan ele geçirilebiliyorsa ikinci kanıt bir şey eklemez.

Etmen Sınıfları

Kanıtlar üç sınıfa ayrılır: bilinen bir şey (parola, kurtarma kodu), sahip olunan bir şey (telefondaki uygulama, donanım anahtarı) ve kişinin kendisine ait bir şey (biyometrik). Çok etmenli doğrulama, farklı sınıflardan en az iki kanıt ister.

Aşağıdaki hesap zaman tabanlı kodu üretir, doğrular ve etmen sınıflarını karşılaştırır.

cat > mfa.mjs <<'EOF'
// mfa.mjs — zaman tabanli tek kullanimlik kod, saat kaymasi ve kurtarma kodlari
import { createHmac, createHash, timingSafeEqual } from "node:crypto";

// 1. Zaman tabanli kod (TOTP): paylasilan sirdan ve zaman diliminden turetilir.
const ADIM = 30;                       // saniye
const BASAMAK = 6;
function totp(sir, saat, adim = ADIM) {
  const sayac = Math.floor(saat / adim);
  const tampon = Buffer.alloc(8);
  tampon.writeBigUInt64BE(BigInt(sayac));
  const ozet = createHmac("sha1", sir).update(tampon).digest();
  const kaydir = ozet[ozet.length - 1] & 0x0f;
  const deger = ozet.readUInt32BE(kaydir) & 0x7fffffff;
  return String(deger % 10 ** BASAMAK).padStart(BASAMAK, "0");
}

const SIR = Buffer.from("paylasilan-ornek-sir");
const SAAT = 1_770_000_000;
console.log("zaman           dilim       kod");
for (const fark of [0, 29, 30, 60]) {
  const t = SAAT + fark;
  console.log(`  +${String(fark).padEnd(3)} saniye    ${String(Math.floor(t / ADIM)).padEnd(11)} ${totp(SIR, t)}`);
}

// 2. Saat kaymasi penceresi: sunucu kac dilim geriye/ileriye bakmali?
function dogrula(sir, sunulan, saat, pencere) {
  for (let k = -pencere; k <= pencere; k++) {
    const beklenen = totp(sir, saat + k * ADIM);
    const a = Buffer.from(sunulan), b = Buffer.from(beklenen);
    if (a.length === b.length && timingSafeEqual(a, b)) return { kabul: true, dilimFarki: k };
  }
  return { kabul: false };
}
const kullaniciSaati = SAAT - 45;                 // kullanicinin saati 45 saniye geride
const kod = totp(SIR, kullaniciSaati);
console.log("\npencere  sonuc     dilim farki   deneme sayisi");
for (const p of [0, 1, 2]) {
  const s = dogrula(SIR, kod, SAAT, p);
  console.log(`${String(p).padStart(7)}  ${(s.kabul ? "kabul" : "red").padEnd(9)} ${String(s.kabul ? s.dilimFarki : "-").padEnd(13)} ${2 * p + 1}`);
}
console.log("pencere buyudukce kabul edilen kod sayisi artar: her ek dilim tahmin alanini genisletir.");

// 3. Ayni kodun ikinci kez kullanilmasi engellenir.
const KULLANILAN = new Set();
function tekKullanim(ozne, sunulan, saat) {
  const s = dogrula(SIR, sunulan, saat, 1);
  if (!s.kabul) return "red: kod gecersiz";
  const anahtar = `${ozne}:${Math.floor((saat + s.dilimFarki * ADIM) / ADIM)}`;
  if (KULLANILAN.has(anahtar)) return "red: kod zaten kullanildi";
  KULLANILAN.add(anahtar);
  return "kabul";
}
const guncelKod = totp(SIR, SAAT);
console.log("\ntek kullanim denemesi");
console.log("  ilk sunum    -> " + tekKullanim("U-1001", guncelKod, SAAT));
console.log("  ikinci sunum -> " + tekKullanim("U-1001", guncelKod, SAAT + 5));

// 4. Etmen turleri ve kurtarma: her etmen ayni sinifa dusmemeli.
const ETMENLER = [
  { ad: "parola",                   sinif: "bilgi",  cevrimdisi: true,  devredilebilir: true },
  { ad: "zaman tabanli kod",        sinif: "sahiplik", cevrimdisi: true,  devredilebilir: false },
  { ad: "kisa mesajla kod",         sinif: "sahiplik", cevrimdisi: false, devredilebilir: false },
  { ad: "donanim guvenlik anahtari", sinif: "sahiplik", cevrimdisi: true, devredilebilir: false },
  { ad: "kurtarma kodu",            sinif: "bilgi",  cevrimdisi: true,  devredilebilir: true },
];
console.log("\netmen                       sinif      cevrimdisi  devredilebilir");
for (const e of ETMENLER)
  console.log(`  ${e.ad.padEnd(26)} ${e.sinif.padEnd(10)} ${(e.cevrimdisi ? "evet" : "hayir").padEnd(11)} ${e.devredilebilir ? "evet" : "hayir"}`);
const ayniSinif = ETMENLER.filter((e) => e.sinif === "bilgi").length;
console.log(`bilgi sinifindaki etmen: ${ayniSinif} — ikinci etmen birincinin sinifindan secilirse kazanc yoktur.`);

// 5. Kurtarma kodlari: tek kullanimlik ve karma olarak saklanir.
const kurtarma = ["3f8a-91cd", "77b2-4e0a", "c15d-8823"];
const depo = new Map(kurtarma.map((k) => [createHash("sha256").update(k).digest("hex"), "kullanilmadi"]));
function kurtarmaDene(k) {
  const h = createHash("sha256").update(k).digest("hex");
  if (!depo.has(h)) return "red: gecersiz kod";
  if (depo.get(h) === "kullanildi") return "red: kod daha once kullanilmis";
  depo.set(h, "kullanildi");
  return "kabul";
}
console.log("\nkurtarma kodu denemeleri");
for (const [ad, k] of [["gecerli kod", kurtarma[0]], ["ayni kod tekrar", kurtarma[0]], ["listede olmayan", "0000-0000"]])
  console.log(`  ${ad.padEnd(18)} -> ${kurtarmaDene(k)}`);
console.log(`kalan kullanilabilir kod: ${[...depo.values()].filter((d) => d === "kullanilmadi").length} / ${kurtarma.length}`);
EOF
node mfa.mjs
zaman           dilim       kod
  +0   saniye    59000000    942367
  +29  saniye    59000000    942367
  +30  saniye    59000001    325366
  +60  saniye    59000002    081076

pencere  sonuc     dilim farki   deneme sayisi
      0  red       -             1
      1  red       -             3
      2  kabul     -2            5
pencere buyudukce kabul edilen kod sayisi artar: her ek dilim tahmin alanini genisletir.

tek kullanim denemesi
  ilk sunum    -> kabul
  ikinci sunum -> red: kod zaten kullanildi

etmen                       sinif      cevrimdisi  devredilebilir
  parola                     bilgi      evet        evet
  zaman tabanli kod          sahiplik   evet        hayir
  kisa mesajla kod           sahiplik   hayir       hayir
  donanim guvenlik anahtari  sahiplik   evet        hayir
  kurtarma kodu              bilgi      evet        evet
bilgi sinifindaki etmen: 2 — ikinci etmen birincinin sinifindan secilirse kazanc yoktur.

kurtarma kodu denemeleri
  gecerli kod        -> kabul
  ayni kod tekrar    -> red: kod daha once kullanilmis
  listede olmayan    -> red: gecersiz kod
kalan kullanilabilir kod: 2 / 3

Zaman Tabanlı Kod Nasıl Doğrulanır

Çıktının ilk tablosu kodun zaman dilimine bağlı olduğunu gösteriyor: aynı dilim içindeki her an aynı kodu verir, dilim değiştiğinde kod değişir. Kodu üreten şey paylaşılan sır ile dilim numarasıdır; sunucu ile istemci arasında ağ üzerinden bir şey taşınmaz.

İkinci tablo saat kaymasını ölçüyor. Kullanıcının saati kırk beş saniye geride olduğunda sıfır pencereyle doğrulama başarısız oluyor; iki dilimlik pencerede kabul ediliyor ve farkın kaç dilim olduğu raporlanıyor.

Pencerenin bedeli tabloda görünüyor: her ek dilim, kabul edilen kod sayısını artırır ve tahmin alanını genişletir. Bir dilimlik pencere (toplam üç kod) yaygın bir dengedir; daha geniş pencere yerine sunucu saatinin doğru tutulması tercih edilir.

Aynı Kod İki Kez Kullanılamaz

Üçüncü bölüm tekrar kullanım savunmasını gösteriyor. Kod, geçerlilik dilimi boyunca — yaklaşık yarım dakika — geçerlidir; bu süre içinde aynı kodun ikinci kez kabul edilmesi, araya giren birinin kodu yeniden oynatmasına izin verir.

Çözüm, kabul edilen kodun dilimini kullanıcı bazında kaydetmektir. Kaydın ömrü pencere kadardır; eski kayıtlar temizlenir. Bu, Kurumsal Federasyon dersindeki tekrar kullanım listesiyle aynı kalıptır — farklı bağlamda aynı çözüm.

Deneme sınırlaması ayrı bir katmandır: altı basamaklı kod bir milyon olasılık taşır ve sınırsız deneme hakkı verilirse pencere içinde tahmin edilebilir. Ardışık başarısız denemeler hesap düzeyinde sayılır ve eşik aşıldığında ikinci etmen geçici olarak kilitlenir.

Sınıf Aynıysa Kazanç Yoktur

Dördüncü tablo etmenleri üç ölçütle karşılaştırıyor: sınıf, çevrimdışı çalışabilme ve devredilebilirlik. Son sütun en ayırt edici olanıdır. Parola ve kurtarma kodu devredilebilir: kullanıcı onları farkında olmadan başkasına söyleyebilir, bir sayfaya yazabilir. Zaman tabanlı kod da devredilebilir görünür ama otuz saniyelik ömrü devri zorlaştırır; donanım anahtarı ise devredilemez, çünkü imzalama işlemi cihazın içinde olur.

Tablonun son satırı bir kuralı sayıyla söylüyor: listedeki iki etmen bilgi sınıfındadır. İkinci etmen birincinin sınıfından seçilirse — parolanın yanına bir “gizli soru” konursa — saldırı yüzeyi değişmez, çünkü ikisi de aynı yolla ele geçirilir.

Kısa mesajla gönderilen kodun ayrı bir konumu vardır: sahiplik sınıfındadır ama çevrimdışı çalışmaz ve teslimi üçüncü bir tarafa bağlıdır. Hiç ikinci etmen olmamasından iyidir; bulunabildiğinde uygulama tabanlı kod ya da donanım anahtarı yeğlenir.

Kurtarma Kodları Hesabın Yedek Anahtarıdır

Son bölüm kurtarma kodlarını çalıştırıyor. İkinci etmen kaybedildiğinde — telefon değiştiğinde, cihaz bozulduğunda — kullanıcının hesaba dönmesi gerekir. Kurtarma kodları bu yolu açar ve tam da bu yüzden hesabın en zayıf halkasıdır.

Üç kural bunu dengeler. Kodlar tek kullanımlıktır: kullanılan kod tekrar kabul edilmez ve kalan kod sayısı kullanıcıya gösterilir. Kodlar karma olarak saklanır; veritabanı sızıntısında doğrudan kullanılamazlar. Ve kod kullanıldığında kullanıcıya bildirim gider — kurtarma akışı, hesabın ele geçirilmesinin en olası yoludur ve sessiz çalışmamalıdır.

Hesap Kurtarma dersinde kurulan sıfırlama akışıyla birlikte düşünüldüğünde ölçüt şudur: hesaba dönüş yolları, giriş yolundan daha zayıf olmamalıdır. Parolayı sıfırlamak ikinci etmeni atlatabiliyorsa, ikinci etmen yoktur.

Özet

  • Çok etmenli doğrulamanın kazancı kanıt sayısından değil, kanıtların farklı sınıflardan ve bağımsız olmasından gelir; modelde iki etmen bilgi sınıfına düşüyor.
  • Zaman tabanlı kod paylaşılan sır ile zaman diliminden türetilir; saat kayması penceresi kabul edilen kod sayısını artırır, bu yüzden dar tutulur.
  • Kabul edilen kodun dilimi kaydedilir; aynı kod ikinci kez kabul edilmez ve deneme sayısı ayrıca sınırlanır.
  • Kısa mesajla kod sahiplik sınıfındadır ama çevrimdışı çalışmaz ve teslimi üçüncü tarafa bağlıdır; uygulama tabanlı kod ya da donanım anahtarı yeğlenir.
  • Kurtarma kodları tek kullanımlıktır, karma olarak saklanır ve kullanıldığında bildirim üretir; hesaba dönüş yolu giriş yolundan zayıf olamaz.

Sonraki Adım

Bu konu boyunca sunucu, kullanıcının bildiği ya da sahip olduğu bir şeyi doğrulayabildiğini varsaydı. Parolanın kendisinin nasıl saklandığı, bu doğrulamanın hangi hesapla yapıldığı ve saklama biçiminin sızıntı durumunda ne kadar dayandığı henüz sorulmadı. Sonraki konu bu varsayımın altına iner ve parola saklamayı ölçülebilir parametrelere bağlar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat