İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat