yigityalim/twitter/github/share
Projelere Dön

Kriptografi El Kitabı

Modern kriptografik primitiflere derinlemesine dalış — ECDSA, SHA-256, ChaCha20-Poly1305, AES-GCM, Argon2id, X25519, HKDF ve daha fazlası. Çalıştırılabilir kod örnekleriyle.

Genel Bakış

Teknoloji Yığını

“Rust”“TypeScript”“WebAssembly”“Node.js”“Web Crypto API”“noble-ciphers”“noble-hashes”“noble-curves”

Bağlantılar

GitHub
© 2026 Yiğit Yalım.Tüm hakları saklıdır.

İçindekiler

Neden Bu Belge Var?

Altındaki primitifleri anlayana kadar her şifreleme kütüphanesi bir kara kutudur. Bu belge projelerimde — Occlude, ATU Humidor, Fuarcat — kullandığım algoritmaları parçalarına ayırıyor ve her birinin kod düzeyinde nasıl çalıştığını gösteriyor.


Temel Kavramlar

Kriptografi Neyi Çözer?

Primitiflere geçmeden önce, her ailenin hangi problemi çözdüğünü içselleştir:

ÖzellikAnlamıPrimitive
Gizlilik (Confidentiality)Yalnızca hedeflenen alıcılar okuyabilirSimetrik/asimetrik şifreleme
Bütünlük (Integrity)Veri manipüle edilmemiştirHash fonksiyonları, MAC’lar
Özgünlük (Authenticity)Gönderen iddia ettiği kişidirMAC’lar, dijital imzalar
İnkar Edilemezlik (Non-repudiation)Gönderen gönderdiğini inkâr edemezDijital imzalar (MAC’lar değil)
İleriye Dönük Gizlilik (Forward Secrecy)Uzun vadeli anahtarın ele geçirilmesi geçmiş oturumları açığa çıkarmazGeçici anahtar değişimi (ECDH)

MAC’lar özgünlük sağlar ama inkar edilemezlik sağlamaz — her iki taraf da anahtarı paylaşır, dolayısıyla ikisi de etiketi üretmiş olabilir. İmzalar asimetrik anahtarlar kullanır, dolayısıyla yalnızca özel anahtar sahibi imzalamış olabilir.

Kerckhoffs İlkesi

Güvenlik tamamen anahtara dayanmalıdır, algoritmaya değil. Saldırganın şifreni her ayrıntısıyla bildiğini varsay. Güvenlik algoritmanın gizli kalmasına bağlıysa (“gizlilik yoluyla güvenlik”), başarısız olur.

Kriptografik Yığın

Uygulama Katmanı    → Zarf şifrelemesi, anahtar yönetimi, rotasyon
Protokol Katmanı    → TLS 1.3, Noise Protokolü, Signal Protokolü
İnşaat Katmanı      → AEAD, HKDF, HMAC, kimlik doğrulamalı DH
Primitive Katmanı   → AES, ChaCha20, SHA-256, Curve25519, P-256

Gerekenden daha düşük bir katmana asla inme. Ham şifreler yerine AEAD kullan. Ham ECDH yerine protokol kullan.


Rastgelelik

CSPRNG (Kriptografik Olarak Güvenli Sözde Rastgele Sayı Üreteci)

Tüm kriptografik işlemler, gerçek rastgelelikten hesaplama açısından ayırt edilemeyen rastgele sayılar gerektirir. Math.random() asla kullanma — kriptografik olarak güvenli değildir.

csprng.ts
// Node.js / Edge runtime'lar
import { randomBytes } from "node:crypto";
 
const key = randomBytes(32);         // 256-bit anahtar
const nonce = randomBytes(12);       // GCM için 96-bit nonce
const salt = randomBytes(16);        // 128-bit salt
 
// Web Crypto API (tarayıcı / Cloudflare Workers / Deno)
const key2 = crypto.getRandomValues(new Uint8Array(32));
const nonce2 = crypto.getRandomValues(new Uint8Array(12));
 
// Noble (evrensel — arka planda WebCrypto kullanır)
import { randomBytes as nobleRandom } from "@noble/ciphers/webcrypto";
const nonce3 = nobleRandom(24); // Uint8Array döner
csprng.rs
use rand::rngs::OsRng;
use rand::RngCore;
 
fn anahtar_uret() -> [u8; 32] {
    let mut key = [0u8; 32];
    OsRng.fill_bytes(&mut key);
    key
}
 
// rand crate'in random() yardımcısıyla
use rand::random;
let nonce: [u8; 24] = random();

Kural: Her zaman işletim sistemi destekli entropi kullan (OsRng, crypto.getRandomValues, /dev/urandom). Zamanla tohumlanan thread-local PRNG’ler kırık sayılır.

Entropi Kaynakları

İşletim sistemi CSPRNG’si donanım olaylarından beslenir (kesme zamanlaması, CPU titremesi, x86’da RDRAND). Linux’ta: /dev/urandom (engellemesiz, tüm kriptografi için uygun). /dev/random bloke eder — modern kodda asla kullanma, ek güvenlik sağlamaz.


Hash Fonksiyonları

SHA-256

Bütünlük doğrulamanın temel taşı. Rastgele girdiden sabit 256-bit özet üretir. HMAC, anahtar türetme, dijital imzalar ve içerik adresleme için kullanılır.

sha256.ts
import { createHash } from "node:crypto";
 
function sha256(data: string): string {
  return createHash("sha256").update(data, "utf-8").digest("hex");
}
 
const hash = sha256("hello world");
// => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

Web Crypto API kullanarak — Node.js crypto‘nun olmadığı tarayıcılarda ve edge runtime’larda kullanılabilir:

