Ders 20 / 21
Yapı Çıktısı İmzalama ve Doğrulama
Kaynak işlemesinden dağıtılan çıktıya uzanan zincir gerçek bir anahtar çiftiyle kurulur ve iki soru sayıya çevrilir: doğrulama zincirin hangi noktasında yapılırsa kaç sahte çıktı geçer, ve o anahtara erişen kaç özne imzalayabilir.
İçindekiler
Önceki ders envanteri üretti ve bir boşlukla bitti: 107 kayıtlık dosya ile tarif ettiği çıktı arasında hiçbir bağ yoktu. Aynı boşluk zincirin bütün halkalarında vardır. Hattın okuduğu kaynak işlemesi ile ürettiği yapı çıktısı arasında, o çıktı ile depoya yazılan bayt arasında, depodaki bayt ile dağıtılan şey arasında — her geçişte nesne el değiştirir ve el değiştirdiğinin kanıtı yoktur.
Bu ders o bağı kurar ve iki soruyu sayıya çevirir: doğrulama zincirin hangi noktasında yapılırsa kaç sahte çıktı geçer, ve imzayı üretebilen kaç özne vardır. M22/K02 imzalama zincirini deponun içinde kurmuş, doğrulanmayan halkanın açtığı yolu saymıştı; o kurulum tekrarlanmaz. Buradaki eksen doğrulamanın yeri ve anahtarın kimde durduğudur.
DD25. İmzalama gerçek bir anahtar çiftiyle yapılır, ama anahtar her koşumda yeniden üretilir; basılan şey doğrulama sonucudur, imzanın kendisi değil. Gerçek anahtar ya da sır yazılmaz. DD26. Zincir dört halkadır: kaynak işlemesi, yapı çıktısı, envanter ve beyan; beyan üç özeti bağlar, imza beyanı bağlar.
Zincirin Dört Halkası
// olcum-agi/zincir.mjs — kaynak islemesi, yapi ciktisi, envanter, beyan: zincir kurulur ve sinanir import { generateKeyPairSync, sign, verify, createHash } from "node:crypto"; const ozet = (b) => createHash("sha256").update(b).digest(); const kisa = (b) => ozet(b).toString("hex").slice(0, 12); // Kurgu anahtar cifti her kosumda yeniden uretilir; gercek anahtar ya da sir yazilmaz. const { publicKey: acik, privateKey: gizli } = generateKeyPairSync("ed25519"); const { privateKey: baskaGizli } = generateKeyPairSync("ed25519"); // Halka 1: kaynak islemesi — agac icerigi ozetlenir. Halka 2: yapi ciktisi. Halka 3: envanter. const kaynak = Buffer.from(["app.js", "ayar.json", "olcum.js"] .map((y) => `${y}\n// kurgu bolgesel olcum agi\n`).join("")); const cikti = Buffer.concat([Buffer.from("govde\n"), Buffer.alloc(24576, "u")]); const envanter = Buffer.from( // onceki dersin envanteri, 107 kayit Array.from({ length: 107 }, (_, i) => `bilesen-${i}@2.3`).join("\n")); // Halka 4: beyan uc ozeti baglar; imza beyani baglar. const beyanYaz = (surum, k, c, e) => Buffer.from([`servis=toplayici`, `surum=${surum}`, `kaynak=${kisa(k)}`, `cikti=${kisa(c)}`, `envanter=${kisa(e)}`].join("\n")); const BEYAN = beyanYaz(41, kaynak, cikti, envanter); const IMZA = sign(null, ozet(BEYAN), gizli); console.log(`beyan ${BEYAN.length} bayt, imza ${IMZA.length} bayt; ` + `cikti ${cikti.length} bayt, envanter ${envanter.length} bayt`); // Dogrulayici uc ayri sinama yapar: imza, cikti ozeti, envanter ozeti. const dogrula = (b, i, ac, c, e) => ({ imza: verify(null, ozet(b), ac, i), cikti: b.includes(`cikti=${kisa(c)}`), envanter: b.includes(`envanter=${kisa(e)}`), }); const bozukCikti = Buffer.from(cikti); bozukCikti[9000] = 0x41; // tek bayt const bozukEnvanter = Buffer.from(envanter.toString().replace("[email protected]", "[email protected]")); const SENARYO = [ ["bozulmamis", BEYAN, IMZA, acik, cikti, envanter], ["beyanda surum degistirilmis", beyanYaz(39, kaynak, cikti, envanter), IMZA, acik, cikti, envanter], ["ciktinin tek bayti degismis", BEYAN, IMZA, acik, bozukCikti, envanter], ["envanter degistirilmis", BEYAN, IMZA, acik, cikti, bozukEnvanter], ["baska anahtarla imzalanmis", BEYAN, sign(null, ozet(BEYAN), baskaGizli), acik, cikti, envanter], ]; const SUTUN = [32, 8, 13, 17, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(SUTUN[i])).join("")); console.log(""); satir(["senaryo", "imza", "cikti ozeti", "envanter ozeti", "kabul"]); for (const [ad, b, i, ac, c, e] of SENARYO) { const s = dogrula(b, i, ac, c, e); satir([ad, s.imza, s.cikti, s.envanter, s.imza && s.cikti && s.envanter]); }
beyan 86 bayt, imza 64 bayt; cikti 24582 bayt, envanter 1601 bayt senaryo imza cikti ozeti envanter ozeti kabul bozulmamis true true true true beyanda surum degistirilmis false true true false ciktinin tek bayti degismis true false true false envanter degistirilmis true true false false baska anahtarla imzalanmis false true true false
Bağın bedeli iki kalemdir: 86 baytlık beyan ve 64 baytlık imza. Bağladıkları 24.582 baytlık çıktı ile 1.601 baytlık envanterdir; 150 bayt, 26 kilobaytı yerine çiviler.
Tablo zincirin ayrıştığı yeri gösterir. Üç sınamanın her biri tam bir senaryoyu yakalar ve ötekiler o senaryoyu kaçırır. İmza sınaması beyandaki sürüm değiştirildiğinde ve imza başka bir anahtardan geldiğinde düşer — dört sahtelikten ikisini yakalar. Ama çıktının tek baytı değiştiğinde imza hâlâ geçerlidir: beyan bozulmamıştır, bozulan beyanın tarif ettiği şeydir. Aynı biçimde envanterdeki tek bir sürüm numarası değiştirildiğinde de imza doğrulanır.
Bu, kapının marjinal değerinin zincirin içine uygulanmış hâlidir. İmza sınaması tek başına kullanıldığında dört sahtelikten ikisi geçer. Çıktı özeti sınaması yalnız kendi senaryosunu yakalar, envanter özeti sınaması yalnız kendininkini. Üçü birlikte dördünü de tutar ve bunun bedeli üç özet hesabıdır.
Bir ayrıntı üç sınamayı da belirler: ikisi nesnenin kendisini ister. İmza yalnız beyanla sınanabilir, ama çıktı özeti çıktının baytlarını, envanter özeti envanterin baytlarını yeniden okumayı gerektirir. Nesnesi elinde olmayan bir doğrulayıcı o iki sınamayı yapamaz — ve doğrulamanın nerede yapıldığı sorusu tam olarak buradan çıkar.
Doğrulama Nerede Yapılıyor
DD27. Dağıtım denemeleri kurgudur; her deneme gerçekten imzalanır, sahtelik imzadan sonra
uygulanır ve tohum 31071 görünürdür. DD28. Otuzuncu denemeden sonra anahtar döndürülür ve
servisin yetkili öznesi değişir.
// olcum-agi/dagitim.mjs — dogrulama nerede yapilirsa kac sahte cikti geciyor import { generateKeyPairSync, sign, verify, createHash } from "node:crypto"; const ozet = (b) => createHash("sha256").update(b).digest(); const kisa = (b) => ozet(b).toString("hex").slice(0, 12); const A = generateKeyPairSync("ed25519"); // servisin ilk yetkili oznesi const B = generateKeyPairSync("ed25519"); // guvenilen ama bu servis icin yetkisiz ozne const C = generateKeyPairSync("ed25519"); // donduruldukten sonraki yetkili ozne const der = (k) => k.export({ type: "spki", format: "der" }).toString("base64"); const GUVENILEN = [A, B, C].map((k) => k.publicKey); // dogrulayicinin tanidigi anahtarlar const uretec = (tohum) => () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648; const r = uretec(31071); // tohum gorunur const SAHTE = ["depoda cikti degistirilmis", "envanter degistirilmis", "baska oznenin imzasi", "eski surumun yeniden dagitimi"]; // Kurgu dagitim denemeleri: her biri gercekten imzalanir, sahtelik imzadan SONRA uygulanir. // 30. denemeden sonra anahtar donduruldu: yetkili ozne A degil C. const DENEME = []; for (let i = 0, surum = 40; i < 60; i++) { const tur = r() < 0.6 ? "gecerli" : SAHTE[Math.floor(r() * SAHTE.length)]; const yeni = tur === "eski surumun yeniden dagitimi" ? surum - 3 : ++surum; let cikti = Buffer.alloc(4096, String.fromCharCode(97 + (i % 26))); let env = Buffer.from(`envanter-${i}\n` + "kayit\n".repeat(107)); const imzalayan = tur === "baska oznenin imzasi" ? B : (i < 30 ? A : C); const beyan = Buffer.from(`servis=toplayici\nsurum=${yeni}\n` + `cikti=${kisa(cikti)}\nenvanter=${kisa(env)}`); const imza = sign(null, ozet(beyan), imzalayan.privateKey); if (tur === "depoda cikti degistirilmis") cikti = Buffer.alloc(4096, "z"); if (tur === "envanter degistirilmis") env = Buffer.concat([env, Buffer.from("ek\n")]); DENEME.push({ tur, surum: yeni, cikti, env, beyan, imza, acik: imzalayan.publicKey }); } // Bes disiplin: zincir ayni, degisen sey dogrulamanin YERI ve ozne listesi. const ESKI = [A].map((k) => der(k.publicKey)), GUNCEL = [A, C].map((k) => der(k.publicKey)); const DISIPLIN = [ ["dogrulama yok", {}], ["yalniz hat sonunda", { hat: 1 }], ["dagitim aninda: imza ve ozetler", { imza: 1, ozet: 1 }], ["+ ozne listesi (guncellenmemis)", { imza: 1, ozet: 1, ozne: ESKI, taze: 1 }], ["+ ozne listesi (guncel)", { imza: 1, ozet: 1, ozne: GUNCEL, taze: 1 }], ]; const SAHTE_SAY = DENEME.filter((d) => d.tur !== "gecerli").length; const GECERLI_SAY = DENEME.length - SAHTE_SAY; const SUTUN = [35, 20, 20, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(SUTUN[i])).join("")); satir(["disiplin", "kabul edilen sahte", "durdurulan gecerli", "gecen sahte turu"]); for (const [ad, k] of DISIPLIN) { let sonSurum = 40, gecerliGecen = 0; const gecen = []; for (const d of DENEME) { // Hat sonu dogrulamasi: cikti uretildigi anda dogruydu; sahtelik sonra uygulandi. if (k.imza && !GUVENILEN.some((a) => verify(null, ozet(d.beyan), a, d.imza))) continue; if (k.ozet && !(d.beyan.includes(`cikti=${kisa(d.cikti)}`) && d.beyan.includes(`envanter=${kisa(d.env)}`))) continue; if (k.ozne && !k.ozne.includes(der(d.acik))) continue; if (k.taze && d.surum <= sonSurum) continue; sonSurum = Math.max(sonSurum, d.surum); d.tur === "gecerli" ? gecerliGecen++ : gecen.push(d.tur); } satir([ad, `${gecen.length}/${SAHTE_SAY}`, `${GECERLI_SAY - gecerliGecen}/${GECERLI_SAY}`, [...new Set(gecen)].map((t) => t.split(" ")[0]).join(", ") || "-"]); } console.log(""); console.log(`gecerli ${GECERLI_SAY}; ` + SAHTE.map((t) => `${t.split(" ")[0]} ${DENEME.filter((d) => d.tur === t).length}`).join(", "));
disiplin kabul edilen sahte durdurulan gecerli gecen sahte turu dogrulama yok 18/18 0/42 envanter, eski, baska, depoda yalniz hat sonunda 18/18 0/42 envanter, eski, baska, depoda dagitim aninda: imza ve ozetler 9/18 0/42 eski, baska + ozne listesi (guncellenmemis) 0/18 18/42 - + ozne listesi (guncel) 0/18 0/42 - gecerli 42; depoda 4, envanter 5, baska 6, eski 3
İlk iki satır bu dersin en sert sayısıdır: doğrulama yokken de, yalnız hat sonunda yapılırken de on sekiz sahtenin on sekizi geçer. Hat kendi ürettiği çıktıyı doğrular ve doğru bulur — çünkü o anda çıktı gerçekten doğrudur. Sahtelik imzadan sonra uygulanır. Yakalamak istediği olaydan önce duran bir kapının marjinal değeri sıfırdır ve bu, kapının zayıf olmasından değil, yanlış yerde olmasından gelir.
Üçüncü satır doğrulamayı dağıtım anına taşır ve sayı yarıya iner: 18 sahteden 9’u geçer. Tutulan dokuz tanesi depoda değiştirilen çıktı (4) ile değiştirilen envanterdir (5) — her ikisi de nesnenin baytları yeniden okunduğu için yakalanır. Geçen dokuz tanesi ise imzası geçerli olanlardır. Altısında imza başka bir öznenin geçerli anahtarındandır; doğrulayıcı “tanıdığım bir anahtar imzalamış” der ve geçirir. Üçünde her şey gerçektir, yalnız eskidir: geçmiş bir sürümün beyanı ve imzası bugün de doğrulanır.
Dördüncü satır iki kuralı ekler — imzalayan servisin yetkili öznesi olmalı, sürüm son dağıtılandan büyük olmalı — ve sahte sayısı sıfıra iner. Ama aynı satırda ikinci sayı belirir: 42 geçerli dağıtımın 18’i durdurulur. Anahtar otuzuncu denemeden sonra döndürülmüştür ve doğrulayıcının özne listesi eski anahtarda kalmıştır. Kapı sahtenin tamamını tutar ve geçerli dağıtımların %43’ünü de tutar.
Bu, kapının üçüncü sayısını yazar. Doğrulamanın kendisi bir özet hesabı ve bir liste aramasıdır; eklediği süre ölçüsüzdür. Bekleten şey doğrulama değil, listenin tazeliğidir: anahtar döndükten sonra liste güncellenene kadar her geçerli dağıtım durur. Ardışık on sekiz geçerli dağıtımı durduran bir kapının ekipte ürettiği davranış bilinir — kapı bir kez atlanır ve atlama kalıcılaşır. Son satır aynı zinciri güncel listeyle koşar: 0 sahte, 0 yanlış durdurma.
Anahtar Kaç Öznede
Dördüncü satır bir soruyu açıkta bırakır: “yetkili özne” tekil bir şey midir? İmzayı üretebilen, gizli anahtara erişebilen herkestir.
DD29. Saklama modelleri kurgudur; anahtara erişen özne sayısı modelin girdisidir.
// olcum-agi/anahtar.mjs — anahtarin kendisi bir kapidir: kac ozne imzalayabiliyor import { generateKeyPairSync, sign, verify, createHash } from "node:crypto"; const ozet = (b) => createHash("sha256").update(b).digest(); const der = (k) => k.export({ type: "spki", format: "der" }).toString("base64"); const beyan = (i) => Buffer.from(`servis=toplayici\nsurum=${100 + i}`); // Kurgu saklama modelleri: anahtar nerede duruyorsa, oraya erisen her ozne imzalayabilir. const SAKLAMA = [ ["gizli anahtar depoda", 23], ["hat calistiricisinda ortam degiskeni", 11], ["imzalama hizmeti, cagri yetkisi uc ozne", 3], ["donanimda, tek otomatik ozne", 1], ]; const SUTUN = [42, 8, 14, 15, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(SUTUN[i])).join("")); satir(["saklama", "ozne", "paylasilan", "ozne basina", "ayirt edilen"]); for (const [ad, n] of SAKLAMA) { // (a) Paylasilan tek anahtar: n ozne ayni gizli anahtarla imzalar. const tek = generateKeyPairSync("ed25519"); const paylasilan = Array.from({ length: n }, (_, i) => sign(null, ozet(beyan(i)), tek.privateKey)) .filter((im, i) => verify(null, ozet(beyan(i)), tek.publicKey, im)).length; // (b) Ozne basina anahtar: yetkili liste tek oznedir, otekiler kendi anahtarlariyla imzalar. const anahtarlar = Array.from({ length: n }, () => generateKeyPairSync("ed25519")); const LISTE = [der(anahtarlar[0].publicKey)]; const oznece = anahtarlar .map((a, i) => [a, sign(null, ozet(beyan(i)), a.privateKey), i]) .filter(([a, im, i]) => LISTE.includes(der(a.publicKey)) && verify(null, ozet(beyan(i)), a.publicKey, im)).length; // Ayirt edilen imzalayan: dogrulayicinin n imzada gordugu farkli acik anahtar sayisi. const gorulen = (d) => new Set(d).size; satir([ad, n, `${paylasilan}/${n}`, `${oznece}/${n}`, `${gorulen(Array(n).fill(der(tek.publicKey)))} -> ${gorulen(anahtarlar.map((a) => der(a.publicKey)))}`]); }
saklama ozne paylasilan ozne basina ayirt edilen gizli anahtar depoda 23 23/23 1/23 1 -> 23 hat calistiricisinda ortam degiskeni 11 11/11 1/11 1 -> 11 imzalama hizmeti, cagri yetkisi uc ozne 3 3/3 1/3 1 -> 3 donanimda, tek otomatik ozne 1 1/1 1/1 1 -> 1
Paylaşılan tek anahtarla, anahtara erişen her öznenin imzası doğrulanır: 23 özneden 23’ü, 11’den 11’i. Doğrulayıcı hiçbirini reddedemez, çünkü hepsi aynı anahtardır. Son sütun bunu niceliğe çevirir — doğrulayıcı 23 imzada tek bir açık anahtar görür. İmzadan imzalayana giden yol yoktur; “yetkili özne imzaladı” cümlesi ölçülemez bir cümledir.
Özne başına anahtar ve tek adlık bir yetkili liste, kabul sayısını özne sayısından bağımsız kılar: 23’te de 11’de de 3’te de kabul 1’dir. Ayırt edilen imzalayan sayısı ise özne sayısına eşitlenir. Aradaki fark aynı algoritmayla, aynı imza uzunluğuyla elde edilir; değişen tek şey anahtarın kaç öznede durduğudur.
M22/K02 imzalamanın güveni ortadan kaldırmadığını, anahtara taşıdığını göstermişti. Buradaki tablo o taşınmanın büyüklüğünü verir. Anahtar depoda dururken imza “depoyu okuyabilen 23 özneden biri bunu imzaladı” der; donanımda tek bir otomatik öznede dururken “o özne imzaladı” der. İki cümle arasındaki fark 23 katıdır ve önceki dersteki envanter kapsamı gibi, kapının gücünün üst sınırını kapının kendisi değil, dışındaki bir sayı belirler.
Özet
- Zincir dört halkadır ve bağın bedeli 86 baytlık beyan ile 64 baytlık imzadır; bağladıkları 24.582 baytlık çıktı ve 1.601 baytlık envanterdir.
- Üç sınamanın her biri tam bir senaryoyu yakalar: imza sınaması dört sahtelikten ikisini tutar, çıktı özeti ile envanter özeti kalan ikisini ayrı ayrı tutar. İkisi nesnenin baytlarını yeniden okumayı gerektirir.
- Doğrulama yokken 18 sahtenin 18’i geçer; yalnız hat sonunda doğrulanırken de 18’i geçer, çünkü sahtelik imzadan sonra uygulanır. Yanlış yerde duran kapının marjinal değeri sıfırdır.
- Dağıtım anında imza ve özetler sınandığında 9 sahte geçer: 6’sı başka öznenin geçerli imzası, 3’ü geçmiş sürümün yeniden dağıtımı. Özne listesi ve tazelik kuralıyla sahte sayısı 0’a iner.
- Güncellenmemiş özne listesi sahtenin tamamını tutar ama 42 geçerli dağıtımın 18’ini de durdurur; bekleten şey doğrulama değil, listenin tazeliğidir.
- Paylaşılan anahtarla 23 öznenin 23’ünün imzası kabul edilir ve doğrulayıcı tek bir açık anahtar görür; özne başına anahtarla kabul 1’e, ayırt edilen imzalayan 23’e çıkar.
Sonraki Adım
Zincir artık kurulu, dağıtım anında sınanıyor ve anahtar tek bir öznede duruyor. Ölçülen sayılar bir noktaya kadar sağlam: 0 sahte, 0 yanlış durdurma, 150 baytlık bağ. Ama o tek özne bir insan değil, hattın kendisidir — ve bu bölümdeki bütün sayılar hattın dürüst çalıştığı varsayımı üstünde durur.
O varsayım şimdiye kadar hiç sayılmadı. Hattın koştuğu iş akışı tanımı depodan gelir ve depoyu değiştirebilen kaç özne vardır? Hat çalıştırıcısı imzalama anahtarına, yapı çıktısı deposuna ve dağıtım hedefine aynı anda erişir; bir aşamanın çektiği bir bağımlılık o erişimlerin hepsini kullanabilir mi? Sonraki ders bu kursun kapılarını kapıları koyan makinenin üstüne çevirir ve aynı üç sayıyı hattın kendisi için sorar: ne tutuyor, ne kaçırıyor, ne kadar bekletiyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.