Node.js'te bir verinin özetini (hash) almak, HMAC imzası üretmek ya da güvenli rastgele değer oluşturmak için yerleşik node:crypto modülünü kullanırsınız; ek paket gerekmez. Kısa cevap: tek seferlik SHA-256 özeti için crypto.hash('sha256', veri), büyük ya da parça parça gelen veri için createHash(), paylaşılan bir anahtarla imza için createHmac(), imza karşılaştırmak için timingSafeEqual(), token için randomBytes(), kimlik için randomUUID() kullanırsınız. Modül belgede Stability: 2 - Stable olarak işaretli ve OpenSSL'in hash, MAC, şifreleme ve imza işlevlerini sarmalar.

Bu yazı özet, HMAC ve rastgelelik tarafını kapsar. Veriyi geri çözülebilir biçimde şifrelemek ayrı bir konudur ve AES-256-GCM şifreleme yazısında anlatılıyor. Kullanıcı parolalarını saklamak ise yine farklı bir iştir: düz SHA-256 bunun için uygun değildir, yavaş ve tuzlu algoritmalar gerekir; bu konu parola hash'leme yazısında. Örnekler Node.js v26.13.0 ile çalıştırıldı.

Hash, HMAC ve rastgele değer: hangisi ne işe yarar?

Hash, HMAC ve rastgele değer: hangisi ne işe yarar? Küçük verinin tek seferlik özeti, Büyük veya akış halindeki verinin özeti, Anahtarlı imza, İmza karşılaştırma

Üç kavram sık karıştırılır. Bir hash (özet), herhangi uzunluktaki veriden sabit uzunlukta bir parmak izi üretir; aynı girdi her zaman aynı özeti verir, özetten girdiye geri dönülemez. Dosya bütünlüğünü denetlemek, önbellek anahtarı üretmek ya da yinelenen içeriği bulmak için kullanılır. Ancak herkes aynı hesabı yapabildiği için bir hash, verinin kimden geldiğini kanıtlamaz. HMAC ise hesaba gizli bir anahtar katar: imzayı yalnızca anahtarı bilen biri üretebilir ve doğrulayabilir; webhook doğrulama ve imzalı bağlantılar bu yüzden HMAC kullanır. Kriptografik rastgelelik de tahmin edilemeyen değerler üretir; oturum token'ı, sıfırlama bağlantısı ve tek kullanımlık kod gibi değerler için Math.random() değil bu API'ler kullanılmalıdır.

İhtiyaçAPINot
Küçük verinin tek seferlik özeticrypto.hash(alg, veri)Varsayılan çıktı hex; 5 MB altı hazır veride createHash'ten hızlı olabilir
Büyük veya akış halindeki verinin özeticreateHash(alg).update().digest()Hash bir Transform stream'dir
Anahtarlı imzacreateHmac(alg, anahtar)Webhook, imzalı URL, API isteği doğrulama
İmza karşılaştırmatimingSafeEqual(a, b)Sabit zamanlı; bayt uzunlukları eşit olmalı
Token, anahtar, tuzrandomBytes(n)Kriptografik olarak güçlü rastgele baytlar
Benzersiz kimlikrandomUUID() / randomUUIDv7()v4 tamamen rastgele, v7 zaman damgalı
Aralıkta rastgele tam sayırandomInt(min, max)min dahil, max hariç; modulo yanlılığından kaçınır

crypto.hash ve createHash ile özet almak

crypto.hash(), belgedeki YAML kaydına göre v21.7.0 ve v20.12.0 ile eklendi; v25.5.0 ve v24.13.1 ile deneysel olmaktan çıktı. Veri bir dizeyse UTF-8 olarak kodlanıp özetlenir; çıktı kodlaması varsayılan olarak hex'tir, üçüncü parametre bir dize ise doğrudan çıktı kodlaması kabul edilir ('base64', 'buffer' gibi). Nesne tabanlı createHash() ise update() ile parça parça veri eklemenize ve sonunda digest() çağırmanıza izin verir.

javascript
import { hash, createHash, getHashes } from 'node:crypto';

// Tek seferlik özet: crypto.hash (varsayılan çıktı hex)
console.log(hash('sha256', 'Node.js'));
console.log(hash('sha256', 'Node.js', 'base64'));

// Aynı sonuç, nesne tabanlı API ile
const h = createHash('sha256');
h.update('Node');
h.update('.js'); // parça parça eklemek tek seferde eklemekle aynıdır
console.log(h.digest('hex'));