sha256-webcrypto.ts
async function sha256(data: string): Promise<string> {
  const encoded = new TextEncoder().encode(data);
  const buffer = await crypto.subtle.digest("SHA-256", encoded);
  return Array.from(new Uint8Array(buffer))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

SHA-512

Özet boyutu iki katı. Ed25519 imza şemalarında HMAC-SHA-512 için dahili hash ve daha yüksek güvenlikli KDF’ler için kullanılır. SHA-384 de mevcuttur (kırpılmış SHA-512, 64-bit donanımda biraz daha hızlı).

sha512.ts
import { createHash } from "node:crypto";
 
function sha512(data: string): string {
  return createHash("sha512").update(data, "utf-8").digest("hex");
}

SHA-3 (Keccak)

SHA-2’den tamamen farklı bir yapı — Merkle-Damgård değil, sünger tabanlı. SHA-3, HMAC olmadan kullanılan SHA-2’yi etkileyen uzunluk uzatma saldırılarına karşı savunmasız değildir. Ethereum, Keccak-256 kullanır (NIST SHA-3’ten biraz farklı bir parameterization).

sha3.ts
import { createHash } from "node:crypto";
 
// SHA3-256 (NIST standardı)
const sha3_256 = createHash("sha3-256").update("hello").digest("hex");
 
// SHA3-512
const sha3_512 = createHash("sha3-512").update("hello").digest("hex");
sha3.rs
use sha3::{Sha3_256, Digest};
 
fn main() {
    let mut hasher = Sha3_256::new();
    hasher.update(b"hello world");
    let result = hasher.finalize();
    println!("{:x}", result);
}

SHA-3 vs SHA-2: SHA-2, SHA-NI uzantılarına sahip donanımda daha hızlıdır. HMAC ek yükü olmadan uzunluk uzatma direnci gerektiğinde veya algoritma çeşitliliği argümanında SHA-2’ye ikinci görüntü dirençli alternatif gerektiğinde SHA-3 tercih edilir.

BLAKE2b / BLAKE2s

Yazılımda SHA-2’den daha hızlı, SHA-3 ile karşılaştırılabilir güvenlik marjıyla. BLAKE2b 64-bit için; BLAKE2s 32-bit ve gömülü sistemler için optimize edilmiştir. Argon2 tarafından dahili olarak kullanılır.

blake2.ts
import { createHash } from "node:crypto"; // Node 21+ blake2b512 / blake2s256 destekler
 
const h = createHash("blake2b512").update("hello").digest("hex");
blake2.rs
use blake2::{Blake2b512, Digest};
 
let mut h = Blake2b512::new();
h.update(b"hello world");
let result = h.finalize();

BLAKE3

SHA değil ama belirtmeye değer — ağaç hashlenebilir, paralel çalışabilen bir hash fonksiyonu. Modern donanımda SHA-256’dan belirgin biçimde daha hızlı.

blake3_hash.rs
use blake3;
 
fn main() {
    let hash = blake3::hash(b"hello world");
    println!("{}", hash.to_hex());
    // => "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24"
}

BLAKE3 ayrıca anahtarlı hashleme (HMAC yerine) ve anahtar türetmeyi (HKDF yerine) doğal olarak destekler:

blake3-anahtarli.rs
use blake3;
 
// Anahtarlı hashleme — HMAC'ın yerini alır
let key: [u8; 32] = *b"an example very very secret key!";
let mac = blake3::keyed_hash(&key, b"mesaj");
 
// Anahtar türetme — HKDF'nin yerini alır
let derived = blake3::derive_key("uygulama bağlamı", b"girdi anahtar materyali");

Uzunluk Uzatma Saldırısı

SHA-256 ve SHA-512 (Merkle-Damgård ailesi) savunmasızdır: saldırgan H(gizli || mesaj) ve mesaj uzunluğuna sahipse, gizliyi bilmeden H(gizli || mesaj || ek) hesaplayabilir. Her zaman HMAC kullan — kimlik doğrulama için asla çıplak SHA-2 kullanma.

uzunluk-uzatma-onleme.ts
// YANLIŞ — uzunluk uzatma saldırısına açık
const mac = sha256(gizli + mesaj);
 
// DOĞRU — HMAC bu saldırıya karşı bağışık
import { createHmac } from "node:crypto";
const mac2 = createHmac("sha256", gizli).update(mesaj).digest("hex");

SHA-3 ve BLAKE3 uzunluk uzatma saldırılarına karşı bağışıktır.


Mesaj Doğrulama Kodları (MAC)

HMAC-SHA-256

Hash tabanlı Mesaj Doğrulama Kodu. Hem bütünlüğü hem özgünlüğü kanıtlar — gönderenin gizli anahtarı bilmesi gerekir.

hmac.ts
import { createHmac } from "node:crypto";
 
function hmacSha256(anahtar: string, mesaj: string): string {
  return createHmac("sha256", anahtar).update(mesaj).digest("hex");
}
 
const mac = hmacSha256("gizli-anahtarim", "odeme:1000:TRY");
// Doğrulayıcı aynı anahtarla yeniden hesaplayıp karşılaştırır

MAC doğrulaması için zamanlama güvenli karşılaştırma kritiktir — asla === kullanma:

zamanlama-guvenli.ts
import { timingSafeEqual } from "node:crypto";
 
function macDogrula(beklenen: string, alinan: string): boolean {
  const a = Buffer.from(beklenen, "hex");
  const b = Buffer.from(alinan, "hex");
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}

Poly1305

Tek kullanımlık MAC. Son derece hızlı — ChaCha20-Poly1305’in kimlik doğrulama bileşeni olarak tasarlandı. Anahtar asla yeniden kullanılmamalıdır, bu nedenle genel amaçlı MAC olarak bağımsız kullanılamaz.

GMAC (Galois MAC)

AES-GCM’in kimlik doğrulama bileşeni. GF(2^128) üzerinde GHASH tabanlı polinom MAC. Donanım desteğiyle hızlıdır (PCLMULQDQ). AES-GCM’deki “kimlik doğrulama etiketi” GMAC etiketidir.

SipHash

Hash tabloları için hızlı, anahtarlı hash — kriptografik anlamda MAC değil. Hash flooding DoS saldırılarını önler. Rust’ın HashMap’i varsayılan olarak SipHash-1-3 kullanır.

siphash.rs
use std::collections::HashMap;
// HashMap varsayılan olarak SipHash-1-3 kullanır — bedava gelir
let mut map: HashMap<String, i32> = HashMap::new();

Anahtar Türetme Fonksiyonları (KDF)

Argon2id

Parola hashlemenin altın standardı. Bellek açısından zorlu, GPU ve ASIC kaba kuvvet saldırılarına dayanıklı. Argon2id; Argon2i (yan kanal dirençli) ve Argon2d’yi (GPU dirençli) birleştirir.

argon2id.ts
import { hash, verify } from "@node-rs/argon2";
 
// Parola hashleme — donanımınıza göre bellek/iterasyonları ayarlayın
const hashed = await hash("kullanici-parolasi", {
  memoryCost: 65536,   // 64 MB
  timeCost: 3,         // 3 iterasyon
  parallelism: 4,      // 4 iş parçacığı
  algorithm: 2,        // argon2id
});
 
// Doğrulama — dahili olarak sabit zamanlı
const gecerli = await verify(hashed, "kullanici-parolasi");

Rust’ta — Occlude WASM kripto motoru için argon2 crate’i kullanarak:

argon2_kdf.rs
use argon2::{Argon2, Algorithm, Version, Params};
 
fn anahtar_turet(parola: &[u8], salt: &[u8]) -> [u8; 32] {
    let params = Params::new(65536, 3, 4, Some(32))
        .expect("gecerli parametreler");
    let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
 
    let mut anahtar = [0u8; 32];
    argon2.hash_password_into(parola, salt, &mut anahtar)
        .expect("hashleme basarisiz");
    anahtar
}

2025+ için Argon2 parametreleri:

  • Etkileşimli girişler: m=65536, t=3, p=4
  • Yüksek güvenlikli çevrimdışı anahtarlar: m=262144, t=4, p=4
  • Gömülü/WASM kısıtlı: m=16384, t=2, p=1

scrypt

Colin Percival’ın bellek açısından zorlu KDF’si. Argon2’den önce. Hâlâ yaygın kullanımda (OpenSSL, libsodium, Ethereum keystore v3). Üç parametre: N (CPU/bellek maliyeti), r (blok boyutu), p (paralellik).

scrypt.ts
import { scrypt, scryptSync } from "node:crypto";
 
// Asenkron
scrypt("parola", "salt", 64, { N: 16384, r: 8, p: 1 }, (err, turetilen) => {
  if (err) throw err;
  console.log(turetilen.toString("hex")); // 64-byte anahtar
});
 
// Senkron (event loop'u bloke eder — yalnızca CLI araçları için)
const anahtar = scryptSync("parola", "salt-buffer", 32, {
  N: 131072, // 2^17 — varsayılandan daha güvenli
  r: 8,
  p: 1,
});
scrypt.rs
use scrypt::{scrypt, Params};
 
fn anahtar_turet(parola: &[u8], salt: &[u8]) -> [u8; 32] {
    let params = Params::new(17, 8, 1, 32).unwrap(); // N=2^17
    let mut anahtar = [0u8; 32];
    scrypt(parola, salt, &params, &mut anahtar).unwrap();
    anahtar
}

scrypt vs Argon2id: Yeni sistemler için Argon2id tercih edilir. scrypt kabul edilebilir ve yaygın desteklenir. scrypt’in önbellek zamanlama yan kanalları Argon2id’yi kesinlikle daha iyi yapar. Yalnızca mevcut sistemlerle birlikte çalışabilirlik gerektirdiğinde scrypt kullan.

bcrypt

Eski standart. Blowfish tabanlı. Maliyet faktörü her artışta işi iki katına çıkarır. Maksimum girdi: 72 byte (sessizce kırpar — bir tuzak). Bellek açısından zorlu değil. Eski sistemler için hâlâ kabul edilebilir; yeni sistemler için Argon2id’i tercih et.

bcrypt.ts
import bcrypt from "bcrypt";
 
const TUR = 12; // 2^12 iterasyon — modern donanımda ~250ms
 
const hashed = await bcrypt.hash("kullanici-parolasi", TUR);
const gecerli = await bcrypt.compare("kullanici-parolasi", hashed);

bcrypt’in 72 byte limiti: Parolalar 72 byte’ı geçebiliyorsa (örn. uzun parolalar), bcrypt’ten önce SHA-256 ile ön hashleme yap — Blowfish’in hatalı ele aldığı null byte içerebilen ham baytlar değil, özetin base64 kodlamasını kullan:

bcrypt-onhashleme.ts
import { createHash } from "node:crypto";
import bcrypt from "bcrypt";
 
function onHashle(parola: string): string {
  return createHash("sha256").update(parola).digest("base64");
}
 
const hashed = await bcrypt.hash(onHashle("cok uzun bir parola..."), 12);
const gecerli = await bcrypt.compare(onHashle("cok uzun bir parola..."), hashed);

HKDF (HMAC Tabanlı Anahtar Türetme Fonksiyonu)

Anahtar materyali çıkarır ve genişletir. İki aşama: çıkarma (entropiyi yoğunlaştır) ve genişletme (bir kaynaktan birden fazla anahtar türet).

hkdf.ts
import { hkdf } from "node:crypto";
 
async function anahtarTuret(
  girisAnahtari: Buffer,
  salt: Buffer,
  bilgi: string
): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    hkdf("sha256", girisAnahtari, salt, bilgi, 32, (err, turetilen) => {
      if (err) reject(err);
      else resolve(Buffer.from(turetilen));
    });
  });
}
 
// Şifreleme ve kimlik doğrulama için ayrı anahtarlar türet
const masterAnahtar = Buffer.from("ecdh-den-paylasilan-gizli");
const salt = crypto.getRandomValues(new Uint8Array(32));
const sifreAnahtari = await anahtarTuret(masterAnahtar, Buffer.from(salt), "enc");
const macAnahtari = await anahtarTuret(masterAnahtar, Buffer.from(salt), "mac");

bilgi parametresi türetilen anahtarı bağlamına bağlar. Her amaç için farklı değerler kullan:

hkdf-baglam-baglama.ts
// X25519 anahtar değişiminin ardından — her yön için ayrı anahtarlar türet
const paylasılanGizli = x25519.getSharedSecret(benimOzel, onlarınPublic);
const salt = Buffer.alloc(32); // Sıfır salt, IKM tam entropiye sahipken kabul edilebilir
 
const sifreAnahtariAyeB = await anahtarTuret(paylasılanGizli, salt, "uygulama-v1:enc:a-to-b");
const sifreAnahtariBayeA = await anahtarTuret(paylasılanGizli, salt, "uygulama-v1:enc:b-to-a");
const macAnahtariAyeB = await anahtarTuret(paylasılanGizli, salt, "uygulama-v1:mac:a-to-b");

PBKDF2

Eski ama hâlâ yaygın kullanımda. Yinelemeli hashleme — Argon2’den daha az bellek zorlu, ancak evrensel desteklenir.

pbkdf2.ts
import { pbkdf2Sync } from "node:crypto";
 
function anahtarTuret(parola: string, salt: Buffer): Buffer {
  return pbkdf2Sync(parola, salt, 600_000, 32, "sha256");
  //                              ^^^^^^^ OWASP 2023 asgari değeri
}

İterasyon sayıları (OWASP 2023):

  • PBKDF2-SHA1: 1.300.000 iterasyon
  • PBKDF2-SHA256: 600.000 iterasyon
  • PBKDF2-SHA512: 210.000 iterasyon

Daha yüksek iterasyon sayısı her zaman daha iyidir; sunucunda ~300ms’ye göre kalibre et.

Salt Kullanımı

Salt, hash ile birlikte saklanan rastgele, kimlik bilgisi başına üretilen bir değerdir. Salt olmadan özdeş parolalar özdeş hash üretir — toplu saldırıları ve gökkuşağı tablosu aramalarını mümkün kılar.

