İçeriğe geç
academia.sh

Ders 14 / 23

Parola Politikaları

Karmaşıklık kurallarının kullanıcı davranışını nasıl daralttığı, dönemsel değiştirme kuralının kattığı gerçek fark, sızmış parola listesine karşı denetim ve karma öneki yöntemiyle parolayı açığa çıkarmayan sorgu.

İçindekiler

Önceki dersin gruplama sorgusu tek bir şeyi gösterdi: sekiz üyeden üçü aynı dizgiyi seçmiş ve o dizgi kutuphane idi. Tuz bu üç kaydın birbirine benzemesini engeller, yavaş karma her birinin sınanmasını pahalılaştırır — ama hiçbiri seçimin kendisini değiştirmez. Sunucu tarafındaki savunmaların bittiği yer, parola politikasının başladığı yerdir.

Politika, kullanıcıya hangi parolanın kabul edileceğini söyleyen kuraldır. Yazılması kolay görünen bu kural, kullanılabilirlikle güvenliğin en doğrudan çarpıştığı yerdir: kural sıkılaştıkça kullanıcı, kuralı en az çabayla karşılayan öngörülebilir bir yola girer. Bu ders, hangi kuralın gerçekten işe yaradığını ölçmekle ilgilidir.

Kuralın Öngördüğü Alan ve Kullanıcının Ürettiği Kalıp

Bir parola politikasının değeri, kuramsal olarak açtığı arama alanıyla (search space) ölçülmez. Ölçüt, kural karşısında kullanıcıların gerçekte ürettiği kalıbın alanıdır.

“En az sekiz karakter, büyük harf, küçük harf, rakam ve simge zorunlu” kuralını ele alalım. Kural, doksan dört karakterlik bir kümeden sekiz konum öngörür. Kullanıcının bu kuralı en az çabayla karşılama yolu ise bellidir: bildiği bir kelimeyi alır, ilk harfini büyütür, sonuna bir iki rakam ve bir simge ekler. Ortaya çıkan kalıbın alanı, kuralın öngördüğü alandan çok daha küçüktür.

Aşağıdaki betik bu farkı dört politika için hesaplıyor. Kalıp alanları bir modeldir; varsayımlar kodun içinde açıkça yazılıdır ve değiştirilerek yeniden hesaplanabilir.

cat > politika.mjs <<'EOF'
const bit = (n) => Math.log2(n);

const SOZLUK = 20000;      // kullanicinin sectigi taban kelime sayisi (model)
const politikalar = [
  {
    ad: "en az 8, dort tur karakter",
    kuramsal: 94 ** 8,
    // Kurali karsilayan en kisa yol: kelime + bas harf buyuk + 1-2 rakam + 1 simge
    kalip: SOZLUK * 1 * 110 * 10,
    aciklama: "kelime + Bas harf + rakam + simge",
  },
  { ad: "en az 12, kural yok", kuramsal: 26 ** 12,
    kalip: SOZLUK * SOZLUK * 100, aciklama: "iki kelime + rakam" },
  { ad: "en az 16, kural yok", kuramsal: 26 ** 16,
    kalip: SOZLUK ** 3 * 10, aciklama: "uc kelime + rakam" },
  { ad: "4 sozcuklu parola (2048 liste)", kuramsal: 2048 ** 4,
    kalip: 2048 ** 4, aciklama: "secim rastgele, kalip yok" },
];

console.log("politika                       | kuramsal | kalip    | kayip | kalibin kaynagi");
console.log("-------------------------------|----------|----------|-------|----------------------------");
for (const p of politikalar) {
  console.log(
    p.ad.padEnd(30) + " | " +
    (bit(p.kuramsal).toFixed(1) + " bit").padStart(8) + " | " +
    (bit(p.kalip).toFixed(1) + " bit").padStart(8) + " | " +
    (bit(p.kuramsal / p.kalip).toFixed(1)).padStart(5) + " | " + p.aciklama
  );
}

