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
kdeğ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.