salt.ts
import { randomBytes } from "node:crypto";
 
// YANLIŞ — salt yok, özdeş parolalar özdeş hash üretir
const bozuk = sha256(parola);
 
// YANLIŞ — statik salt (global salt = uzatılmış parola, gerçek salt değil)
const yanlisOlan = sha256("statik-salt" + parola);
 
// DOĞRU — rastgele kimlik bilgisi başına salt, hash ile saklanır
const salt = randomBytes(16).toString("hex");
const hash = sha256(salt + parola); // Uygulamada PBKDF2/Argon2 kullanılır
// Sakla: { salt, hash }

Argon2/bcrypt/scrypt salt’ları dahili olarak yönetir ve çıktı dizesine kodlar. Bu fonksiyonlarla salt’ları manuel yönetmen gerekmez.


Simetrik Şifreleme

Blok Şifreler vs Akış Şifreler

Blok şifreler (AES) sabit boyutlu bloklarda çalışır (AES için 128 bit). Bir bloktan uzun verileri şifrelemek için çalışma modu gerektirir.

Akış şifreler (ChaCha20) düz metin ile XOR’lanan sözde rastgele anahtar akışı üretir. Dolgu gerekmez.

AES modları:

  • ECB: Asla kullanma. Her blok bağımsız şifrelenir — özdeş düz metin blokları özdeş şifreli metin üretir. ECB penguen görseli bunu açıkça ortaya koyar.
  • CBC: Dolgu gerektirir, MAC olmadan dolgu oracle saldırılarına açıktır. MAC olmadan kullanma.
  • CTR: AES’i akış şifresine dönüştürür. Dolgu yok. Kimlik doğrulama olmadan XOR üzerinde oynamaya açık — HMAC ile birleştirilmelidir.
  • GCM: CTR modu + GMAC. Doğru varsayılan.

AES-256-GCM

Kimlik doğrulamalı şifrelemenin endüstri standardı. 256-bit anahtar, 96-bit nonce, şifreli metin + 128-bit kimlik doğrulama etiketi üretir. GCM modu hem gizlilik hem bütünlük sağlar.

aes-gcm.ts
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
 
interface Sifrelenmis {
  nonce: Buffer;
  sifreliMetin: Buffer;
  etiket: Buffer;
}
 
function sifrele(duzMetin: string, anahtar: Buffer): Sifrelenmis {
  const nonce = randomBytes(12); // GCM için 96-bit nonce
  const sifreleyici = createCipheriv("aes-256-gcm", anahtar, nonce);
 
  const sifreliMetin = Buffer.concat([
    sifreleyici.update(duzMetin, "utf-8"),
    sifreleyici.final(),
  ]);
  const etiket = sifreleyici.getAuthTag();
 
  return { nonce, sifreliMetin, etiket };
}
 
function coz(sifrelenmis: Sifrelenmis, anahtar: Buffer): string {
  const cozucu = createDecipheriv("aes-256-gcm", anahtar, sifrelenmis.nonce);
  cozucu.setAuthTag(sifrelenmis.etiket);
 
  return Buffer.concat([
    cozucu.update(sifrelenmis.sifreliMetin),
    cozucu.final(),
  ]).toString("utf-8");
}

Web Crypto API kullanarak — tarayıcılarda kullanılabilen tek simetrik şifre:

aes-gcm-webcrypto.ts
async function sifreleWebCrypto(
  duzMetin: string,
  hamAnahtar: Uint8Array
): Promise<{ nonce: Uint8Array; sifreliMetin: Uint8Array }> {
  const anahtar = await crypto.subtle.importKey(
    "raw",
    hamAnahtar,
    { name: "AES-GCM" },
    false,
    ["encrypt"]
  );
 
  const nonce = crypto.getRandomValues(new Uint8Array(12));
  const kodlanmis = new TextEncoder().encode(duzMetin);
  const sifreliMetin = new Uint8Array(
    await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, anahtar, kodlanmis)
  );
 
  return { nonce, sifreliMetin };
  // Not: GCM etiketi Web Crypto tarafından şifreli metne eklenir (son 16 byte)
}

AES-CBC (Eski Sistemler İçin)

CBC (Şifreli Blok Zincirleme) — her düz metin bloğu şifrelenmeden önce önceki şifreli metin bloğuyla XOR’lanır. PKCS#7 dolgusu ve kimlik doğrulama MAC’ı gerektirir. Yalnızca birlikte çalışabilirlik zorunlu kıldığında kullan.

aes-cbc.ts
import { createCipheriv, createDecipheriv, randomBytes, createHmac, timingSafeEqual } from "node:crypto";
 
// Şifrele-sonra-MAC yapısı — doğru sıra
function cbcSifrele(duzMetin: Buffer, sifreAnahtari: Buffer, macAnahtari: Buffer): {
  iv: Buffer; sifreliMetin: Buffer; mac: Buffer;
} {
  const iv = randomBytes(16); // CBC için 128-bit IV
  const sifreleyici = createCipheriv("aes-256-cbc", sifreAnahtari, iv);
  const sifreliMetin = Buffer.concat([sifreleyici.update(duzMetin), sifreleyici.final()]);
 
  // IV + şifreli metin üzerinde MAC (Şifrele-sonra-MAC)
  const mac = createHmac("sha256", macAnahtari)
    .update(Buffer.concat([iv, sifreliMetin]))
    .digest();
 
  return { iv, sifreliMetin, mac };
}
 
function cbcCoz(iv: Buffer, sifreliMetin: Buffer, mac: Buffer, sifreAnahtari: Buffer, macAnahtari: Buffer): Buffer {
  // Çözmeden önce MAC'ı doğrula — dolgu oracle'ı önler
  const beklenen = createHmac("sha256", macAnahtari)
    .update(Buffer.concat([iv, sifreliMetin]))
    .digest();
 
  if (!timingSafeEqual(mac, beklenen)) throw new Error("MAC doğrulaması başarısız");
 
  const cozucu = createDecipheriv("aes-256-cbc", sifreAnahtari, iv);
  return Buffer.concat([cozucu.update(sifreliMetin), cozucu.final()]);
}

Dolgu Oracle Saldırısı: Bir şifre çözme oracle’ı dolgunun geçerli olup olmadığını açıklarsa (farklı hata mesajları, zamanlama farkları veya davranış yoluyla bile), saldırgan anahtarı bilmeden herhangi bir CBC şifreli metni byte byte çözebilir. Her zaman çözmeden önce MAC’ı doğrula. Yeni kodda AES-CBC yerine her zaman AES-GCM kullan.

AES-CTR

Sayaç modu — AES’i akış şifresine dönüştürür. Aynı anahtar için nonce/sayaç kombinasyonu asla tekrarlanmamalıdır. Dolgu yok. Kimlik doğrulamalı değil — HMAC ile birleştirilmelidir.

aes-ctr.ts
import { createCipheriv, randomBytes } from "node:crypto";
 
// CTR şifreleme ve çözme için aynı fonksiyonu kullanır
function aesCtr(veri: Buffer, anahtar: Buffer, sayac: Buffer): Buffer {
  const sifreleyici = createCipheriv("aes-256-ctr", anahtar, sayac);
  return Buffer.concat([sifreleyici.update(veri), sifreleyici.final()]);
}
 
const anahtar = randomBytes(32);
const sayac = randomBytes(16); // 128-bit sayaç bloğu
const sifreliMetin = aesCtr(Buffer.from("duz metin"), anahtar, sayac);
const duzMetin = aesCtr(sifreliMetin, anahtar, sayac); // aynı işlem

AES Anahtar Sarmalama (RFC 3394)

Anahtarları anahtarlarla şifrelemek için kullanılır — zarf şifrelemesi ve güvenli anahtar dışa aktarımı için standart.

aes-anahtar-sarma.ts
// Web Crypto AES-KW'yi doğal olarak destekler
const sarmaAnahtari = await crypto.subtle.generateKey(
  { name: "AES-KW", length: 256 },
  false,
  ["wrapKey", "unwrapKey"]
);
 
const sarılacakAnahtar = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true, // sarmak için dışa aktarılabilir olmalı
  ["encrypt", "decrypt"]
);
 
const sarılmis = await crypto.subtle.wrapKey("raw", sarılacakAnahtar, sarmaAnahtari, "AES-KW");
// sarılmis bir ArrayBuffer — veriyle birlikte saklamak güvenli

ChaCha20-Poly1305

AES-GCM’in modern alternatifi. Donanım hızlandırma gerektirmez (AES-NI’nin aksine), tasarım gereği sabit zamanlı ve her mimaride zamanlama saldırılarına karşı dayanıklıdır. TLS 1.3, WireGuard ve SSH’de kullanılır.

chacha20.ts
import { chacha20poly1305 } from "@noble/ciphers/chacha";
import { randomBytes } from "@noble/ciphers/webcrypto";
 
function sifrele(duzMetin: Uint8Array, anahtar: Uint8Array): {
  nonce: Uint8Array;
  sifreliMetin: Uint8Array;
} {
  const nonce = randomBytes(12); // 96-bit nonce
  const sifreleyici = chacha20poly1305(anahtar, nonce);
  const sifreliMetin = sifreleyici.encrypt(duzMetin);
  return { nonce, sifreliMetin };
}
 
function coz(
  sifreliMetin: Uint8Array,
  anahtar: Uint8Array,
  nonce: Uint8Array
): Uint8Array {
  const sifreleyici = chacha20poly1305(anahtar, nonce);
  return sifreleyici.decrypt(sifreliMetin);
}

XChaCha20-Poly1305

Uzatılmış nonce varyantı — 96-bit yerine 192-bit nonce. Nonce çarpışması riskini tamamen ortadan kaldırır, bu da sayaç olmadan rastgele nonce üretmeyi güvenli kılar. Occlude’un kullandığı budur.