console.log("\n90 gunde bir degistirme kurali, kullanici sayaci ilerletiyorsa:");
for (const donem of [1, 4, 8, 20]) {
  const alan = SOZLUK * 110 * 10 * donem;
  console.log(`  ${String(donem).padStart(2)} donem sonra: ${bit(alan).toFixed(1)} bit ` +
    `(ilk donemden fark: ${(bit(alan) - bit(SOZLUK * 110 * 10)).toFixed(1)} bit)`);
}
EOF
node politika.mjs
politika                       | kuramsal | kalip    | kayip | kalibin kaynagi
-------------------------------|----------|----------|-------|----------------------------
en az 8, dort tur karakter     | 52.4 bit | 24.4 bit |  28.0 | kelime + Bas harf + rakam + simge
en az 12, kural yok            | 56.4 bit | 35.2 bit |  21.2 | iki kelime + rakam
en az 16, kural yok            | 75.2 bit | 46.2 bit |  29.0 | uc kelime + rakam
4 sozcuklu parola (2048 liste) | 44.0 bit | 44.0 bit |   0.0 | secim rastgele, kalip yok

90 gunde bir degistirme kurali, kullanici sayaci ilerletiyorsa:
   1 donem sonra: 24.4 bit (ilk donemden fark: 0.0 bit)
   4 donem sonra: 26.4 bit (ilk donemden fark: 2.0 bit)
   8 donem sonra: 27.4 bit (ilk donemden fark: 3.0 bit)
  20 donem sonra: 28.7 bit (ilk donemden fark: 4.3 bit)

Birinci satır politikanın en yaygın biçimini gösteriyor: kural elli iki bit vaat ediyor, kullanıcının kalıbı yirmi dört bit veriyor. Aradaki yirmi sekiz bitlik fark, kuralın kullanıcıyı ittiği yönün sonucudur — kural karmaşıklığı zorunlu kıldığı için kullanıcı karmaşıklığı en ucuz yerden, sondan ekler.

İkinci ve üçüncü satır uzunluğun ne yaptığını gösteriyor. Hiçbir karakter kuralı olmadan, yalnız asgari uzunluğu artırmak kalıp alanını yirmi dört bitten otuz beşe, sonra kırk altıya çıkarıyor. Uzunluk, kullanıcıyı daha fazla seçim yapmaya zorlar; karmaşıklık ise aynı seçimi süslemeye zorlar.

Dördüncü satır sınırı gösteriyor: seçim kullanıcıya değil rastgele üretece bırakıldığında kalıp diye bir şey kalmaz ve kuramsal alan gerçek alana eşit olur. Kırk dört bit, birincisinden yirmi bit fazladır ve akılda tutulması ondan daha kolaydır.

Dönemsel Değiştirme Kuralı

Çıktının ikinci bölümü, yaygın bir işletim kuralının bilançosudur. Doksan günde bir parola değiştirmeyi zorunlu kılan kural, kullanıcı sondaki sayacı ilerlettiğinde yirmi dönem sonunda toplam dört buçuk bit katar. Aynı kullanıcının aynı parolayı yirmi kez yazması karşılığında elde edilen budur.

Karşı hanede duran maliyet ölçülebilir değil ama gözlenebilirdir: her dönem başında artan sıfırlama istekleri, yazılı not alma davranışı ve sondaki sayacın öngörülebilirliği. Dönemsel değiştirme kuralının savunulabilir tek biçimi koşulludur — parolanın sızdığına dair bir belirti varsa değiştirme zorunlu kılınır. Takvime bağlı zorunlu değiştirme, ölçülen katkısı kadar zarar üretir.

Sızmış Parola Listesine Karşı Denetim

Kalıp tablosunun asıl sonucu şudur: kullanıcının seçimini kurallarla yönlendirmek zayıf bir araçtır. Güçlü araç, seçimi doğrudan reddetmektir. Daha önce sızmış olduğu bilinen bir parola, kaç kural karşılarsa karşılasın, aday listelerinin içindedir ve reddedilmelidir.