// Tek bir bayt farkı bambaşka bir özet üretir
console.log(hash('sha256', 'node.js').slice(0, 16));

console.log(getHashes().includes('sha256'), getHashes().includes('sha3-256'));

// digest() ikinci kez çağrılamaz
try {
  h.digest('hex');
} catch (err) {
  console.log(err.code);
}
// çıktı: 1fc0c785332e80fb931c7a11323f27abb47a0a7f8bbdf23ffca3cac46497b21c
// çıktı: H8DHhTMugPuTHHoRMj8nq7R6Cn+LvfI//KPKxGSXshw=
// çıktı: 1fc0c785332e80fb931c7a11323f27abb47a0a7f8bbdf23ffca3cac46497b21c
// çıktı: 324373d862ead39f
// çıktı: true true
// çıktı: ERR_CRYPTO_HASH_FINALIZED

Çıktılar birkaç önemli noktayı gösteriyor. "Node" ve ".js" parçalarını ayrı update() çağrılarıyla eklemek, tek parça eklemekle aynı özeti verdi; bu, akış halindeki veriyi özetlemeyi mümkün kılan özelliktir. Yalnızca bir harfin büyüklüğü değişince özet tamamen farklılaştı. digest() ikinci kez çağrıldığında ERR_CRYPTO_HASH_FINALIZED hatası geldi; belgeye göre digest() çağrıldıktan sonra Hash nesnesi tekrar kullanılamaz. Ara sonuç almak istiyorsanız v13.1.0 ile gelen hash.copy(), iç durumun bir kopyasını üretir; ama kopyalama da digest() sonrasında hata verir.

Hangi algoritmaların kullanılabildiği platformdaki OpenSSL sürümüne ve yapılandırmasına bağlıdır; listeyi crypto.getHashes() verir. Belgedeki kayda göre v26.9.0 ile OpenSSL sağlayıcılarının sunduğu hash algoritmaları da desteklenir hale geldi ve cSHAKE için functionName ile customization seçenekleri eklendi; bu seçenekler OpenSSL 4.0 veya üstüyle derlenmiş Node.js gerektirir. Hex ve base64 gibi kodlamaların ne anlama geldiğini Buffer yazısında anlattık.

Büyük dosyanın özetini belleğe almadan hesaplamak

Belge, veri büyükse ya da akış halinde geliyorsa crypto.hash() yerine createHash() kullanmayı önerir. Bir dosyanın tamamını readFile() ile okuyup özetlemek gigabaytlık dosyalarda belleği gereksiz yere doldurur. Bunun yerine dosyayı stream olarak okuyup her parçayı update() ile eklersiniz; bellekte aynı anda yalnızca bir parça bulunur.

javascript
import { createReadStream, writeFileSync } from 'node:fs';
import { createHash } from 'node:crypto';

writeFileSync('yedek.txt', 'a'.repeat(1_000_000)); // 1 MB örnek dosya

// Büyük dosyayı belleğe almadan, parça parça özetlemek
async function dosyaOzeti(yol, algoritma = 'sha256') {
  const ozet = createHash(algoritma);
  for await (const parca of createReadStream(yol)) {
    ozet.update(parca);
  }
  return ozet.digest('hex');
}

console.log(await dosyaOzeti('yedek.txt'));
// çıktı: cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0

Hash sınıfı bir Transform stream olduğu için aynı işi createReadStream(yol).pipe(createHash('sha256')) biçiminde de yapabilirsiniz; belge her iki kullanımı da gösterir. İndirdiğiniz bir dosyanın yayıncının verdiği SHA-256 değeriyle eşleşip eşleşmediğini denetlemek, yedeklerin bozulmadığını doğrulamak ya da aynı içerikli dosyaları bulmak bu desenin tipik kullanımlarıdır. Stream mantığı için stream yazısına bakın.

HMAC ile webhook imzası doğrulamak

Yaygın bir webhook deseninde gönderen taraf, isteğin gövdesini iki tarafın bildiği gizli bir anahtarla HMAC-SHA256 olarak imzalar ve imzayı bir başlıkta yollar; hangi başlığın ve hangi biçimin kullanıldığını servisin kendi belgesinden öğrenirsiniz. Alıcı olarak siz de aynı gövde ve aynı anahtarla imzayı yeniden hesaplar, gelenle karşılaştırırsınız. Karşılaştırmada sıradan === yerine timingSafeEqual() kullanılır: belgeye göre bu fonksiyon baytları sabit zamanlı bir algoritmayla karşılaştırır ve bir saldırganın yanıt süresinden doğru imzayı tahmin etmesine yarayacak zamanlama bilgisi sızdırmaz.