xchacha20.ts
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
import { randomBytes } from "@noble/ciphers/webcrypto";
 
const anahtar = randomBytes(32);    // 256-bit anahtar
const nonce = randomBytes(24);      // 192-bit nonce — rastgele üretmek güvenli
const mesaj = new TextEncoder().encode("gizli not");
 
const sifreleyici = xchacha20poly1305(anahtar, nonce);
const sifrelenmis = sifreleyici.encrypt(mesaj);
 
// Çözme
const cozucu = xchacha20poly1305(anahtar, nonce);
const cozulmus = cozucu.decrypt(sifrelenmis);
console.log(new TextDecoder().decode(cozulmus));
// => "gizli not"

Rust’ta — @occlude/crypto WASM modülünü çalıştıran aynı algoritma:

xchacha20_rust.rs
use chacha20poly1305::{
    XChaCha20Poly1305, XNonce,
    aead::{Aead, KeyInit},
};
 
fn sifrele(duz_metin: &[u8], anahtar: &[u8; 32]) -> (Vec<u8>, [u8; 24]) {
    let sifreleyici = XChaCha20Poly1305::new(anahtar.into());
    let nonce_bytes: [u8; 24] = rand::random();
    let nonce = XNonce::from_slice(&nonce_bytes);
 
    let sifrelenmis = sifreleyici.encrypt(nonce, duz_metin)
        .expect("şifreleme başarısız");
 
    (sifrelenmis, nonce_bytes)
}
 
fn coz(sifrelenmis: &[u8], anahtar: &[u8; 32], nonce: &[u8; 24]) -> Vec<u8> {
    let sifreleyici = XChaCha20Poly1305::new(anahtar.into());
    let nonce = XNonce::from_slice(nonce);
 
    sifreleyici.decrypt(nonce, sifrelenmis)
        .expect("şifre çözme başarısız — yanlış anahtar veya manipüle edilmiş veri")
}

İlişkili Veri (AAD)

AEAD şifreler hem şifreli metni hem de ek düz metin meta verilerini doğrular. AAD şifrelenmez ama şifreli metne bağlanır — AAD’yi değiştirmek şifre çözmeyi başarısız kılar.

aead-aad.ts
// AAD ile AES-GCM — AAD doğrulanır ama şifrelenmez
async function aadIleSimrele(
  duzMetin: string,
  anahtar: CryptoKey,
  aad: Uint8Array // örn. kayıt ID, kullanıcı ID, şema versiyonu
): Promise<{ nonce: Uint8Array; sifreliMetin: Uint8Array }> {
  const nonce = crypto.getRandomValues(new Uint8Array(12));
  const kodlanmis = new TextEncoder().encode(duzMetin);
  const sifreliMetin = new Uint8Array(
    await crypto.subtle.encrypt(
      { name: "AES-GCM", iv: nonce, additionalData: aad },
      anahtar,
      kodlanmis
    )
  );
  return { nonce, sifreliMetin };
}
 
// Kullanım: şifreli metni kayıt ID'sine bağla — kesip yapıştırma saldırılarını önler
const kayitId = new TextEncoder().encode("kayit-uuid-1234");
const { nonce, sifreliMetin } = await aadIleSimrele(gizli, anahtar, kayitId);
// Saldırgan bu şifreli metni farklı bir kayıt ID'si altına yapıştıramaz — şifre çözme başarısız olur

Yaygın AAD adayları: kayıt/satır ID’si, kullanıcı ID’si, şema versiyonu, protokol versiyonu, zaman damgası (kaba).


Asimetrik Kriptografi

ECDSA (Eliptik Eğri Dijital İmza Algoritması)

Dijital imzalar için kullanılır. İmzalayan özel anahtara sahiptir; herkes açık anahtarla doğrulayabilir. secp256k1 Bitcoin/Ethereum tarafından kullanılır; P-256 (secp256r1) NIST standardıdır.

ecdsa-p256.ts
async function anahtarCiftiOlustur() {
  return crypto.subtle.generateKey(
    { name: "ECDSA", namedCurve: "P-256" },
    true,
    ["sign", "verify"]
  );
}
 
async function imzala(
  ozelAnahtar: CryptoKey,
  veri: Uint8Array
): Promise<ArrayBuffer> {
  return crypto.subtle.sign(
    { name: "ECDSA", hash: "SHA-256" },
    ozelAnahtar,
    veri
  );
}
 
async function dogrula(
  acikAnahtar: CryptoKey,
  imza: ArrayBuffer,
  veri: Uint8Array
): Promise<boolean> {
  return crypto.subtle.verify(
    { name: "ECDSA", hash: "SHA-256" },
    acikAnahtar,
    imza,
    veri
  );
}
 
const { privateKey, publicKey } = await anahtarCiftiOlustur();
const mesaj = new TextEncoder().encode("Alice'e 100 TL transfer et");
const imza = await imzala(privateKey, mesaj);
const gecerli = await dogrula(publicKey, imza, mesaj);
console.log(gecerli); // => true

ECDSA secp256k1

Bitcoin/Ethereum eğrisi. Web Crypto’da yok; @noble/curves kullan.

secp256k1.ts
import { secp256k1 } from "@noble/curves/secp256k1";
 
const ozelAnahtar = secp256k1.utils.randomPrivateKey();
const acikAnahtar = secp256k1.getPublicKey(ozelAnahtar);
 
// İmzala — mesajın ham hali değil, hash'i gönderilmeli
const mesajHash = new Uint8Array(32);
crypto.getRandomValues(mesajHash); // yer tutucu — uygulamada sha256(mesaj) kullan
const imza = secp256k1.sign(mesajHash, ozelAnahtar);
 
// Doğrula
const gecerli = secp256k1.verify(imza, mesajHash, acikAnahtar);
 
// Ethereum tarzı kurtarma
const kurtarilan = imza.recoverPublicKey(mesajHash);

ECDSA P-384 / P-521

Daha yüksek güvenlik seviyeleri. P-384 NSS Suite B’de kullanılır (devlet sınıfı). P-521 ~260-bit güvenlik sağlar — neredeyse her şey için fazla.

ecdsa-p384.ts
const cifti = await crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-384" },
  true,
  ["sign", "verify"]
);

Ed25519

Edwards eğrisi Dijital İmza Algoritması. ECDSA’dan daha hızlı, deterministik (imza başına rastgele nonce gerekmez) ve yan kanal saldırılarına dayanıklı.

ed25519.ts
import { ed25519 } from "@noble/curves/ed25519";
 
// Anahtar üretimi
const ozelAnahtar = ed25519.utils.randomPrivateKey();
const acikAnahtar = ed25519.getPublicKey(ozelAnahtar);
 
// İmzala
const mesaj = new TextEncoder().encode("bu isteği doğrula");
const imza = ed25519.sign(mesaj, ozelAnahtar);
 
// Doğrula
const gecerli = ed25519.verify(imza, mesaj, acikAnahtar);
console.log(gecerli); // => true

Rust’ta:

ed25519_imza.rs
use ed25519_dalek::{SigningKey, Signer, Verifier};
use rand::rngs::OsRng;
 
fn main() {
    let imzalama_anahtari = SigningKey::generate(&mut OsRng);
    let dogrulama_anahtari = imzalama_anahtari.verifying_key();
 
    let mesaj = b"bu isteği doğrula";
    let imza = imzalama_anahtari.sign(mesaj);
 
    assert!(dogrulama_anahtari.verify(mesaj, &imza).is_ok());
}

ECDSA vs Ed25519:

  • ECDSA imza başına rastgele nonce gerektirir — bozuk bir RNG, özel anahtarı sızdıran bozuk bir imza üretir. PS3 bu şekilde kırıldı.
  • Ed25519 deterministiktir — aynı anahtar + aynı mesaj = aynı imza. RNG bağımlılığı yok.
  • Yeni sistemler için Ed25519’u tercih et.

X25519 (ECDH Anahtar Değişimi)

Curve25519 üzerinde Diffie-Hellman anahtar anlaşması. İki taraf, onu asla iletmeden aynı paylaşılan gizliyi türetir. Paylaşılan gizli ardından şifreleme anahtarları üretmek için HKDF’ye beslenir.

x25519.ts
import { x25519 } from "@noble/curves/ed25519";
 
// Alice anahtar çiftini üretir
const aliceOzel = x25519.utils.randomPrivateKey();
const aliceAcik = x25519.getPublicKey(aliceOzel);
 
// Bob anahtar çiftini üretir
const bobOzel = x25519.utils.randomPrivateKey();
const bobAcik = x25519.getPublicKey(bobOzel);
 
// Her ikisi de aynı paylaşılan gizliyi türetir
const alicePaylasilan = x25519.getSharedSecret(aliceOzel, bobAcik);
const bobPaylasilan = x25519.getSharedSecret(bobOzel, aliceAcik);
 
// alicePaylasilan === bobPaylasilan (aynı 32 byte)
// Şifreleme anahtarları türetmek için HKDF'ye besle

ECDH P-256 (Web Crypto)

Harici kütüphane olmadan tarayıcı-native anahtar anlaşması için:

ecdh-webcrypto.ts
async function ecdhCiftiOlustur() {
  return crypto.subtle.generateKey(
    { name: "ECDH", namedCurve: "P-256" },
    true,
    ["deriveKey", "deriveBits"]
  );
}
 