Bu denetimin uygulanmasında bir sorun vardır: liste büyüktür ve kullanıcının seçtiği parolanın listeyi tutan tarafa gönderilmesi kabul edilemez. Çözüm, sorguyu parolanın kendisi yerine karmasının bir öneki (prefix) üzerinden yapmaktır. Sunucu, önekle eşleşen tüm kayıtları döndürür; kullanıcının parolasının o küme içinde olup olmadığına istemci kendi belleğinde karar verir. Sunucunun öğrendiği tek şey önektir; hangi kaydın arandığı belirsiz kalır. Bu belirsizliğe k-anonimlik (k-anonymity) denir ve k, dönen kümedeki kayıt sayısıdır.

Önek uzunluğu bir ödünleşimdir. Kısa önek daha büyük kova döndürür — daha çok gizlilik, daha çok veri aktarımı. Uzun önek tersini yapar. Aşağıdaki betik bu ödünleşimi yerel bir liste üzerinde ölçüyor.

cat > onek.mjs <<'EOF'
import { createHash } from "node:crypto";

const kok = ["kutuphane","kitap","okuma","sube","deniz","cinar","yaz","kis","uye","odunc",
             "masa","raf","kart","ceza","iade","katalog","salon","kalem","defter","not"];

function* liste(n = 200000) {                     // yerel, uretilmis ornek liste
  yield* ["kutuphane", "sahil-subesi", "odunc123"];
  for (let i = 0; i < n; i++) {
    yield `${kok[i % 20]}-${kok[Math.floor(i / 20) % 20]}-${Math.floor(i / 400) % 500}`;
  }
}

const karmalar = [...liste()].map((p) =>
  createHash("sha1").update(p).digest("hex").toUpperCase());
console.log(`yerel liste: ${karmalar.length.toLocaleString("tr-TR")} kayit`);

console.log("\nonek | kova sayisi | ortalama kova | en buyuk kova | sunucunun gordugu");
console.log("-----|-------------|---------------|---------------|-------------------");
for (const uzunluk of [2, 3, 4, 5]) {
  const kova = new Map();
  for (const h of karmalar) {
    const o = h.slice(0, uzunluk);
    kova.set(o, (kova.get(o) ?? 0) + 1);
  }
  const boyutlar = [...kova.values()];
  const ort = boyutlar.reduce((a, b) => a + b, 0) / boyutlar.length;
  const enbuyuk = boyutlar.reduce((a, b) => (b > a ? b : a), 0);
  console.log(
    ` ${uzunluk}   | ${String(kova.size).padStart(11)} | ${ort.toFixed(1).padStart(13)} | ` +
    `${String(enbuyuk).padStart(13)} | ${(uzunluk * 4)} bit / 160 bit`
  );
}
EOF
node onek.mjs
yerel liste: 200.003 kayit

onek | kova sayisi | ortalama kova | en buyuk kova | sunucunun gordugu
-----|-------------|---------------|---------------|-------------------
 2   |         256 |         781.3 |           843 | 8 bit / 160 bit
 3   |        4096 |          48.8 |            74 | 12 bit / 160 bit
 4   |       62463 |           3.2 |            13 | 16 bit / 160 bit
 5   |      181985 |           1.1 |             5 | 20 bit / 160 bit

Tablo önek uzunluğunun liste boyutuna bağlı seçildiğini gösteriyor. İki yüz bin kayıtlık bu listede dört karakterlik önek ortalama üç kayıt döndürüyor; bu, k-anonimliğin anlamlı olmadığı bir kova boyutudur. Üç karakter, ortalama kırk dokuz kayıtlık kova verir ve sunucu yüz altmış bitlik karmanın yalnız on ikisini görür. Liste milyarlarca kayda çıktığında aynı kova boyutuna beş karakterlik önekle ulaşılır — kural, önek uzunluğunun kova boyutunu hedeflenen k değerinde tutacak biçimde seçilmesidir.