javascript
import { createHmac, timingSafeEqual } from 'node:crypto';

const GIZLI = 'webhook-paylasilan-anahtar'; // gerçekte ortam değişkeninden okunur

function imzala(govde) {
  return createHmac('sha256', GIZLI).update(govde).digest('hex');
}

function dogrula(govde, gelenImza) {
  const beklenen = Buffer.from(imzala(govde), 'hex');
  const gelen = Buffer.from(gelenImza, 'hex');
  // Uzunluk farklıysa timingSafeEqual hata fırlatır; önce uzunluğu kontrol edin
  if (gelen.length !== beklenen.length) return false;
  return timingSafeEqual(gelen, beklenen);
}

const govde = JSON.stringify({ siparis: 1042, durum: 'odendi' });
const imza = imzala(govde);
console.log(imza.length);
console.log(dogrula(govde, imza));
console.log(dogrula(govde.replace('1042', '1043'), imza));
console.log(dogrula(govde, 'abcd'));

try {
  timingSafeEqual(Buffer.from('ab'), Buffer.from('abc'));
} catch (err) {
  console.log(err.code);
}
// çıktı: 64
// çıktı: true
// çıktı: false
// çıktı: false
// çıktı: ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH

Örnekte doğru imza true, değiştirilmiş gövde false verdi. Belgeye göre timingSafeEqual() iki girdinin bayt uzunluğu farklıysa hata fırlatır; örnekteki son satır bu hatanın kodunu (ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH) gösteriyor. Bu yüzden dogrula() fonksiyonu önce uzunluğu denetliyor. Belge ayrıca bu fonksiyonun çevresindeki kodun zamanlama açısından güvenli olacağını garanti etmediğini hatırlatır.

Pratikte iki ayrıntıya dikkat edin. Birincisi, imza ham gövde baytları üzerinden hesaplanır; gövdeyi önce JSON olarak ayrıştırıp sonra yeniden dizeye çevirirseniz boşluklar ve alan sırası değişebilir, imza tutmaz. Ham gövdeyi nasıl okuyacağınızı node:http sunucu yazısında anlattık. İkincisi, belge anahtar olarak dize kullanırken dikkatli olmanızı ister; rastgele bir kaynaktan üretilmiş bir anahtarın uzunluğu algoritmanın blok boyutunu (SHA-256 için 512 bit) aşmamalıdır. Gizli anahtarı koda yazmak yerine ortam değişkeninden okuyun; bunun için --env-file ve process.env yazısına bakabilirsiniz.

Güvenli rastgele değerler: randomBytes, randomUUID, randomInt

randomBytes(n) kriptografik olarak güçlü, sözde rastgele n bayt üretir. Geri çağırma verilmezse senkron çalışıp bir Buffer döndürür; verilirse işi asenkron yapar. randomUUID() rastgele bir RFC 4122 sürüm 4 UUID üretir; belgeye göre performans için 128 UUID'e yetecek rastgele veri önceden üretilip önbellekte tutulur, bunu istemiyorsanız disableEntropyCache: true verirsiniz. v26.1.0 ile gelen randomUUIDv7() ise ilk 48 bitine milisaniye hassasiyetli bir Unix zaman damgası koyar; belge bunun zamana göre sıralanabilen veritabanı anahtarları için uygun olduğunu, ama gömülü zaman damgası monoton olmayan bir saate dayandığı için kesin artan sıranın garanti edilmediğini belirtir.

javascript
import { randomBytes, randomUUID, randomUUIDv7, randomInt } from 'node:crypto';

const token = randomBytes(32).toString('base64url'); // 32 bayt = 256 bit
console.log(token.length, /^[A-Za-z0-9_-]+$/.test(token));

console.log(randomBytes(16).toString('hex').length);

const uuid = randomUUID();
console.log(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(uuid));

const v7 = randomUUIDv7();
console.log(v7[14]); // sürüm hanesi

// Zar: 1 dahil, 7 hariç
const zarlar = Array.from({ length: 1000 }, () => randomInt(1, 7));
console.log(Math.min(...zarlar), Math.max(...zarlar));

// 6 haneli tek kullanımlık kod (baştaki sıfırlar korunur)
const kod = String(randomInt(0, 1_000_000)).padStart(6, '0');
console.log(kod.length);
// çıktı: 43 true
// çıktı: 32
// çıktı: true
// çıktı: 7
// çıktı: 1 6
// çıktı: 6

