Ders 08 / 12
Yapı Çıktısı İmzalama
İmza gerçek bir anahtar çiftiyle üretilip doğrulanır ve kanıtladığı ile kanıtlamadığı ayrılır: sekiz iddianın kaçı imzayla kanıtlanıyor, anahtar ele geçtiğinde kaç çıktı geriye dönük olarak şüpheli hale geliyor ve beyan genişlediğinde imza kapsamı ile doğrulayıcının sınayabildiği alan nasıl ayrışıyor.
İçindekiler
Önceki ders envanteri üretti ve cevaplanmayan dört sorudan biriyle bitti: bu envanter hangi yapı çıktısını tarif ediyor? Aynı boşluk envanterle sınırlı değildir. Hattın ürettiği yapı çıktısı bir depoya yazılır, oradan alınır ve dağıtılır; her el değiştirmede nesne aynı kalmış gibi kabul edilir ve kaldığının kanıtı yoktur.
Bu ders o kanıtı gerçek bir anahtar çiftiyle üretir. Sürekli Tümleştirme ve Teslimat kursunda imza zinciri kurulmuş, doğrulamanın hattın hangi noktasında yapıldığı ve anahtarın kaç öznede durduğu ölçülmüştü; o ölçüm tekrarlanmaz. Buradaki eksen iddiadır: doğrulaması geçen bir çıktı için hangi cümleler söylenebilir ve bunların kaçı imzanın gerçekten dayandığı cümledir.
TZ6. İmzalama gerçek 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. TZ7. Doğrulayıcının elindeki nesne kümesi üç kalemdir: beyan, imza ve yapı çıktısının baytları. Kaynak ağacı, yapı tanımı ve envanter elinde yoktur.
Sekiz İddia
// olcum-agi/iddia.mjs — imzanin kanitladigi ile kanitlamadigi: sekiz iddia 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); // Anahtar ciftleri her kosumda yeniden uretilir; gercek anahtar ya da sir yazilmaz. const YETKILI = generateKeyPairSync("ed25519"); const BASKA = generateKeyPairSync("ed25519"); // taninan ama bu servis icin yetkisiz ozne const GUVENILEN = [YETKILI.publicKey, BASKA.publicKey]; // dogrulayicinin tanidigi anahtarlar const CIKTI = Buffer.concat([Buffer.from("abone-portali govde\n"), Buffer.alloc(32768, "u")]); const beyanYaz = (c) => Buffer.from(["servis=abone-portali", `cikti=${kisa(c)}`].join("\n")); const BEYAN = beyanYaz(CIKTI); const IMZA = sign(null, ozet(BEYAN), YETKILI.privateKey); // Dogrulayicinin elindeki nesne kumesi: beyan, imza, yapi ciktisi. Baska nesne yok. const dogrula = (beyan, imza, cikti) => GUVENILEN.some((a) => verify(null, ozet(beyan), a, imza)) && beyan.includes(`cikti=${kisa(cikti)}`); // Her iddia icin bir kurgu senaryo: dogrulama gecerken iddia dogru mu? const IDDIA = [ ["beyan taninan bir anahtarla imzalandi", () => [dogrula(BEYAN, IMZA, CIKTI), true]], ["beyanin baytlari imzadan sonra degismedi", () => [dogrula(Buffer.from(BEYAN.toString().replace("abone", "sayac")), IMZA, CIKTI), false]], ["tarif edilen ciktinin baytlari degismedi", () => { const bozuk = Buffer.from(CIKTI); bozuk[12000] = 0x41; // tek bayt return [dogrula(BEYAN, IMZA, bozuk), false]; }], ["imzalayan bu servis icin yetkiliydi", () => [dogrula(BEYAN, sign(null, ozet(BEYAN), BASKA.privateKey), CIKTI), false]], ["cikti su kaynak islemesinden uretildi", () => [dogrula(BEYAN, IMZA, CIKTI), false]], ["imza su tarihte uretildi", () => [dogrula(BEYAN, IMZA, CIKTI), false]], ["cikti hat denetimlerinden gecti", () => [dogrula(BEYAN, IMZA, CIKTI), false]], ["icindeki bilesenler envanterdekilerdir", () => [dogrula(BEYAN, IMZA, CIKTI), false]], ]; const S = [42, 12, 13, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(S[i])).join("")); satir(["iddia", "dogrulama", "iddia dogru", "imza kanitliyor mu"]); let kanit = 0; for (const [ad, calistir] of IDDIA) { const [gecti, dogruMu] = calistir(); const kanitliyor = gecti === dogruMu; // dogrulama iddiayla ayni yone gidiyor mu if (kanitliyor) kanit++; satir([ad, gecti ? "geciyor" : "dusuyor", dogruMu ? "evet" : "hayir", kanitliyor ? "evet" : "HAYIR"]); } console.log(""); console.log(`beyan ${BEYAN.length} bayt, imza ${IMZA.length} bayt, cikti ${CIKTI.length} bayt`); console.log(`sekiz iddianin ${kanit}'i imzayla kanitlaniyor, ${IDDIA.length - kanit}'i ` + `kanitlanmiyor: dogrulama geciyor, iddia yanlis`);
iddia dogrulama iddia dogru imza kanitliyor mu beyan taninan bir anahtarla imzalandi geciyor evet evet beyanin baytlari imzadan sonra degismedi dusuyor hayir evet tarif edilen ciktinin baytlari degismedi dusuyor hayir evet imzalayan bu servis icin yetkiliydi geciyor hayir HAYIR cikti su kaynak islemesinden uretildi geciyor hayir HAYIR imza su tarihte uretildi geciyor hayir HAYIR cikti hat denetimlerinden gecti geciyor hayir HAYIR icindeki bilesenler envanterdekilerdir geciyor hayir HAYIR beyan 39 bayt, imza 64 bayt, cikti 32788 bayt sekiz iddianin 3'i imzayla kanitlaniyor, 5'i kanitlanmiyor: dogrulama geciyor, iddia yanlis
İlk üç satır imzanın gerçekten yaptığı işi verir. Doğrulama geçtiğinde şu üç cümle kesindir: beyan tanınan bir anahtarla imzalanmıştır, imzadan sonra beyanın baytları değişmemiştir ve beyanın özetle bağladığı çıktının baytları değişmemiştir. İlk ikisi 39 baytlık beyanla, üçüncüsü 32.788 baytlık çıktının yeniden okunmasıyla sınanır. İmzanın kanıtladığı üç cümle bunlardır.
Kalan beş satırın hepsinde doğrulama geçer ve iddia yanlıştır. Dördüncü satırda beyan geçerli bir anahtarla imzalanmıştır, ama imzalayan bu servisin yetkili öznesi değildir; doğrulayıcı “tanıdığım bir anahtar” der ve geçirir — yetki, imzanın değil, anahtar listesinin taşıdığı bir karardır. Beşinci, altıncı, yedinci ve sekizinci satırlarda beyan o cümleleri hiç kurmaz: kaynak işlemesinden, imzanın üretildiği andan, hat denetimlerinden ve çıktının içindeki bileşenlerden 39 baytlık beyanda tek kelime geçmez. Doğrulama geçer, çünkü doğrulamanın bakacağı bir alan yoktur.
Bu ayrım tek cümleye iner: imza “bu çıktı şu anahtarla imzalandı” der, “bu çıktı güvenli” demez. İkinci cümlenin doğru olması imzayla ilgili değildir; imzanın altındaki beyanda ne yazdığıyla, o yazının kim tarafından doldurulduğuyla ve doğrulayıcının o alanı sınayıp sınayamadığıyla ilgilidir.
Üç kanıtlanan cümlenin bedeli de eşit değildir. İlk ikisi yalnız beyanla sınanır: 39 baytlık beyan ve 64 baytlık imza, toplam 103 bayt okuma. Üçüncüsü çıktının baytlarını yeniden okumayı gerektirir — 32.788 bayt, yani 318 katı. Bu oran doğrulamanın nerede yapılabileceğini belirler: nesnesi elinde olmayan bir doğrulayıcı üçüncü cümleyi hiç kuramaz ve elinde yalnız beyan olan bir kapı, imzayı doğruladığı hâlde tek baytı değişmiş bir çıktıyı geçirir.
Anahtar Ele Geçtiğinde
İmzanın kanıtladığı üç cümle bile bir koşula bağlıdır: gizli anahtarı yalnız yetkili öznenin kullanabildiği. O koşul kalktığında sorulacak sayı şudur — kaç çıktı şüpheli hale gelir?
TZ8. Yüz yirmi kurgu hat koşumu birer çıktı üretip imzalar; anahtarı ele geçiren özne dört çıktıyı daha imzalar ve bu dördü hiçbir hat koşumu değildir. TZ9. Yalnız ekleme yapılan yayım kaydı her girdiyi bir önceki zincir özetiyle birlikte özetler; geçmişe girdi sokmak zinciri kırar.
// olcum-agi/anahtar-kaybi.mjs — anahtar ele gectiginde kac cikti supheli hale geliyor 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 ANAHTAR = generateKeyPairSync("ed25519"); // her kosumda yeniden uretilir // Kurgu: yuz yirmi hat kosumu, her biri bir cikti uretip imzaliyor. Kosum kimligi hatta kayitli. const KOSUM = 120, SAHTE = 4; const kayitYap = (no, kimlik) => { const cikti = Buffer.alloc(4096, String.fromCharCode(97 + (no % 26))); const beyan = Buffer.from(`servis=abone-portali\nkosum=${kimlik}\ncikti=${kisa(cikti)}`); return { no, kimlik, beyan, imza: sign(null, ozet(beyan), ANAHTAR.privateKey) }; }; const gercek = Array.from({ length: KOSUM }, (_, i) => kayitYap(i + 1, `k-${i + 1}`)); // Anahtari ele geciren ozne dort cikti imzalar; bunlarin hicbiri bir hat kosumu degildir. const sahte = Array.from({ length: SAHTE }, (_, i) => kayitYap(1000 + i, `x-${i + 1}`)); // Yalniz ekleme yapilan yayim kaydi: her girdi bir onceki zincir ozetini de ozetler. const zincirle = (kayitlar) => kayitlar.reduce((z, k) => [...z, ozet(Buffer.concat([z.at(-1) ?? Buffer.alloc(32), k.beyan, k.imza]))], []); const ZINCIR = zincirle(gercek); // Uc kayit turu. Sahte kayitlar duz listede araya sokulur, zincirde de ayni yere sokulur. const araya = [...gercek.slice(0, 60), ...sahte, ...gercek.slice(60)]; const kirikIndis = zincirle(araya).findIndex((z, i) => i < ZINCIR.length && !z.equals(ZINCIR[i])); const gecen = [...gercek, ...sahte] .filter((k) => verify(null, ozet(k.beyan), ANAHTAR.publicKey, k.imza)).length; const S = [34, 15, 14, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(S[i])).join("")); satir(["kayit turu", "dogrulamayi", "araya sokma", "supheli cikti"]); satir(["kayit yok, yalniz imza", gecen, "gorulmez", `${gecen}/${gecen}`]); satir(["duz kosum listesi", gecen, "gorulmez", `${gecen}/${gecen}`]); satir(["zincirli yayim kaydi", gecen, kirikIndis >= 0 ? `indis ${kirikIndis}` : "gorulmez", `${SAHTE}/${gecen}`]); // Zincir gecmisi dondurur: gecmise sokulan girdi kirilma verir, sonuna eklenen gizlenemez. console.log(""); console.log(`imzalanan cikti ${gercek.length} + ${sahte.length} = ${gecen}; ` + `hepsi ayni anahtarla, hepsi dogrulamayi geciyor`); console.log(`zincir ${ZINCIR.length} girdi; gecmise sokulan ${SAHTE} girdi ` + `zinciri indis ${kirikIndis}'ta kiriyor, duz listede iz birakmiyor`); console.log(`geriye donuk suphe: kayitsiz ${gecen} cikti, zincirli kayitla ${SAHTE} cikti`);
kayit turu dogrulamayi araya sokma supheli cikti kayit yok, yalniz imza 124 gorulmez 124/124 duz kosum listesi 124 gorulmez 124/124 zincirli yayim kaydi 124 indis 60 4/124 imzalanan cikti 120 + 4 = 124; hepsi ayni anahtarla, hepsi dogrulamayi geciyor zincir 120 girdi; gecmise sokulan 4 girdi zinciri indis 60'ta kiriyor, duz listede iz birakmiyor geriye donuk suphe: kayitsiz 124 cikti, zincirli kayitla 4 cikti
Üç satırın ilk sütunu aynıdır ve dersin en sert sayısı odur: 124 çıktının 124’ü doğrulamayı geçer. Dört sahte çıktı gerçek anahtarla imzalanmıştır; doğrulayıcının onları ayırt etmesini sağlayacak hiçbir şey yoktur, çünkü imza yalnız “bu anahtarla imzalandı” der ve bu doğrudur.
Fark son sütunda çıkar. Kayıt tutulmayan düzende anahtarın ele geçtiği duyulduğunda hangi imzanın ne zaman üretildiği bilinemez; hiçbir çıktı için “bu, ele geçmeden önce imzalanmıştı” cümlesi kurulamaz ve 124 çıktının 124’ü şüpheli olur. Bu, kayıt tutmamanın gerçek bedelidir: kayıp imzanın kendisinde değil, ondan sonra yapılması gereken işin büyüklüğündedir.
Düz bir koşum listesi bunu değiştirmez. Liste sıradan bir dosyadır; dört girdi araya sokulur ve hiçbir iz kalmaz — liste artık dördünü de hattın koşumu gibi gösterir. Zincirli yayım kaydında aynı sokma indis 60’ta zinciri kırar. Zincirin yaptığı iş imzalamayı engellemek değildir; engelleyemez de. Yaptığı iş geçmişi dondurmaktır: sonradan üretilen bir imza ancak sonuna eklenebilir, geçmişe yerleştirilemez. Şüpheli çıktı sayısı 124’ten 4’e iner ve bu 4, kaydın sonundaki eşleşmeyen girdilerdir.
İki sayı arasındaki fark tespitte değil, şüphenin kapsamındadır. Her iki düzende de dört sahte çıktı üretilmiştir ve her ikisinde de doğrulama onları geçirir. Değişen şey, olay anlaşıldıktan sonra yapılacak iştir: kayıtsız düzende 124 çıktının hepsi yeniden üretilip yeniden imzalanmalıdır, zincirli kayıtta yalnız 4’ü. Bu, imzalamanın hattan aldığı sürenin karşılığının nerede ödendiğini gösterir — imza atmanın maliyeti koşum başına birkaç saniyedir, kayıt tutmamanın maliyeti tek bir olayda 120 çıktılık bir yeniden üretimdir.
Beyan Genişlediğinde
Beş kanıtlanmayan iddianın dördü beyanda o alanların hiç bulunmamasından geliyordu. Alanları eklemek yeter mi?
TZ10. Beyan dokuz alana çıkarılır ve dört alana kasten yanlış değer yazılır; beyan gerçekten imzalanır. Doğrulayıcının elindeki tek nesne yine yapı çıktısıdır.
// olcum-agi/beyan-genisletme.mjs — beyan genisleyince imza kapsami ile sinanabilirlik ayrisir 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"); const CIKTI = Buffer.concat([Buffer.from("abone-portali govde\n"), Buffer.alloc(32768, "u")]); // Dogrulayicinin elinde yalnizca yapi ciktisi var; kaynak agaci, yapi tanimi ve envanter yok. const ELDEKI = { cikti: CIKTI }; // Dokuz alan. `dogru` alanin gercek degeri, `yazilan` beyana konan deger. const ALAN = [ ["servis", "abone-portali", "abone-portali"], ["cikti", kisa(CIKTI), kisa(Buffer.alloc(8, "z"))], // kasten yanlis ["kaynak", "9f31c0aa4b12", "0000deadbeef"], // kasten yanlis ["yapi-tanimi", "51ba7c0e93d4", "51ba7c0e93d4"], ["envanter", "c4e0a71f2b88", "c4e0a71f2b88"], ["temel-imaj", "7ad3e1c95006", "111122223333"], // kasten yanlis ["kosum", "k-118", "k-118"], ["zaman", "yapi-basi", "yapi-basi"], ["denetim", "iki bulgu acik", "bulgu yok"], // kasten yanlis ]; const beyan = Buffer.from(ALAN.map(([a, , y]) => `${a}=${y}`).join("\n")); const imza = sign(null, ozet(beyan), A.privateKey); const imzaGecti = verify(null, ozet(beyan), A.publicKey, imza); const S = [14, 18, 24, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(S[i])).join("")); satir(["alan", "beyandaki deger", "yeniden hesaplanabilir", "yakalandi mi"]); let sinanabilir = 0, yanlis = 0, yakalanan = 0; for (const [ad, dogru, yazilan] of ALAN) { // Bir alan ancak dogrulayicinin elindeki bir nesneden yeniden hesaplanabiliyorsa sinanir. const hesaplanir = ad in ELDEKI; const dogruMu = dogru === yazilan; if (hesaplanir) sinanabilir++; if (!dogruMu) yanlis++; const yakala = hesaplanir && !dogruMu && kisa(ELDEKI[ad]) !== yazilan; if (yakala) yakalanan++; satir([ad, dogruMu ? "dogru" : "YANLIS", hesaplanir ? "evet" : "hayir", yakala ? "evet" : (dogruMu ? "-" : "HAYIR")]); } console.log(""); console.log(`beyan ${ALAN.length} alan, ${beyan.length} bayt; imza dogrulamasi ` + `${imzaGecti ? "geciyor" : "dusuyor"}`); console.log(`imzanin kapsadigi alan ${ALAN.length}/${ALAN.length}, ` + `dogrulayicinin sinayabildigi alan ${sinanabilir}/${ALAN.length}`); console.log(`kasten yanlis yazilan ${yanlis} alanin ${yakalanan}'i yakalandi, ` + `${yanlis - yakalanan}'u beyanla birlikte imzalandi ve gecti`);
alan beyandaki deger yeniden hesaplanabilir yakalandi mi servis dogru hayir - cikti YANLIS evet evet kaynak YANLIS hayir HAYIR yapi-tanimi dogru hayir - envanter dogru hayir - temel-imaj YANLIS hayir HAYIR kosum dogru hayir - zaman dogru hayir - denetim YANLIS hayir HAYIR beyan 9 alan, 176 bayt; imza dogrulamasi geciyor imzanin kapsadigi alan 9/9, dogrulayicinin sinayabildigi alan 1/9 kasten yanlis yazilan 4 alanin 1'i yakalandi, 3'u beyanla birlikte imzalandi ve gecti
Beyan iki alandan dokuza çıktı ve imzanın kapsadığı alan 9/9 oldu: dokuzunun hiçbiri imzadan sonra değiştirilemez. Doğrulayıcının sınayabildiği alan ise 1/9’da kaldı. Aradaki fark dersin son ayrımıdır: imza kapsamı ile doğrulanabilirlik ayrı iki niceliktir. İlki beyanı yazan tarafın kararıdır, ikincisi doğrulayanın elindeki nesne kümesinin.
Son satır bunun sonucudur. Dört alan kasten yanlış yazılmıştır; yalnız biri — çıktı özeti — yakalanır, çünkü doğrulayıcı çıktının baytlarını okuyup yeniden hesaplayabilir. Diğer üçü yakalanmaz ve daha kötüsü, imzalanmış olarak geçer. İmzalı bir alan, imzasız bir alandan daha güvenilir görünür; oysa imza yalnız “bu değer sonradan değiştirilmedi” der, “bu değer doğru yazıldı” demez.
Özet
- Doğrulaması geçen bir çıktı için sekiz iddiadan 3’ü imzayla kanıtlanır: beyan tanınan bir anahtarla imzalandı, beyanın baytları değişmedi, özetle bağlanan çıktının baytları değişmedi.
- Kalan 5 iddiada doğrulama geçer ve iddia yanlıştır: yetki anahtar listesinin kararıdır, kaynak işlemesi, imzalama anı, denetim sonucu ve bileşen listesi 39 baytlık beyanda hiç geçmez.
- Anahtar ele geçtiğinde 124 çıktının 124’ü doğrulamayı geçer; kayıt tutulmayan düzende hiçbiri için “ele geçmeden önce imzalanmıştı” denemez ve 124’ü şüpheli olur.
- Düz koşum listesine dört girdi iz bırakmadan sokulur; zincirli yayım kaydında aynı sokma indis 60’ta zinciri kırar ve şüpheli çıktı 4’e iner. Zincir imzalamayı engellemez, geçmişi dondurur.
- Beyan dokuz alana çıktığında imzanın kapsadığı alan 9/9, doğrulayıcının sınayabildiği alan 1/9’dur; kasten yanlış yazılan dört alanın üçü imzalanmış olarak geçer.
Sonraki Adım
İmza artık kurulu ve neyi kanıtlayıp neyi kanıtlamadığı yazılı. Son ölçüm bir soruyu ortada bıraktı: doğrulayıcı dokuz alanın sekizini sınayamıyorsa o sekiz alanı beyana yazmanın anlamı ne?
Cevap alanların kendisinde değil, kimin doldurduğunda ve neyle karşılaştırılabildiğindedir. Bir alanın değeri, doğrulayanın bağımsız olarak elde edebildiği bir nesneden yeniden hesaplanabiliyorsa o alan bir kanıttır; hesaplanamıyorsa bir beyandır ve beyanın doğruluğu onu yazana güvenmeye kalır. Sonraki ders yapının nerede ve nasıl üretildiğini anlatan belgeyi alan alan kurar ve tek bir sayıyı çıkarır: bu belgenin kaç alanı bağımsız olarak doğrulanabiliyor, kaçı yalnız beyan olarak kalıyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.