async function paylasılanAnahtarTuret(
  benimOzel: CryptoKey,
  onlarinAcik: CryptoKey
): Promise<CryptoKey> {
  return crypto.subtle.deriveKey(
    { name: "ECDH", public: onlarinAcik },
    benimOzel,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
}
 
const alice = await ecdhCiftiOlustur();
const bob = await ecdhCiftiOlustur();
 
const aliceAnahtari = await paylasılanAnahtarTuret(alice.privateKey, bob.publicKey);
const bobAnahtari = await paylasılanAnahtarTuret(bob.privateKey, alice.publicKey);
// aliceAnahtari ve bobAnahtari birbirinin mesajlarını şifreler/çözer

RSA-OAEP

Klasik. Hâlâ anahtar sarmalama ve eski sistemler için kullanılır. ECC’ye kıyasla daha büyük anahtarlar (eşdeğer güvenlik için 256 bit yerine 2048-4096 bit).

rsa-oaep.ts
async function rsaSifrele(
  acikAnahtar: CryptoKey,
  duzMetin: Uint8Array
): Promise<ArrayBuffer> {
  return crypto.subtle.encrypt({ name: "RSA-OAEP" }, acikAnahtar, duzMetin);
}
 
async function rsaCoz(
  ozelAnahtar: CryptoKey,
  sifreliMetin: ArrayBuffer
): Promise<ArrayBuffer> {
  return crypto.subtle.decrypt({ name: "RSA-OAEP" }, ozelAnahtar, sifreliMetin);
}
 
// 4096-bit RSA anahtar çifti üret
const anahtarCifti = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]), // 65537
    hash: "SHA-256",
  },
  true,
  ["encrypt", "decrypt"]
);

RSA-OAEP vs RSA-PKCS1v1.5: PKCS1v1.5, BLEICHENBACHER saldırısına (uyarlanabilir seçilmiş şifreli metin) karşı savunmasızdır. Yeni şifreleme için asla kullanma. RSA-OAEP doğru dolgudur.

RSA-PSS (Olasılıksal İmza Şeması)

RSA imzaları için doğru dolgu. RSA-PKCS1v1.5 imzaları deterministik ve potansiyel olarak değiştirilebilirdir; PSS rastgelelik ve güvenlik kanıtı ekler.

rsa-pss.ts
const anahtarCifti = await crypto.subtle.generateKey(
  {
    name: "RSA-PSS",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256",
  },
  true,
  ["sign", "verify"]
);
 
const imza = await crypto.subtle.sign(
  { name: "RSA-PSS", saltLength: 32 }, // saltLength = hash çıktı uzunluğu
  anahtarCifti.privateKey,
  new TextEncoder().encode("imzalanacak mesaj")
);
 
const gecerli = await crypto.subtle.verify(
  { name: "RSA-PSS", saltLength: 32 },
  anahtarCifti.publicKey,
  imza,
  new TextEncoder().encode("imzalanacak mesaj")
);

RSA Anahtar Boyutu vs Güvenlik Seviyesi

RSA Anahtar BoyutuECC EşdeğeriGüvenlik BitKırılma Tahmini
1024-bit—~80Çoktan kırıldı
2048-bitP-224~112~2030
3072-bitP-256~128~2040
4096-bitP-384~140>2050

Yeni sistemler için: 4096-bit RSA veya P-256/Ed25519 (eşdeğer güvenlik, çok daha küçük anahtarlar).


Anahtar Serileştirme ve Kodlama

Anahtarlar standart formatlarda saklanmalı ve iletilmelidir. Asla kendi formatını icat etme.

JWK (JSON Web Key)

Web Crypto anahtarları için standart JSON gösterimi. Platformlar arası taşınabilir.

jwk.ts
// JWK'ya dışa aktar
const anahtarCifti = await crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-256" },
  true,
  ["sign", "verify"]
);
 
const ozelJwk = await crypto.subtle.exportKey("jwk", anahtarCifti.privateKey);
const acikJwk = await crypto.subtle.exportKey("jwk", anahtarCifti.publicKey);
 
// ozelJwk şuna benzer:
// { kty: "EC", crv: "P-256", d: "...", x: "...", y: "...", key_ops: ["sign"] }
 
// JWK'dan içe aktar
const ictenAlinan = await crypto.subtle.importKey(
  "jwk",
  acikJwk,
  { name: "ECDSA", namedCurve: "P-256" },
  true,
  ["verify"]
);

PKCS#8 (Özel Anahtar) ve SPKI (Açık Anahtar)

Binary DER kodlamalı formatlar — PEM dosyaları için standart. OpenSSL, TLS sertifikaları ve SSH’nin arka planda kullandığı şey.

pkcs8.ts
// Özel anahtarı PKCS#8 DER olarak dışa aktar
const pkcs8 = await crypto.subtle.exportKey("pkcs8", anahtarCifti.privateKey);
 
// Açık anahtarı SubjectPublicKeyInfo (SPKI) DER olarak dışa aktar
const spki = await crypto.subtle.exportKey("spki", anahtarCifti.publicKey);
 
// PEM kodlaması — DER'i başlıklarla base64'e sar
function pemOlustur(der: ArrayBuffer, tur: "PRIVATE KEY" | "PUBLIC KEY"): string {
  const b64 = btoa(String.fromCharCode(...new Uint8Array(der)));
  const satirlar = b64.match(/.{1,64}/g)!.join("\n");
  return `-----BEGIN ${tur}-----\n${satirlar}\n-----END ${tur}-----`;
}
 
const ozelPEM = pemOlustur(pkcs8, "PRIVATE KEY");
const acikPEM = pemOlustur(spki, "PUBLIC KEY");
pem.rs
use rsa::{RsaPrivateKey, pkcs8::EncodePrivateKey, pkcs8::LineEnding};
use rand::rngs::OsRng;
 
fn main() {
    let ozel_anahtar = RsaPrivateKey::new(&mut OsRng, 4096).unwrap();
    let pem = ozel_anahtar.to_pkcs8_pem(LineEnding::LF).unwrap();
    println!("{}", pem.as_str()); // -----BEGIN PRIVATE KEY-----...
}

Ham Format

Simetrik anahtarlar ve ham ECC anahtar materyali için (başlık yok, saf byte’lar):

ham-anahtar.ts
// AES anahtarını ham byte olarak dışa aktar
const aesAnahtari = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);
 
const hamAnahtar = await crypto.subtle.exportKey("raw", aesAnahtari);
// hamAnahtar 32 byte'lık ArrayBuffer
 
// Şifreli sakla (ham anahtarları asla açık metin olarak saklama)
// Geri içe aktar
const ictenAlinan = await crypto.subtle.importKey(
  "raw",
  hamAnahtar,
  { name: "AES-GCM" },
  false, // yeniden dışa aktarılamaz
  ["encrypt", "decrypt"]
);

Hibrit Şifreleme (ECIES Deseni)

RSA yalnızca küçük miktarda veriyi şifreleyebilir (anahtar boyutuyla sınırlı). Asimetrik kripto yavaştır. Çözüm: rastgele simetrik anahtarı asimetrik kripto ile şifrele, ardından gerçek veriyi bu simetrik anahtarla şifrele. Bu hibrit şifreleme — TLS, PGP ve gerçek dünyadan her E2EE sisteminin temelidir.

ecies.ts
import { x25519 } from "@noble/curves/ed25519";
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
import { hkdf } from "@noble/hashes/hkdf";
import { sha256 } from "@noble/hashes/sha2";
import { randomBytes } from "@noble/ciphers/webcrypto";
 
// Gönderen alıcının açık anahtarıyla şifreler
function eciesSifrele(
  duzMetin: Uint8Array,
  aliciAcikAnahtari: Uint8Array
): {
  geciciAcik: Uint8Array;
  nonce: Uint8Array;
  sifreliMetin: Uint8Array;
} {
  // 1. Geçici anahtar çifti üret
  const geciciOzel = x25519.utils.randomPrivateKey();
  const geciciAcik = x25519.getPublicKey(geciciOzel);
 
  // 2. Alıcının açık anahtarıyla ECDH
  const paylasılanGizli = x25519.getSharedSecret(geciciOzel, aliciAcikAnahtari);
 
  // 3. HKDF ile şifreleme anahtarı türet
  const sifreAnahtari = hkdf(sha256, paylasılanGizli, geciciAcik, "ecies-v1", 32);
 
  // 4. Şifrele
  const nonce = randomBytes(24);
  const sifreleyici = xchacha20poly1305(sifreAnahtari, nonce);
  const sifreliMetin = sifreleyici.encrypt(duzMetin);
 
  return { geciciAcik, nonce, sifreliMetin };
}
 
// Alıcı kendi özel anahtarıyla çözer
function eciesCoz(
  geciciAcik: Uint8Array,
  nonce: Uint8Array,
  sifreliMetin: Uint8Array,
  aliciOzelAnahtari: Uint8Array
): Uint8Array {
  // 1. ECDH — gönderenin türettiğiyle aynı paylaşılan gizli
  const paylasılanGizli = x25519.getSharedSecret(aliciOzelAnahtari, geciciAcik);
 
  // 2. Aynı şifreleme anahtarını türet
  const sifreAnahtari = hkdf(sha256, paylasılanGizli, geciciAcik, "ecies-v1", 32);
 
  // 3. Çöz
  const sifreleyici = xchacha20poly1305(sifreAnahtari, nonce);
  return sifreleyici.decrypt(sifreliMetin);
}

Geçici anahtar çifti ileriye dönük gizlilik sağlar — alıcının uzun vadeli özel anahtarı daha sonra ele geçirilse bile, her şifreleme farklı bir geçici anahtar kullandığı için geçmiş mesajlar güvende kalır.


Dijital İmzalar: İleri Desenler

Kör İmzalar (Blind Signatures)

Bir imzalayan, içeriğini görmeden bir mesajı imzalar. Anonim kimlik bilgisi sistemleri ve e-nakit için kullanılır (Chaum 1982).

Eşik İmzaları (Threshold Signatures)

k / n şeması: geçerli bir imza üretmek için n taraftan k’sının işbirliği gerekir. Hiçbir tek taraf tam özel anahtarı tutmaz. Çoklu imza cüzdanları ve HSM kümelerinde kullanılır.

Schnorr İmzaları