randomInt(min, max) belgeye göre min dahil, max hariç bir tam sayı döndürür ve modulo yanlılığından kaçınacak biçimde uygulanmıştır; aralık 2 üzeri 48'den küçük olmalı, sınırlar güvenli tam sayı olmalıdır. Tek kullanımlık kod gibi baştaki sıfırların önemli olduğu durumlarda sayıyı padStart() ile tamamlamak gerekir. Token'ları URL içinde taşıyacaksanız base64url kodlaması, + ve / karakterlerinden kaçındığı için pratiktir. Rastgele değerin uzunluğu güvenliği belirler: 32 bayt 256 bit eder.

Bir uyarı daha: randomBytes() fonksiyonunun senkron sürümü, istenen baytlar üretilene kadar olay döngüsünü bekletir. Node.js Learn rehberi, crypto.randomBytes(), crypto.scrypt() ve crypto.pbkdf2() gibi fonksiyonların asenkron sürümlerinin libuv'nin Worker Pool havuzunda çalıştığını, senkron sürümlerinin ise bir sunucuda kaçınılması gereken API'ler arasında olduğunu belirtir. Küçük token'lar için senkron çağrının etkisi küçüktür, ama yüksek trafikli bir sunucuda büyük miktarda rastgele veri gerekiyorsa asenkron sürümü tercih edin.

Zayıf algoritmalar ve dizelerle ilgili tuzaklar

Belgenin "Support for weak or compromised algorithms" bölümü, modülün güvenliği kırılmış bazı algoritmaları hâlâ desteklediğini ve algoritma seçiminin sorumluluğunun kullanıcıda olduğunu söyler. NIST SP 800-131A önerilerine dayanarak MD5 ve SHA-1'in dijital imza gibi çakışma direnci gereken yerlerde artık kabul edilemez olduğunu belirtir. Yeni kodda SHA-256 veya SHA-512 ailesini kullanın; MD5'i yalnızca güvenlik dışı amaçlarla (eski bir sistemin beklediği sağlama toplamı gibi) düşünün.

Belge dizelerle ilgili iki tuzağı da anlatır. Birincisi, her bayt dizisi geçerli UTF-8 değildir; bir hash ya da şifreleme çıktısını toString('utf8') ile dizeye çevirip geri Buffer'a çevirirseniz geçersiz diziler yer değiştirme karakterine dönüşür ve veri bozulur. Bu yüzden ikili çıktıyı hex veya base64 ile taşıyın. İkincisi, bazı Unicode karakterler birden fazla biçimde yazılabilir; aynı görünen iki dize farklı baytlara, dolayısıyla farklı özetlere sahip olabilir. Belge, kullanıcı girdisini kriptografik API'lere vermeden önce String.prototype.normalize() ile normalleştirmeyi önerir. Dize metotlarını string metotları yazısında anlattık.

Mini örnek: imzalı ve süreli indirme bağlantısı

Öğrendiklerimizi birleştirelim. Bir dosya için, belirli bir süre sonra geçersiz olan ve kimsenin değiştiremeyeceği bir indirme bağlantısı üretmek istiyorsunuz. Bağlantıya dosya adını ve son geçerlilik zamanını koyar, bu ikisinin HMAC imzasını da ekleriz. Sunucu isteği alınca aynı imzayı yeniden hesaplar, timingSafeEqual() ile karşılaştırır ve sürenin dolup dolmadığına bakar. Dosya adını ya da süreyi değiştiren biri imzayı yeniden üretemez, çünkü anahtarı bilmez.

javascript
import { createHmac, timingSafeEqual } from 'node:crypto';

const ANAHTAR = Buffer.from('b1f0c3d2a9e84765b1f0c3d2a9e84765', 'hex'); // örnek; gerçekte randomBytes(32)

function imza(dosya, sonZaman) {
  return createHmac('sha256', ANAHTAR).update(`${dosya}\n${sonZaman}`).digest('base64url');
}

function baglantiUret(dosya, simdi, gecerlilikSn) {
  const son = simdi + gecerlilikSn * 1000;
  const url = new URL('https://ornek.test/indir');
  url.searchParams.set('dosya', dosya);
  url.searchParams.set('son', String(son));
  url.searchParams.set('imza', imza(dosya, son));
  return url.href;
}

function baglantiDogrula(href, simdi) {
  const q = new URL(href).searchParams;
  const dosya = q.get('dosya') ?? '';
  const son = Number(q.get('son'));
  const beklenen = Buffer.from(imza(dosya, son));
  const gelen = Buffer.from(q.get('imza') ?? '');
  if (gelen.length !== beklenen.length || !timingSafeEqual(gelen, beklenen)) return 'imza geçersiz';
  if (simdi > son) return 'süresi dolmuş';
  return 'geçerli: ' + dosya;
}