Buradaki karma fonksiyonunun seçimi ayrı bir noktadır. Bu hesap parola saklamak için değil, kayıtları önek kovalarına dağıtmak için yapılır; hız burada bir kusur değil gerekliliktir. Önceki iki dersin yavaş karma kuralı saklama yolunda geçerlidir, bu dizin anahtarında değil.

Sorgunun Çalıştırılması

Aşağıdaki blok yerel bir aralık servisi ayağa kaldırıyor ve iki parolayı denetliyor: önceki dersteki üyelerin paylaştığı kutuphane ile U-1001’in seçtiği deniz-kabugu-7. İstekte giden tek şey üç karakterlik önektir.

cat > aralik-sunucu.mjs <<'EOF'
import { createServer } from "node:http";
import { createHash } from "node:crypto";

const kok = ["kutuphane","kitap","okuma","sube","deniz","cinar","yaz","kis","uye","odunc",
             "masa","raf","kart","ceza","iade","katalog","salon","kalem","defter","not"];
function* liste(n = 200000) {
  yield* ["kutuphane", "sahil-subesi", "odunc123"];
  for (let i = 0; i < n; i++) {
    yield `${kok[i % 20]}-${kok[Math.floor(i / 20) % 20]}-${Math.floor(i / 400) % 500}`;
  }
}

const ONEK = 3;                                   // bu liste boyutuna gore secildi
const kova = new Map();
for (const p of liste()) {
  const h = createHash("sha1").update(p).digest("hex").toUpperCase();
  const o = h.slice(0, ONEK);
  if (!kova.has(o)) kova.set(o, []);
  kova.get(o).push(h.slice(ONEK));
}

createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;
  if (yol === "/saglik") return yanit.writeHead(200).end("hazir\n");
  const eslesme = /^\/aralik\/([0-9A-F]{3})$/.exec(yol);
  if (!eslesme) return yanit.writeHead(404).end("");
  yanit.writeHead(200, { "content-type": "text/plain" });
  yanit.end((kova.get(eslesme[1]) ?? []).join("\n") + "\n");
}).listen(8493, "127.0.0.1");
EOF
node aralik-sunucu.mjs & sunucu=$!
until curl -sf http://127.0.0.1:8493/saglik > /dev/null; do :; done

karma() { node -e 'const {createHash}=require("node:crypto");
  process.stdout.write(createHash("sha1").update(process.argv[1]).digest("hex").toUpperCase())' "$1"; }

for parola in kutuphane deniz-kabugu-7; do
  tam=$(karma "$parola"); onek=${tam:0:3}; kuyruk=${tam:3}
  curl -s "http://127.0.0.1:8493/aralik/$onek" -o kova.txt
  echo "parola      : $parola"
  echo "  giden onek: $onek   (tam karmanin ilk 3 karakteri)"
  echo "  donen kova: $(wc -l < kova.txt | tr -d ' ') kuyruk"
  if grep -qx "$kuyruk" kova.txt; then echo "  sonuc     : listede VAR, reddedilir"
  else echo "  sonuc     : listede yok, kabul edilir"; fi
done

kill $sunucu
rm -f kova.txt aralik-sunucu.mjs
parola      : kutuphane
  giden onek: 2E5   (tam karmanin ilk 3 karakteri)
  donen kova: 51 kuyruk
  sonuc     : listede VAR, reddedilir
parola      : deniz-kabugu-7
  giden onek: 5B8   (tam karmanin ilk 3 karakteri)
  donen kova: 54 kuyruk
  sonuc     : listede yok, kabul edilir

Kararı veren taraf istemcidir; sunucu yalnız bir kova döndürür ve hangi kaydın sorulduğunu bilmez. Önek değerleri listeye ve karma fonksiyonuna bağlıdır, kova boyutları da öyle; akış değişmez. Aynı yordam kendi altyapınızda tutulan bir listeyle de çalışır — o durumda liste zaten sizdedir ve önek katmanı, denetimi yapan servisin parolayı hiç görmemesini sağlar.

Denetimin nerede yapılacağı ayrı bir karardır. Kayıt ve parola değiştirme akışlarında denetim zorunludur. Giriş akışında da yapılabilir: kullanıcı doğru parolayla girdiğinde parola elde olur ve listeye karşı denetlenebilir; listede çıkarsa kullanıcıya değiştirme istemi gösterilir. Bu, önceki derslerdeki parametre yükseltmesiyle aynı yerde yapılan bir işlemdir.

Politikanın Yazılı Hâli

Buraya kadarki ölçümler tek bir politikaya varır.

Asgari uzunluk konur, azami uzunluk konmaz. Sekiz karakterlik alt sınır düşüktür; on iki daha savunulabilirdir. Üst sınır ise gereksizdir — karma zaten sabit uzunlukta çıktı üretir. Kırk karakterde kesen bir alan, parola yöneticisi kullanan kullanıcıyı elle yazmaya zorlar.

Karakter türü zorunluluğu konmaz. Ölçülen etkisi, kullanıcıyı öngörülebilir bir kalıba itmektir. Kuralın yerini uzunluk ve liste denetimi alır.

Tüm karakterler kabul edilir. Boşluk, noktalama ve Türkçe harfler dâhil. Kısıtlanan her karakter kümesi, kullanıcının seçim alanını daraltır. Önceki derste kurulan normalleştirme adımı bu kabulü güvenli kılar.

Yapıştırma engellenmez. Yapıştırmayı engelleyen alan, parola yöneticisi kullanımını imkânsızlaştırır ve kullanıcıyı akılda tutulabilir — yani zayıf — parolaya iter.

Geri bildirim yazarken verilir. Kullanıcı parolayı yazarken kabul edilip edilmeyeceğini görmelidir; gönderdikten sonra reddedilen bir form, kullanıcıyı kuralı en az çabayla karşılamaya iter.

Liste denetimi zorunludur. Sızmış parola listesine karşı denetim, tüm karakter kurallarının toplamından daha etkilidir ve yukarıdaki yöntemle parolayı açığa çıkarmadan yapılır.

Özet

  • Politikanın değeri kuramsal arama alanıyla değil, kural karşısında kullanıcıların ürettiği kalıbın alanıyla ölçülür.
  • Karakter türü zorunluluğu kalıp alanını daraltır; asgari uzunluğu artırmak aynı kullanıcı davranışında daha geniş alan üretir.
  • Takvime bağlı dönemsel değiştirme yirmi dönemde dört buçuk bit katar; savunulabilir biçimi, sızıntı belirtisine bağlı koşullu değiştirmedir.
  • Sızmış parola listesine karşı denetim, kullanıcının seçimini kurallarla yönlendirmekten daha etkilidir.
  • Karma önekiyle sorgulama, parolayı ve tam karmayı açığa çıkarmadan denetim yapar; önek uzunluğu, kova boyutunu hedeflenen k değerinde tutacak biçimde seçilir.

Sonraki Adım

Parola ne kadar iyi seçilirse seçilsin unutulur ve her sistem bir kurtarma yolu sunmak zorundadır. Bu yol, kimlik doğrulamanın tamamını atlayan ikinci bir kapıdır: doğru kurulmazsa, parolayı hiç bilmeyen birinin hesaba girmesine yeter. Sonraki ders sıfırlama akışının saldırı yüzeyini ele alıyor — belirtecin tek kullanımlık, kısa ömürlü ve karma olarak saklanması, kullanıcı numaralandırmasına karşı aynı yanıtın döndürülmesi ve sıfırlamanın oturumlar üzerindeki etkisi.

İ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