ECDSA’dan daha basit, rastgele oracle modelinde kanıtlanmış güvenli ve imza agregasyonunu destekler (birden fazla imza tek birine birleşir). Bitcoin Taproot Schnorr kullanır.

schnorr.ts
import { schnorr } from "@noble/curves/secp256k1";
 
const ozelAnahtar = schnorr.utils.randomPrivateKey();
const acikAnahtar = schnorr.getPublicKey(ozelAnahtar);
 
const mesaj = sha256("merhaba"); // Schnorr hash'i imzalar
const imza = schnorr.sign(mesaj, ozelAnahtar);
const gecerli = schnorr.verify(imza, mesaj, acikAnahtar);

Kimlik Doğrulamalı Anahtar Değişimi

Diffie-Hellman (Klasik)

İki taraf açık kanal üzerinde paylaşılan gizli konusunda anlaşır. Hiçbiri gizliyi iletmez. Ayrık logaritma problemi bir dinleyicinin gizliyi hesaplamasını engeller.

Klasik DH, asal modulo çarpımsal gruplar kullanır. Modern sistemler eliptik eğriler kullanır (ECDH) — eşdeğer güvenlik için çok daha küçük anahtarlar.

DH kimlik doğrulamasızdır — ek kimlik doğrulama katmanı olmadan (imzalar, PKI, önceden paylaşılmış anahtarlar) ortadaki adam saldırısına açıktır.

İstasyon-İstasyon (STS) Protokolü

DH + imzalar. Her taraf kimliğini kanıtlamak için transkripti imzalar. SSH anahtar kimlik doğrulamasının temeli.

Noise Protokol Çerçevesi

Güvenli, kimlik doğrulamalı ve isteğe bağlı olarak ileriye dönük gizli el sıkışmalar oluşturmak için modern çerçeve. WireGuard Noise_IKpsk2 desenini kullanır. Her desen hangi anahtarların ne zaman iletileceğini açıklayan token dizisiyle (örn. XX, IK, NX) tanımlanır.

Noise_XX deseni (karşılıklı kimlik doğrulama, ileriye dönük gizlilik):
  -> e
  <- e, ee, s, es
  -> s, se

Signal Çift Mandal Protokolü (Double Ratchet)

Uçtan uca şifreli mesajlaşmanın altın standardı. İkisini birleştirir:

  1. X3DH (Genişletilmiş Üçlü Diffie-Hellman) başlangıç anahtar anlaşması için
  2. Çift Mandal devam eden mesaj şifreleme için — bir Diffie-Hellman mandalının (mola kurtarma sağlar) ve simetrik anahtar mandalın (mesaj başına ileriye dönük gizlilik sağlar) kombinasyonu

Her mesaj, mandalı ilerletmekle türetilen farklı bir anahtar kullanır. Bir mesaj anahtarının ele geçirilmesi geçmiş veya gelecek mesajları tehlikeye atmaz.


JWT, JWS ve JWE (JOSE)

JWT (JSON Web Token)

İmzalanmış (veya şifreli) talep yükü. Üç base64url kodlamalı bölüm: başlık.yük.imza.

jwt.ts
import { SignJWT, jwtVerify } from "jose";
 
const gizli = new TextEncoder().encode("256-bit-sırın-buraya-dolgu-ekleniyor!");
 
// İmzala
const token = await new SignJWT({ kullaniciId: "kul_123", rol: "admin" })
  .setProtectedHeader({ alg: "HS256" })
  .setIssuedAt()
  .setExpirationTime("2h")
  .setIssuer("https://uygulamaniz.com")
  .setAudience("https://api.uygulamaniz.com")
  .sign(gizli);
 
// Doğrula
const { payload } = await jwtVerify(token, gizli, {
  issuer: "https://uygulamaniz.com",
  audience: "https://api.uygulamaniz.com",
});
console.log(payload.kullaniciId); // => "kul_123"

JWT güvenlik kuralları:

  • Başlıktaki alg alanını her zaman doğrula — klasik saldırı "alg": "none" olarak ayarlar.
  • iss, aud, exp, nbf’yi her zaman doğrula.
  • Birden fazla servis tarafından doğrulanan tokenlar için RS256 veya ES256 (asimetrik) kullan. HS256 gizliyi paylaşmayı gerektirir.
  • JWT yüklerine hassas veri koyma — yalnızca imzalanır, şifrelenmez.

JWS (JSON Web İmzası)

JWT imzaları için altta yatan spec. Birden fazla imzalayıcıyı destekler (JSON serileştirmesi).

JWE (JSON Web Şifrelemesi)

Yükü şifreler. Tam akış: rastgele İçerik Şifreleme Anahtarı (CEK) üret, AES-GCM veya ChaCha20-Poly1305 ile CEK kullanarak yükü şifrele, RSA-OAEP veya ECDH-ES kullanarak alıcının açık anahtarıyla CEK’i şifrele.

jwe.ts
import { EncryptJWT, jwtDecrypt } from "jose";
 
