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.
Modern kriptografik primitiflere derinlemesine dalış — ECDSA, SHA-256, ChaCha20-Poly1305, AES-GCM, Argon2id, X25519, HKDF ve daha fazlası. Çalıştırılabilir kod örnekleriyle.
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.
Primitiflere geçmeden önce, her ailenin hangi problemi çözdüğünü içselleştir:
| Özellik | Anlamı | Primitive |
|---|---|---|
| Gizlilik (Confidentiality) | Yalnızca hedeflenen alıcılar okuyabilir | Simetrik/asimetrik şifreleme |
| Bütünlük (Integrity) | Veri manipüle edilmemiştir | Hash fonksiyonları, MAC’lar |
| Özgünlük (Authenticity) | Gönderen iddia ettiği kişidir | MAC’lar, dijital imzalar |
| İnkar Edilemezlik (Non-repudiation) | Gönderen gönderdiğini inkâr edemez | Dijital imzalar (MAC’lar değil) |
| İleriye Dönük Gizlilik (Forward Secrecy) | Uzun vadeli anahtarın ele geçirilmesi geçmiş oturumları açığa çıkarmaz | Geç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.
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.
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.
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.
// 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öneruse 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.
İş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.
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.
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:
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("");
}Ö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ı).
import { createHash } from "node:crypto";
function sha512(data: string): string {
return createHash("sha512").update(data, "utf-8").digest("hex");
}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).
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");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.
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.
import { createHash } from "node:crypto"; // Node 21+ blake2b512 / blake2s256 destekler
const h = createHash("blake2b512").update("hello").digest("hex");use blake2::{Blake2b512, Digest};
let mut h = Blake2b512::new();
h.update(b"hello world");
let result = h.finalize();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ı.
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:
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");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.
// 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.
Hash tabanlı Mesaj Doğrulama Kodu. Hem bütünlüğü hem özgünlüğü kanıtlar — gönderenin gizli anahtarı bilmesi gerekir.
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ırMAC doğrulaması için zamanlama güvenli karşılaştırma kritiktir — asla === kullanma:
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);
}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.
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.
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.
use std::collections::HashMap;
// HashMap varsayılan olarak SipHash-1-3 kullanır — bedava gelir
let mut map: HashMap<String, i32> = HashMap::new();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.
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:
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:
m=65536, t=3, p=4m=262144, t=4, p=4m=16384, t=2, p=1Colin 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).
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,
});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, ¶ms, &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.
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.
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:
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);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).
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:
// 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");Eski ama hâlâ yaygın kullanımda. Yinelemeli hashleme — Argon2’den daha az bellek zorlu, ancak evrensel desteklenir.
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):
Daha yüksek iterasyon sayısı her zaman daha iyidir; sunucunda ~300ms’ye göre kalibre et.
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.
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.
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ı:
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.
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:
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)
}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.
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.
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.
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şlemAnahtarları anahtarlarla şifrelemek için kullanılır — zarf şifrelemesi ve güvenli anahtar dışa aktarımı için standart.
// 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üvenliAES-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.
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);
}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.
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:
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")
}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.
// 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 olurYaygın AAD adayları: kayıt/satır ID’si, kullanıcı ID’si, şema versiyonu, protokol versiyonu, zaman damgası (kaba).
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.
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); // => trueBitcoin/Ethereum eğrisi. Web Crypto’da yok; @noble/curves kullan.
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);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.
const cifti = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-384" },
true,
["sign", "verify"]
);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ı.
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); // => trueRust’ta:
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:
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.
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 besleHarici kütüphane olmadan tarayıcı-native anahtar anlaşması için:
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/çözerKlasik. 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).
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 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.
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 | ECC Eşdeğeri | Güvenlik Bit | Kırılma Tahmini |
|---|---|---|---|
| 1024-bit | — | ~80 | Çoktan kırıldı |
| 2048-bit | P-224 | ~112 | ~2030 |
| 3072-bit | P-256 | ~128 | ~2040 |
| 4096-bit | P-384 | ~140 | >2050 |
Yeni sistemler için: 4096-bit RSA veya P-256/Ed25519 (eşdeğer güvenlik, çok daha küçük anahtarlar).
Anahtarlar standart formatlarda saklanmalı ve iletilmelidir. Asla kendi formatını icat etme.
Web Crypto anahtarları için standart JSON gösterimi. Platformlar arası taşınabilir.
// 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"]
);Binary DER kodlamalı formatlar — PEM dosyaları için standart. OpenSSL, TLS sertifikaları ve SSH’nin arka planda kullandığı şey.
// Ö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");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-----...
}Simetrik anahtarlar ve ham ECC anahtar materyali için (başlık yok, saf byte’lar):
// 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"]
);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.
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.
Bir imzalayan, içeriğini görmeden bir mesajı imzalar. Anonim kimlik bilgisi sistemleri ve e-nakit için kullanılır (Chaum 1982).
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.
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.
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);İ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.
DH + imzalar. Her taraf kimliğini kanıtlamak için transkripti imzalar. SSH anahtar kimlik doğrulamasının temeli.
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
Uçtan uca şifreli mesajlaşmanın altın standardı. İkisini birleştirir:
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.
İmzalanmış (veya şifreli) talep yükü. Üç base64url kodlamalı bölüm: başlık.yük.imza.
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ı:
alg alanını her zaman doğrula — klasik saldırı "alg": "none" olarak ayarlar.iss, aud, exp, nbf’yi her zaman doğrula.RS256 veya ES256 (asimetrik) kullan. HS256 gizliyi paylaşmayı gerektirir.JWT imzaları için altta yatan spec. Birden fazla imzalayıcıyı destekler (JSON serileştirmesi).
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.
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);RFC 4226. Sayaç tabanlı OTP: HOTP(K, C) = Truncate(HMAC-SHA1(K, C)).
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");
}RFC 6238. Sayacın Math.floor(Date.now() / 1000 / 30) — 30 saniyelik zaman adımıyla değiştirildiği HOTP.
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.
Gizliyi açıklamadan gizliyi bildiğini kanıtla. Örnekler:
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.
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;
}Ö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
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.
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.
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;
}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.
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.
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
};
}Tüm veriyi hemen çözüp yeniden şifrelemeden aktif şifreleme anahtarını değiştirme. Stratejiler:
kid) saklar. Anahtar deposu hâlâ kullanımda olan herhangi bir sürümü sunar.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 "";
}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).
// 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 siluse 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.
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).
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.
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);
}use subtle::ConstantTimeEq;
fn mac_dogrula(beklenen: &[u8], alinan: &[u8]) -> bool {
beklenen.ct_eq(alinan).into()
}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.
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).
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).
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.
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.
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.
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’ü 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:
TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256Eksiksiz uçtan uca şifreli mesaj akışı — Occlude’da kullanılan desen:
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 | Tür | Anahtar Boyutu | Hız | Kullanım Alanı |
|---|---|---|---|---|
| SHA-256 | Hash | — | Hızlı | Bütünlük, içerik adresleme |
| SHA-512 | Hash | — | Hızlı (64-bit) | Büyük özet, Ed25519 dahili |
| SHA-3-256 | Hash | — | Orta | Uzunluk uzatma bağışıklığı |
| BLAKE2b | Hash | — | Çok hızlı | Dosya hashleme, genel amaç |
| BLAKE3 | Hash | — | Çok hızlı | Dosya hashleme, Merkle ağaçları |
| HMAC-SHA-256 | MAC | 256-bit | Hızlı | API kimlik doğrulama, webhook’lar |
| Poly1305 | MAC | 256-bit (tek kullanım) | Çok hızlı | ChaCha20-Poly1305 kimlik doğrulama etiketi |
| bcrypt | KDF | — | Yavaş | Eski parola hashleme |
| scrypt | KDF | — | Yavaş + bellek zorlu | Parola hashleme, Ethereum keystore |
| Argon2id | KDF | — | Yavaş + bellek zorlu | Parola hashleme (tercih edilen) |
| HKDF | KDF | — | Hızlı | Anahtar genişletme |
| PBKDF2 | KDF | — | Yavaş | Parola hashleme (eski) |
| AES-256-GCM | AEAD | 256-bit | Hızlı (AES-NI) | Veri şifreleme (donanım) |
| AES-256-CBC + HMAC | Şifre+MAC | 256-bit | Orta | Eski, birlikte çalışabilirlik |
| ChaCha20-Poly1305 | AEAD | 256-bit | Hızlı | Veri şifreleme (yazılım) |
| XChaCha20-Poly1305 | AEAD | 256-bit | Hızlı | Rastgele nonce şifrelemesi |
| ECDSA P-256 | İmza | 256-bit | Orta | TLS, JWT, sertifikalar |
| ECDSA secp256k1 | İmza | 256-bit | Orta | Bitcoin/Ethereum |
| Ed25519 | İmza | 256-bit | Hızlı | SSH anahtarları, paket imzalama |
| RSA-PSS 4096 | İmza | 4096-bit | Yavaş | Eski PKI |
| X25519 | Anahtar değişimi | 256-bit | Hızlı | TLS, E2EE anahtar anlaşması |
| ECDH P-256 | Anahtar değişimi | 256-bit | Orta | Web Crypto DH |
| RSA-OAEP 4096 | Şifreleme | 4096-bit | Yavaş | Anahtar sarmalama, eski |
| ECIES (X25519+XChaCha) | Hibrit şifreleme | 256-bit | Hızlı | Asimetrik yük şifrelemesi |
@noble/*, libsodium, ring (Rust).=== bilgi sızdırır.Math.random() kullanma. crypto.getRandomValues veya OsRng kullan.alg alanını doğrula. "alg": "none" saldırısı imza doğrulamasını atlar.Uint8Array.fill(0), Rust’ta zeroize crate.