const simdi = 1_800_000_000_000; // sabit zaman, çıktı tekrarlanabilir olsun
const link = baglantiUret('fatura-2026-11.pdf', simdi, 600);
console.log(baglantiDogrula(link, simdi + 5_000));
console.log(baglantiDogrula(link, simdi + 601_000));
console.log(baglantiDogrula(link.replace('fatura-2026-11', 'fatura-2026-12'), simdi));
console.log(baglantiDogrula(link.replace(/son=\d+/, 'son=9999999999999'), simdi));
// çıktı: geçerli: fatura-2026-11.pdf
// çıktı: süresi dolmuş
// çıktı: imza geçersiz
// çıktı: imza geçersiz

Örnekte zamanı parametre olarak verdik ki çıktı her çalıştırmada aynı olsun; gerçek kodda Date.now() kullanırsınız. İmzaya hangi alanları kattığınız önemlidir: imzalanmayan bir alan (örneğin indirme hızı sınırı) saldırgan tarafından serbestçe değiştirilebilir. URL ayrıştırma için kullandığımız URL ve URLSearchParams sınıflarını URL yazısında anlattık.

Sık yapılan hatalar

Sık yapılan hatalar: Parolayı SHA-256 ile saklamak, İmzayı === ile karşılaştırmak, Güvenlik için Math.random() kullanmak, Aynı Hash nesnesinde digest() sonrası update() yapmak
  • Parolayı SHA-256 ile saklamak: Hızlı hash'ler parola saklamak için uygun değildir; bcrypt, scrypt veya Argon2 gibi yavaş ve tuzlu yöntemler kullanın.
  • İmzayı === ile karşılaştırmak: timingSafeEqual() kullanın ve önce uzunlukları denetleyin.
  • Güvenlik için Math.random() kullanmak: Token ve kod için randomBytes(), randomInt() veya randomUUID() kullanın.
  • Aynı Hash nesnesinde digest() sonrası update() yapmak: Nesne tek kullanımlıktır; yeni bir nesne oluşturun ya da önceden copy() alın.
  • Ayrıştırılmış JSON üzerinden imza doğrulamak: İmza ham gövdeye göre hesaplanır.
  • İkili çıktıyı UTF-8 dizeye çevirmek: Veri bozulur; hex, base64 veya base64url kullanın.
  • Gizli anahtarı kaynak koda gömmek: Ortam değişkeni ya da gizli bilgi yönetimi kullanın; ayrıntı .env yazısında.

Sık Sorulan Sorular

crypto.hash ile createHash arasında ne fark var?

İkisi de aynı algoritmalarla aynı özeti üretir. crypto.hash() veri hazır ve küçükken tek çağrıda sonuç verir; belgeye göre 5 MB ve altındaki veride nesne tabanlı API'den hızlı olabilir. Veri büyükse ya da akış halinde geliyorsa createHash() önerilir.

Web Crypto API Node.js'te kullanılabilir mi?

Evet. Belgeye göre crypto.webcrypto özelliği (v15.0.0 ile eklendi) Web Crypto API standardının bir uygulamasını verir; ayrıntılar Node.js belgesinin ayrı Web Crypto sayfasındadır. Tarayıcıyla ortak kod yazıyorsanız bu API işe yarar; yalnızca Node.js hedefliyorsanız bu yazıdaki node:crypto fonksiyonları daha kısa bir kullanım sunar.

UUID v4 mü v7 mi kullanmalıyım?

Tamamen rastgele, hiçbir bilgi sızdırmayan bir kimlik istiyorsanız v4. Veritabanında oluşturulma sırasına göre kabaca sıralanan anahtarlar istiyorsanız v7 uygun olabilir; ancak v7 oluşturulma zamanını içinde taşır ve belgeye göre kesin artan sıra garanti edilmez.

node:crypto her Node.js kurulumunda var mı?

Belgeye göre Node.js crypto desteği olmadan derlenebilir; bu durumda modülü yüklemek hata fırlatır. CommonJS'te bu hata try...catch ile yakalanabilir; ESM'de ise statik import yerine await import('node:crypto') ile dinamik yükleme önerilir. Resmî dağıtımlarla kurulan Node.js'te modül bulunur.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Node.js öğrenme rehberi · AES-256-GCM şifreleme · parola hash'leme · Buffer ve kodlamalar · node:http ile sunucu