const anahtarCifti = await crypto.subtle.generateKey(
  { name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
  true,
  ["encrypt", "decrypt"]
);
 
// Şifrele
const sifrelenmis = await new EncryptJWT({ kullaniciId: "kul_123" })
  .setProtectedHeader({ alg: "RSA-OAEP-256", enc: "A256GCM" })
  .setExpirationTime("1h")
  .encrypt(anahtarCifti.publicKey);
 
// Çöz
const { payload } = await jwtDecrypt(sifrelenmis, anahtarCifti.privateKey);

TOTP / HOTP (İki Faktörlü Kimlik Doğrulama)

HOTP (HMAC Tabanlı Tek Kullanımlık Şifre)

RFC 4226. Sayaç tabanlı OTP: HOTP(K, C) = Truncate(HMAC-SHA1(K, C)).

hotp.ts
import { createHmac } from "node:crypto";
 
function hotp(gizli: Buffer, sayac: number): string {
  const sayacBuffer = Buffer.alloc(8);
  sayacBuffer.writeBigUInt64BE(BigInt(sayac));
 
  const mac = createHmac("sha1", gizli).update(sayacBuffer).digest();
 
  // Dinamik kırpma
  const offset = mac[19] & 0x0f;
  const kod = ((mac[offset] & 0x7f) << 24) |
              (mac[offset + 1] << 16) |
              (mac[offset + 2] << 8) |
              mac[offset + 3];
 
  return String(kod % 1_000_000).padStart(6, "0");
}

TOTP (Zaman Tabanlı Tek Kullanımlık Şifre)

RFC 6238. Sayacın Math.floor(Date.now() / 1000 / 30) — 30 saniyelik zaman adımıyla değiştirildiği HOTP.

totp.ts
import { createHmac, randomBytes } from "node:crypto";
 
function totp(gizli: Buffer, pencere = 0): string {
  const zaman = Math.floor(Date.now() / 1000 / 30) + pencere;
  return hotp(gizli, zaman);
}
 
function totpDogrula(gizli: Buffer, kod: string, kayma = 1): boolean {
  // Saat kayması için ±kayma pencerelerini kontrol et
  for (let p = -kayma; p <= kayma; p++) {
    if (totp(gizli, p) === kod) return true;
  }
  return false;
}
 
// Kimlik doğrulama uygulamaları için gizli ve QR URI üret
function totpGizliOlustur(): { gizli: string; uri: string } {
  const gizli = randomBytes(20).toString("base32");
  const uri = `otpauth://totp/UygulamAdi:kullanici@ornek.com?secret=${gizli}&issuer=UygulamAdi&algorithm=SHA1&digits=6&period=30`;
  return { gizli, uri };
}

Üretimde TOTP: Kendin yazmak yerine otplib veya @oslojs/otp kütüphanesini kullan. Her zaman hız sınırlaması ve kilitleme uygula — TOTP yalnızca 6 hanedir.


Sıfır Bilgi Kanıtları (Kavramsal)

ZKP Neyi Çözer?

Gizliyi açıklamadan gizliyi bildiğini kanıtla. Örnekler:

  • Parolayı göndermeden parolayı bildiğini kanıtla
  • Doğum tarihini açıklamadan 18 yaşında olduğunu kanıtla
  • İşlem miktarlarını açıklamadan bir işlemin geçerli olduğunu kanıtla (Zcash)

Taahhüt Şemaları

Birçok ZKP’nin altında yatan primitive. Değeri açıklamadan taahhüt et; daha sonra aç ve taahhüdünün o değere yönelik olduğunu kanıtla.

taahhut.ts
import { randomBytes, createHash } from "node:crypto";
 
// Taahhüt: hash(değer || rastgele_körleme_faktörü)
function taahhutEt(deger: string): { taahhut: string; korleme: string } {
  const korleme = randomBytes(32).toString("hex");
  const taahhut = createHash("sha256")
    .update(deger + korleme)
    .digest("hex");
  return { taahhut, korleme };
}
 
// Aç: değer ve körleyi aç, doğrulayıcı yeniden hesaplar
function dogrula(deger: string, korleme: string, taahhut: string): boolean {
  const beklenen = createHash("sha256")
    .update(deger + korleme)
    .digest("hex");
  return beklenen === taahhut;
}

Sigma Protokolleri (Schnorr PoK)

Özel anahtarı açıklamadan ayrık logaritma bilgisini kanıtla (“bu açık anahtara karşılık gelen özel anahtarı biliyorum”):

1. Kanıtlayıcı: rastgele r seç, taahhüt R = r·G gönder
2. Doğrulayıcı: meydan okuma c gönder
3. Kanıtlayıcı: yanıt s = r + c·x gönder (x özel anahtar)
4. Doğrulayıcı: s·G == R + c·X olduğunu kontrol et

SRP (Güvenli Uzak Parola)

RFC 2945. Parolayı veya parola hash’ini sunucuya hiçbir zaman iletmeyen parola kimlik doğrulama protokolü. DH tabanlı. Sunucu ele geçirilse bile saldırgan parolayı öğrenemez.

1. İstemci: A = g^a mod N  (sunucuya gönder)
2. Sunucu: B = kv + g^b mod N  (v sunucuda saklanan parola doğrulayıcısı)
3. Her ikisi hesaplar: S = (DH'dan paylaşılan gizli)
4. Her ikisi türetir: K = H(S), M1 = H(A, B, K), M2 = H(A, M1, K)
5. İstemci M1 gönderir; Sunucu M2 gönderir — K'nın karşılıklı kanıtı

SRP doğru uygulamak karmaşıktır — TypeScript için tssrp6a, Rust için srp crate kullan.


Veri Yapıları

Merkle Ağaçları

Her yaprak düğümün bir veri bloğunun hash’i ve her yaprak olmayan düğümün çocuklarının hash’i olduğu ikili ağaç. Kök hash tüm veri kümesini taahhüt eder. Git, Bitcoin, Ethereum, sertifika şeffaflığı ve Tailscale’de kullanılır.

merkle.ts
import { createHash } from "node:crypto";
 
function sha256(veri: Buffer): Buffer {
  return createHash("sha256").update(veri).digest();
}
 
function merkleKoku(yapraklar: Buffer[]): Buffer {
  if (yapraklar.length === 0) throw new Error("bos");
  if (yapraklar.length === 1) return yapraklar[0];
 
  const sonraki: Buffer[] = [];
  for (let i = 0; i < yapraklar.length; i += 2) {
    const sol = yapraklar[i];
    const sag = yapraklar[i + 1] ?? sol; // tek sayıysa sonuncuyu çoğalt
    sonraki.push(sha256(Buffer.concat([sol, sag])));
  }
  return merkleKoku(sonraki);
}
 
function merkleKaniti(yapraklar: Buffer[], index: number): Buffer[] {
  const kanit: Buffer[] = [];
  let guncel = yapraklar;
  let idx = index;
 
  while (guncel.length > 1) {
    const sonraki: Buffer[] = [];
    for (let i = 0; i < guncel.length; i += 2) {
      const sol = guncel[i];
      const sag = guncel[i + 1] ?? sol;
      if (i === idx || i + 1 === idx) {
        kanit.push(idx % 2 === 0 ? sag : sol);
      }
      sonraki.push(sha256(Buffer.concat([sol, sag])));
    }
    idx = Math.floor(idx / 2);
    guncel = sonraki;
  }
  return kanit;
}

Hash Zincirleri

H(H(H(...H(tohum)...))) dizisi. Bir kimlik bilgisini iptal etmek N konumundaki ön görüntüyü açıklamak anlamına gelir; sonraki konumlar iptal edilemez. S/KEY (OTP), Lamport saatleri ve yalnızca ekleme destekli günlüklerde kullanılır.


Anahtar Yönetimi

Zarf Şifrelemesi

AWS KMS, Google Cloud KMS ve HashiCorp Vault tarafından kullanılan desen. Veri Şifreleme Anahtarı (DEK) veriyi şifreler; DEK’in kendisi Anahtar Şifreleme Anahtarı (KEK) ile şifrelenir. KEK donanımda (HSM/KMS) yaşar ve asla oradan çıkmaz.

zarf.ts
import { randomBytes } from "node:crypto";
 
interface ZarfSifrelenmis {
  sifreliDek: Buffer;   // KEK ile şifrelenmiş DEK — veriyle birlikte saklamak güvenli
  nonce: Buffer;
  sifreliMetin: Buffer;
  etiket: Buffer;
}
 
async function zarfSifrele(
  duzMetin: Buffer,
  kek: Buffer // Anahtar Şifreleme Anahtarı — KMS/HSM'de yaşar
): Promise<ZarfSifrelenmis> {
  // 1. Bu kayıt için yeni DEK üret
  const dek = randomBytes(32);
 
  // 2. Veriyi DEK ile şifrele
  const { nonce, sifreliMetin, etiket } = aesGcmSifrele(duzMetin, dek);
 
  // 3. DEK'i KEK ile şifrele (anahtar sarmalama)
  const sarılmısDek = aesGcmSifrele(dek, kek);
 
  // 4. Şifreli metnin yanında sifreliDek'i sakla
  // Ham DEK kullanımın ardından bellekten silinir
  dek.fill(0); // sıfırla
  return {
    sifreliDek: Buffer.concat([sarılmısDek.nonce, sarılmısDek.etiket, sarılmısDek.sifreliMetin]),
    nonce,
    sifreliMetin,
    etiket
  };
}

Anahtar Rotasyonu

Tüm veriyi hemen çözüp yeniden şifrelemeden aktif şifreleme anahtarını değiştirme. Stratejiler:

  1. Yazmada yeniden şifrele: Bir kayıt yazıldığında yeni anahtar kullan. Eski kayıtlar kademeli olarak taşınır.
  2. Çift okuma dönemi: Rotasyon sırasında sistem hem eski hem yeni anahtarları kabul eder. Tüm kayıtlar taşındıktan sonra eski anahtarı kaldır.
  3. Sürümlü anahtarlar: Her şifrelenmiş kayıt anahtar sürümünü (kid) saklar. Anahtar deposu hâlâ kullanımda olan herhangi bir sürümü sunar.
anahtar-rotasyonu.ts
interface AnahtarDeposu {
  [kid: string]: Buffer;
}
 
interface SifreliMetin {
  kid: string;   // anahtar ID — şifre çözmenin hangi anahtarı kullanacağını söyler
  nonce: string;
  sifreliMetin: string;
  etiket: string;
}
 
const anahtarlar: AnahtarDeposu = {
  "v1": Buffer.from("eski-anahtar-32-byte-xxxxxxxxxxxxxxx"),
  "v2": Buffer.from("yeni-anahtar-32-byte-yyyyyyyyyyyyyyyy"),
};
 
const AKTIF_ANAHTAR = "v2";
 
function coz(st: SifreliMetin): string {
  const anahtar = anahtarlar[st.kid];
  if (!anahtar) throw new Error(`Bilinmeyen anahtar sürümü: ${st.kid}`);
  // ... anahtar kullanarak çöz
  return "";
}

Bellek Sıfırlama

Hassas anahtar materyali kullanımdan sonra bellekten silinmelidir. JavaScript’in GC’si bunu kusursuz yapmaz — Uint8Array.fill(0) kullan ve gizlileri string’e koymaktan kaçın (değiştirilemez, garantili silme yok).

sifirla.ts
// TypedArray'ler sıfırlanabilir
function sifirla(buf: Uint8Array): void {
  buf.fill(0);
}
 
// Bu desenden kaçın — string'ler sıfırlanamaz
const anahtar = "gizli"; // ← string değiştirilemez, bellekte keyfi süre yaşar
 
// Bu deseni tercih et
const anahtar2 = new Uint8Array([...new TextEncoder().encode("gizli")]);
// ... anahtar2 kullan ...
sifirla(anahtar2); // kullanımdan sonra sil
sifirla.rs
use zeroize::Zeroize;
 
fn main() {
    let mut anahtar = vec![0u8; 32];
    // ... anahtar kullan ...
    anahtar.zeroize(); // sıfırlarla üzerine yazar, derleyicinin optimize etmesini engeller
}

Rust’taki zeroize crate derleyicinin sıfırlamayı atlamamasını garanti eder. C/C++’ta explicit_bzero veya SecureZeroMemory kullan — memset optimize edilip kaldırılabilir.

Donanım Güvenlik Modülleri (HSM)

HSM, özel anahtarları üretip saklayan kurcalamaya dayanıklı donanım aygıtıdır. Anahtarlar HSM’den hiçbir zaman şifresiz çıkmaz. İşlemler (imzala, çöz) HSM içinde gerçekleşir. CA kök anahtarları, ödeme işleme (PCI-DSS) ve devlet sınıfı anahtar yönetimi için kullanılır.

Bulut eşdeğerleri: AWS CloudHSM, Google Cloud HSM, Azure Dedicated HSM. Daha basit KMS API’leri: AWS KMS, GCP KMS (anahtarlar HSM destekli veya yazılım tabanlı olabilir).


Sabit Zamanlı Programlama

Neden Önemli?

CPU yürütme süresi bilgi sızdırır. Bir karşılaştırma ilk farklı byte’ta erken çıkarsa, zamanlama ölçümleri doğru değeri byte byte ortaya çıkarır. Bu, zamanlama yan kanal saldırılarının temelidir.

sabit-zamanli.ts
import { timingSafeEqual } from "node:crypto";
 
// YANLIŞ — ilk farklılıkta kısa devre yapar
function kotüKarsilastir(a: string, b: string): boolean {
  return a === b; // kaç byte'ın eşleştiğini sızdırır
}
 
// DOĞRU — farkın nerede olduğundan bağımsız olarak her zaman tüm byte'ları karşılaştırır
function guvenliKarsilastir(a: Buffer, b: Buffer): boolean {
  if (a.length !== b.length) {
    // Uzunluk kontrolü önceden yapılabilir — saldırgan beklenen uzunluğu zaten biliyor
    return false;
  }
  return timingSafeEqual(a, b);
}
 
// Uygulamada:
function hmacDogrula(beklenen: string, alinan: string): boolean {
  const a = Buffer.from(beklenen, "hex");
  const b = Buffer.from(alinan, "hex");
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}
sabit-zamanli.rs
use subtle::ConstantTimeEq;
 
fn mac_dogrula(beklenen: &[u8], alinan: &[u8]) -> bool {
    beklenen.ct_eq(alinan).into()
}

Sabit Zamanlı Algoritmalar

ChaCha20 tüm mimarilerde sabit zamanlı olacak şekilde tasarlanmıştır — tablo araması yok, veriye bağlı dal yok. AES, sabit zamanlı olmak için AES-NI donanım talimatları gerektirir; bunlar olmadan AES tablo aramaları, önbellek zamanlaması yoluyla anahtar bit’lerini sızdırır (AES önbellek zamanlama saldırısı, 2005).

ChaCha20’nin AES-NI olmayan cihazlarda (eski ARM, IoT donanımı) neden tercih edildiğinin nedeni budur.


Yaygın Saldırılar

Doğum Günü Saldırısı

128-bit çıktı uzayıyla ~2^64 işlemden sonra çarpışma beklenebilir (2^128 değil). Bu nedenle AES-GCM nonce’ları aynı anahtar için 2^32’den fazla mesaj şifrelenecekse rastgele üretilmemelidir (96-bit nonce’larla 2^32’de nonce çarpışma olasılığı ~%1’e ulaşır). Sayaç nonce kullan veya XChaCha20 kullan (192-bit nonce — çarpışma 2^96’da).

Dolgu Oracle Saldırısı

Bir sistem dolgunun geçerli olup olmadığını açıklarsa (farklı hata mesajları, yanıt süreleri veya davranış yoluyla), saldırgan uyarlanabilir seçilmiş şifreli metin sorguları kullanarak herhangi bir CBC şifreli metni byte byte çözebilir. Çözüm: AEAD kullan (AES-GCM, ChaCha20-Poly1305).

Nonce Yeniden Kullanımı (Felaket)

AES-GCM ve ChaCha20-Poly1305’te: bir (anahtar, nonce) çiftini yeniden kullanmak iki düz metnin XOR’unu açığa çıkarır ve kimlik doğrulamayı bozar. ECDSA’da: k rastgele nonce’unu yeniden kullanmak özel anahtarı açığa çıkarır (PS3 hack’i, 2010). Azaltma: AES-GCM için sayaçlar, deterministik imzalar (Ed25519), rastgele nonce’lar için XChaCha20.

Tekrar Oynatma Saldırısı

Saldırgan geçerli bir kimlik doğrulamalı mesajı yakalar ve yeniden iletir. Azaltma: kimlik doğrulamalı yüke zaman damgası ve/veya nonce ekle, zaman penceresi dışındaki veya görülen nonce’lara sahip mesajları reddet.

Zayıf Entropi Saldırısı

CSPRNG düşük entropili verilerle (zaman, PID) tohumlanırsa anahtarlar tahmin edilebilir. Azaltma: her zaman OS destekli entropi kullan. Asla yalnızca Date.now()’dan tohum alma.

Anahtar Taahhüt Başarısızlığı (AES-GCM)

AES-GCM anahtara taahhüt etmez — (çabayla) iki farklı anahtar altında çözülen bir şifreli metin oluşturmak mümkündür. Bu, çok alıcılı protokolleri bozar. AES-GCM-SIV kullan veya açık anahtar taahhüdü ekle. Bu aktif bir araştırma alanıdır (2023+); dağıtımdaki çoğu sistem tek alıcı kullandığından etkilenmez.


TLS 1.3 Özeti

TLS 1.3’ü anlamak, pratikte modern kriptografiyi anlamaktır.

İstemci                                    Sunucu
  |— ClientHello (desteklenen şifreler) ——→ |
  |← ServerHello (seçilen şifre)            |
  |← Certificate (açık anahtar)            |
  |← CertificateVerify (imza)              |
  |← Finished (transkript üzerinde HMAC)   |
  |— Finished ————————————————————————————→ |
  |== Şifreli uygulama verisi ===========  |

TLS 1.3 zorunlu değişiklikler:

  • RSA anahtar değişimi artık yok — yalnızca ECDHE (ileriye dönük gizlilik zorunlu)
  • Şifre takımları: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256
  • Sertifika imzası: Ed25519, ECDSA P-256, RSA-PSS
  • Anahtar türetme: her yerde HKDF
  • Kaldırılanlar: RSA şifrelemesi, statik DH, DSA, SHA-1, MD5, RC4, 3DES, CBC takımları

Hepsini Bir Araya Getirme

Eksiksiz uçtan uca şifreli mesaj akışı — Occlude’da kullanılan desen:

e2e-akis.ts
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
import { randomBytes } from "@noble/ciphers/webcrypto";
import { hkdf } from "@noble/hashes/hkdf";
import { sha256 } from "@noble/hashes/sha2";
import { argon2id } from "@noble/hashes/argon2";
 
// Adım 1: Paroladan master anahtar türet
const parola = new TextEncoder().encode("kullanici-parolasi");
const salt = randomBytes(16);
const masterAnahtar = argon2id(parola, salt, {
  t: 3,
  m: 65536,
  p: 4,
  dkLen: 32,
});
 
// Adım 2: HKDF ile mesaj başına şifreleme anahtarı türet
const mesajAnahtari = hkdf(sha256, masterAnahtar, randomBytes(32), "message-v1", 32);
 
// Adım 3: XChaCha20-Poly1305 ile şifrele
const nonce = randomBytes(24);
const duzMetin = new TextEncoder().encode("çok gizli not");
const sifreleyici = xchacha20poly1305(mesajAnahtari, nonce);
const sifreliMetin = sifreleyici.encrypt(duzMetin);
 
// Adım 4: { salt, nonce, sifreliMetin } sakla — sunucu düz metni asla görmez
const zarf = {
  salt: Buffer.from(salt).toString("base64"),
  nonce: Buffer.from(nonce).toString("base64"),
  sifreliMetin: Buffer.from(sifreliMetin).toString("base64"),
};

Algoritma Karşılaştırması

AlgoritmaTürAnahtar BoyutuHızKullanım Alanı
SHA-256Hash—HızlıBütünlük, içerik adresleme
SHA-512Hash—Hızlı (64-bit)Büyük özet, Ed25519 dahili
SHA-3-256Hash—OrtaUzunluk uzatma bağışıklığı
BLAKE2bHash—Çok hızlıDosya hashleme, genel amaç
BLAKE3Hash—Çok hızlıDosya hashleme, Merkle ağaçları
HMAC-SHA-256MAC256-bitHızlıAPI kimlik doğrulama, webhook’lar
Poly1305MAC256-bit (tek kullanım)Çok hızlıChaCha20-Poly1305 kimlik doğrulama etiketi
bcryptKDF—YavaşEski parola hashleme
scryptKDF—Yavaş + bellek zorluParola hashleme, Ethereum keystore
Argon2idKDF—Yavaş + bellek zorluParola hashleme (tercih edilen)
HKDFKDF—HızlıAnahtar genişletme
PBKDF2KDF—YavaşParola hashleme (eski)
AES-256-GCMAEAD256-bitHızlı (AES-NI)Veri şifreleme (donanım)
AES-256-CBC + HMACŞifre+MAC256-bitOrtaEski, birlikte çalışabilirlik
ChaCha20-Poly1305AEAD256-bitHızlıVeri şifreleme (yazılım)
XChaCha20-Poly1305AEAD256-bitHızlıRastgele nonce şifrelemesi
ECDSA P-256İmza256-bitOrtaTLS, JWT, sertifikalar
ECDSA secp256k1İmza256-bitOrtaBitcoin/Ethereum
Ed25519İmza256-bitHızlıSSH anahtarları, paket imzalama
RSA-PSS 4096İmza4096-bitYavaşEski PKI
X25519Anahtar değişimi256-bitHızlıTLS, E2EE anahtar anlaşması
ECDH P-256Anahtar değişimi256-bitOrtaWeb Crypto DH
RSA-OAEP 4096Şifreleme4096-bitYavaşAnahtar sarmalama, eski
ECIES (X25519+XChaCha)Hibrit şifreleme256-bitHızlıAsimetrik yük şifrelemesi

Güvenlik Kuralları

  1. Asla aynı anahtarla nonce yeniden kullanma. Benzersizliği garanti edemiyorsan XChaCha20 kullan.
  2. Asla kendin kripto yazma. Denetlenmiş kütüphaneler kullan: @noble/*, libsodium, ring (Rust).
  3. Her zaman kimlik doğrulamalı şifreleme kullan (GCM, Poly1305). HMAC olmayan ECB ve CBC kırık sayılır.
  4. Tüm sırrına bağlı karşılaştırmalar için zamanlama güvenli karşılaştırma kullan. === bilgi sızdırır.
  5. Parolalar için Argon2id. Bcrypt değil, SHA-256 değil, PBKDF2 değil (uyumluluk zorunlu kılmadıkça).
  6. Anahtarları döndür. Mükemmel kripto bile, bir anahtar ele geçirilip hiç değiştirilmezse başarısız olur.
  7. Kriptografik amaçlar için asla Math.random() kullanma. crypto.getRandomValues veya OsRng kullan.
  8. JWT alg alanını doğrula. "alg": "none" saldırısı imza doğrulamasını atlar.
  9. Mümkünse AAD kullan. Şifreli metni bağlamına bağla (kayıt ID, kullanıcı ID) — kesip yapıştırma saldırılarını önler.
  10. Kimlik doğrulama için çıplak SHA-2 değil HMAC kullan. SHA-2 uzunluk uzatma saldırılarına karşı savunmasızdır.
  11. Kullanımdan sonra anahtar materyali sıfırla. JS’de Uint8Array.fill(0), Rust’ta zeroize crate.
  12. ECDSA yerine Ed25519 tercih et. ECDSA imza başına rastgele nonce’a bağımlıdır; bozuk RNG özel anahtarı sızdırır.
  13. Zarf şifrelemesi kullan. DEK’ler veriyi şifreler; KEK’ler (HSM/KMS’de) DEK’leri şifreler. Ele geçirme kapsamlı kalır.
  14. TLS’de TLS 1.3 gerektir. Tüm CBC takımlarını, SHA-1’i ve statik RSA anahtar değişimini kaldır.
  15. Parolaları kimlik bilgisi başına rastgele salt’larla tuzla. Salt olmadan parola asla hashleme; statik global salt asla kullanma.