Tarayıcıda kriptografik işlemler için Web Crypto API kullanılır: güvenli rastgele sayı için crypto.getRandomValues(), benzersiz kimlik için crypto.randomUUID(), SHA-256 gibi özetler için crypto.subtle.digest(), şifreleme için de crypto.subtle.generateKey(), encrypt() ve decrypt(). subtle altındaki bütün metotlar söz (promise) döndürür, girdi olarak metin değil bayt dizisi (ArrayBuffer veya TypedArray) bekler ve güvenli bağlam (HTTPS) gerektirir.

Bu yazıda rastgele değer üretimini, SHA ailesiyle özet almayı, AES-GCM ile şifreleyip çözmeyi, CryptoKey nesnesini, iv, additionalData ve tagLength parametrelerini ve hata türlerini anlatıyoruz. Tarayıcıdan bağımsız oldukları için bütün örnekleri Node.js 24 ile çalıştırıp çıktıları doğruladık; yayınlanan kod tarayıcıda da aynen geçerlidir. Serinin tamamı için Web API öğrenme rehberi sayfasına bakabilirsiniz. Örneklerde üst düzey await kullanıldığı için tarayıcıda bir modül betiği (type="module") içinde çalıştırın veya kodu bir async fonksiyonun içine alın.

Crypto ve SubtleCrypto: iki katman

Crypto ve SubtleCrypto: iki katman: crypto.getRandomValues(dizi), crypto.randomUUID(), crypto.subtle.digest(), crypto.subtle.generateKey()

API iki arayüzden oluşur. Crypto, sayfada window.crypto, worker içinde self.crypto olarak bulunan temel nesnedir ve iki yardımcı metot sunar: getRandomValues() ve randomUUID(). Crypto.subtle özelliği ise asıl kriptografik işlemleri barındıran SubtleCrypto nesnesini verir. MDN, arayüz adındaki "subtle" sözcüğünün bu algoritmaların ince kullanım gereksinimleri olduğunu, dikkatli kullanılmaları gerektiğini anlatmak için seçildiğini söyler.

Güvenli bağlam kuralı iki katmanda farklıdır. MDN, getRandomValues() metodunun Crypto arayüzünün güvensiz bağlamda da kullanılabilen tek üyesi olduğunu belirtir. randomUUID() ve subtle ise güvenli bağlam ister; CryptoKey arayüzü de güvenlik nedeniyle yalnızca güvenli bağlamda kullanılabilir. Sitenizde crypto.subtle tanımsız görünüyorsa ilk kontrol etmeniz gereken şey sayfanın HTTPS üzerinden açılıp açılmadığıdır; ayrıntılar DOMException ve güvenli bağlam hataları yazısında, HTTPS kurulumu HTTPS ve TLS 1.3 rehberi yazısında.

ÜyeNe yaparGüvenli bağlam
crypto.getRandomValues(dizi)Tamsayı tipli diziyi kriptografik rastgele değerlerle doldururGerekmez
crypto.randomUUID()36 karakterlik v4 UUID üretirGerekir
crypto.subtle.digest()Veriden sabit uzunlukta özet üretirGerekir
crypto.subtle.generateKey()Yeni anahtar veya anahtar çifti üretirGerekir
crypto.subtle.encrypt() / decrypt()Veriyi şifreler ve çözerGerekir
crypto.subtle.sign() / verify()İmza üretir ve doğrular (HMAC dahil)Gerekir

getRandomValues ve randomUUID

getRandomValues() ona verdiğiniz tamsayı tipli diziyi yerinde doldurur ve aynı diziyi geri döndürür; kopya oluşturmaz. Kabul edilen türler Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array ve BigUint64Array olarak listelenir; Float32Array gibi kayan noktalı diziler kabul edilmez. Dizinin bayt uzunluğu 65.536 değerini aşarsa QuotaExceededError fırlatılır, yani tek çağrıda en çok 64 KiB rastgele veri alabilirsiniz.

MDN, uygulamaların performans için gerçek bir donanım rastgele sayı üreticisi değil, yeterli entropiyle tohumlanmış bir sözde rastgele sayı üreticisi kullandığını, ama bunun kriptografik amaçlara uygun olduğunu açıklar. Math.random() ise kriptografik olmayan bir kaynak olarak anılır; parola sıfırlama bağlantısı, oturum belirteci veya şifreleme vektörü üretirken asla kullanılmamalıdır. MDN ayrıca anahtar üretimi için getRandomValues() yerine güvenli bağlamda çalışması garanti olan generateKey() metodunu tercih etmenizi önerir.

javascript
const dizi = new Uint8Array(16);
const sonuc = crypto.getRandomValues(dizi);
console.log(sonuc === dizi); // true
console.log(dizi.length); // 16

const id = crypto.randomUUID();
console.log(id.length); // 36
console.log(id[14]); // 4

try {
  crypto.getRandomValues(new Uint8Array(65537));
} catch (hata) {
  console.log(hata.name); // QuotaExceededError
}

id[14] satırı, v4 UUID biçiminde üçüncü grubun her zaman 4 ile başladığını gösteriyor. randomUUID() parametre almaz ve her çağrıda kriptografik olarak güvenli bir üreticiyle yeni bir değer verir; veritabanı kayıtları veya istemci tarafı geçici kimlikler için uygundur. Bu örneklerin gerçek değerleri her çalıştırmada değişir, bu yüzden çıktı olarak yalnızca uzunlukları ve sabit kalan kısımları gösterdik.

digest ile SHA-256 özeti almak

digest(algoritma, veri), verinin sabit uzunlukta bir özetini (hash) üretir. Algoritma bir dize veya name özelliği olan bir nesne olabilir; desteklenen değerler "SHA-1", "SHA-256", "SHA-384" ve "SHA-512" şeklindedir. Veri bir ArrayBuffer, TypedArray veya DataView olmalıdır; metni önce TextEncoder ile UTF-8 baytlarına çevirirsiniz. Söz, özeti içeren bir ArrayBuffer ile tamamlanır.

Özeti göstermek veya karşılaştırmak için genellikle onaltılık (hex) dizeye çevirmek gerekir. Aşağıdaki fonksiyon bunu her tarayıcıda çalışan bir yöntemle yapıyor. Tek harf farkının bile tamamen farklı bir özet ürettiğine dikkat edin:

javascript
async function sha256Hex(metin) {
  const veri = new TextEncoder().encode(metin);
  const ozet = await crypto.subtle.digest("SHA-256", veri);
  return Array.from(new Uint8Array(ozet), (b) => b.toString(16).padStart(2, "0")).join("");
}

const h1 = await sha256Hex("merhaba");
console.log(h1);
// 4c6bcdd55f3153e1939669ab1ec039e4059174dc25abdfcb2f58868849b4d61b
console.log(h1.length); // 64
console.log(h1 === (await sha256Hex("Merhaba"))); // false
AlgoritmaÇıktı (bit)Çıktı (bayt)Not
SHA-116020MDN: kriptografik uygulamalarda kullanmayın, savunmasız kabul edilir
SHA-25625632Dosya özetlerinde yaygın tercih
SHA-38438448
SHA-51251264

Bayt sütunundaki değerleri dört algoritmayla aynı girdinin byteLength değerini yazdırarak doğruladık. digest() ile ilgili iki sınırı bilmeniz gerekir. Birincisi, MDN bu API'nin akış (streaming) girdisini desteklemediğini, verinin tamamını belleğe alıp vermeniz gerektiğini belirtir; çok büyük dosyaları özetlerken bunu hesaba katın. İkincisi, anahtarlı özet yani HMAC arıyorsanız digest() değil sign() metodunu HMAC algoritmasıyla kullanmalısınız.

MDN'nin "kriptografik olmayan kullanımlar" rehberi, digest() metodunun en basit ve en yararlı işinin dosya özeti çıkarmak olduğunu anlatır: kullanıcının seçtiği bir dosyanın SHA-256 değerini indirme sayfasında yayınlanan değerle karşılaştırarak dosyanın değişmediğini doğrulayabilirsiniz. Dosyayı arrayBuffer() ile okuyup doğrudan digest() metoduna verirsiniz; dosya okuma File API ve Blob yazısında anlatılıyor. Aynı rehber, özetin tek yönlü bir işlem olduğunu ve özetten asıl veriye geri dönülemeyeceğini de vurgular.

AES-GCM ile şifreleme ve çözme

AES-GCM ile şifreleme ve çözme: Anahtar üretmek: generateKey ve CryptoKey, Şifrele, çöz, bozulmayı yakala, additionalData: şifrelenmeyen ama doğrulanan veri

Web Crypto API dört şifreleme algoritması sunar: açık anahtarlı RSA-OAEP ve AES tabanlı üç simetrik mod olan CTR, CBC ve GCM. MDN kimlik doğrulamalı şifreleme kullanmanızı güçlü biçimde önerir. Kimlik doğrulama, şifreli metnin bir saldırgan tarafından değiştirilip değiştirilmediğini kontrol eder. CTR ve CBC bunu varsayılan olarak sağlamaz; GCM ise yerleşik kimlik doğrulama içerir ve bu yüzden çoğu zaman diğer iki moda tercih edilir.

Anahtar üretmek: generateKey ve CryptoKey

generateKey(algoritma, cikarilabilir, kullanimlar) üç parametre alır. AES için algoritma nesnesi name ve length içerir. cikarilabilir (extractable) değeri anahtarın exportKey() veya wrapKey() ile dışarı aktarılıp aktarılamayacağını belirler. kullanimlar dizisi anahtarla neler yapılabileceğini sınırlar: encrypt, decrypt, sign, verify, deriveKey, deriveBits, wrapKey, unwrapKey. Simetrik bir anahtar için bu dizi boş verilirse söz SyntaxError ile reddedilir; bunu da çalıştırarak gördük.

Dönen CryptoKey nesnesinin dört özelliği vardır: type ("secret", "private" veya "public"), extractable, algorithm ve usages. Anahtarın ham baytları JavaScript tarafından görülmez; bu, çıkarılamaz anahtarları güvende tutmanın temelidir.

Şifrele, çöz, bozulmayı yakala

AES-GCM için algoritma parametresi bir AesGcmParams nesnesidir. Zorunlu alanı iv (başlangıç vektörü) değeridir ve MDN bunun en kritik kuralını açıkça yazar: aynı anahtarla yapılan her şifrelemede iv benzersiz olmalı, yani bir iv aynı anahtarla asla tekrar kullanılmamalıdır. AES-GCM spesifikasyonu 96 bit (12 bayt) uzunluk önerir ve değer genellikle rastgele üreticiden gelir. iv gizli değildir, yalnızca benzersizdir; şifreli mesajın yanında açık olarak saklanıp gönderilebilir. Çözme sırasında şifrelemede kullanılan iv değerinin aynısı verilmelidir.

javascript
const anahtar = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"],
);
console.log(anahtar.type); // secret
console.log(anahtar.extractable); // false
console.log(JSON.stringify(anahtar.usages)); // ["encrypt","decrypt"]

const iv = crypto.getRandomValues(new Uint8Array(12));
const acik = new TextEncoder().encode("Sipariş no: 1042");
console.log(acik.byteLength); // 17

const sifreli = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, anahtar, acik);
console.log(sifreli.byteLength); // 33

const cozulmus = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, anahtar, sifreli);
console.log(new TextDecoder().decode(cozulmus)); // Sipariş no: 1042

// Şifreli metnin tek bir bitini değiştirelim
const bozuk = new Uint8Array(sifreli);
bozuk[0] ^= 1;
try {
  await crypto.subtle.decrypt({ name: "AES-GCM", iv }, anahtar, bozuk);
} catch (hata) {
  console.log(hata.name); // OperationError
}

Çıktılardaki sayılar kendi başına öğreticidir. "Sipariş no: 1042" metni 16 karakter olduğu halde 17 bayttır, çünkü "ş" UTF-8 içinde iki bayt tutar. Şifreli çıktı 33 bayttır, yani açık metinden tam 16 bayt uzundur. MDN kimlik doğrulama etiketinin boyutunu belirleyen tagLength parametresinin verilmezse 128 bit, yani 16 bayt olduğunu belirtir; aradaki fark bu etiketten gelir. Tek bir biti değiştirdiğimizde çözme OperationError ile reddedildi: kimlik doğrulamalı şifrelemenin sağladığı koruma tam olarak budur. Yanlış iv ile çözme denediğimizde de aynı hatayı aldık.

additionalData: şifrelenmeyen ama doğrulanan veri

additionalData isteğe bağlı bir alandır. Buraya verdiğiniz veri şifrelenmez ama şifreli veriyle birlikte doğrulanır. Çözme çağrısında aynı veri verilmezse işlem hata fırlatır. MDN bunu, ilişkili veriyi şifrelemeden doğrulamanın bir yolu olarak tanımlar. Örneğin şifreli bir notu sahibinin kimliğine bağlamak, notun başka bir kullanıcının kaydına kopyalanmasını fark edilir kılar:

javascript
const anahtar = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ek = new TextEncoder().encode("kullanici-42");

const sifreli = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv, additionalData: ek },
  anahtar,
  new TextEncoder().encode("gizli not"),
);
const tamam = await crypto.subtle.decrypt({ name: "AES-GCM", iv, additionalData: ek }, anahtar, sifreli);
console.log(new TextDecoder().decode(tamam)); // gizli not

try {
  await crypto.subtle.decrypt(
    { name: "AES-GCM", iv, additionalData: new TextEncoder().encode("kullanici-43") },
    anahtar,
    sifreli,
  );
} catch (hata) {
  console.log(hata.name); // OperationError
}
AesGcmParams alanıZorunlu muKural
nameEvet"AES-GCM" olmalı
ivEvetAynı anahtarla her şifrelemede benzersiz; önerilen 12 bayt; gizli olması gerekmez
additionalDataHayırŞifrelenmez ama doğrulanır; çözmede aynısı verilmeli
tagLengthHayırEtiket uzunluğu (bit), varsayılan 128; Web Crypto 32, 64, 96, 104, 112, 120 veya 128 kabul eder

Hata türleri ve anahtar saklama

encrypt() ve decrypt() iki tür DOMException ile reddedilebilir. InvalidAccessError, istenen işlem verilen anahtar için geçerli olmadığında gelir; örneğin yalnızca encrypt kullanımına izin verilmiş bir anahtarla çözmeye çalışırsanız. OperationError ise işleme özgü bir nedenle başarısızlık demektir: geçersiz boyutta parametre, bozulmuş şifreli metin veya uyuşmayan additionalData. MDN ayrıca GCM spesifikasyonunun tek şifrelemedeki açık metni 2^39 - 256 bit (64 GiB eksi 32 bayt) ile sınırladığını not eder.

Anahtarı sayfa yenilendiğinde kaybetmemek için nereye koymalısınız? MDN, CryptoKey nesnesinin serileştirilebilir olduğunu ve spesifikasyonun geliştiricilerin bunu IndexedDB içinde, uygulama için anlamlı bir anahtarla saklamasını beklediğini anlatır. Böylece çıkarılamaz bir anahtarın ham baytları JavaScript ortamına hiç açılmadan saklanıp geri okunabilir. IndexedDB kullanımı IndexedDB rehberi yazısında. Anahtarı başka bir sisteme taşımanız gerekiyorsa exportKey() şifresiz dışa aktarır; MDN hassas anahtarlar için anahtarı başka bir anahtarla şifreleyerek dışa aktaran wrapKey() metodunu önerir. Paroladan anahtar türetmek için ise her çağrıda yeni değer üreten generateKey() yerine, aynı girdiden aynı anahtarı üreten deriveKey() kullanılır.

Son bir hatırlatma: MDN, birçok kriptografi ihtiyacının zaten web platformu tarafından çözüldüğünü söyler. Ağ trafiğini dinleyen birine karşı korunmanın yolu kendi şifrelemeniz değil doğru yapılandırılmış HTTPS'tir. Parolasız giriş için kendi imza protokolünüzü yazmak yerine passkey ve WebAuthn altyapısını kullanmak daha güvenlidir.

Sık yapılan hatalar

  • Aynı iv değerini tekrar kullanmak. AES-GCM'nin en ciddi hatasıdır; her şifrelemede getRandomValues() ile yeni 12 bayt üretin ve şifreli metinle birlikte saklayın.
  • iv değerini saklamamak. Çözme için aynı değer gerekir; kaybederseniz veriyi geri alamazsınız.
  • Metni doğrudan vermek. subtle metotları dize değil bayt bekler; TextEncoder ile çevirin, sonucu TextDecoder ile geri okuyun.
  • Rastgelelik için Math.random() kullanmak. Kriptografik değildir; getRandomValues() veya randomUUID() kullanın.
  • Parola özeti için SHA kullanmak. MDN, SHA ailesinin hızlı olacak şekilde tasarlandığını ve bu yüzden parola özeti için uygun olmadığını açıkça yazar; bu iş için scrypt veya bcrypt gibi bilerek yavaş tutulan fonksiyonlar gerekir. Web Crypto API ile istemcide parola özeti almayın.
  • Anahtarı gereksiz yere çıkarılabilir üretmek. Dışa aktarmanız gerekmiyorsa extractable değerini false verin.
  • HTTP üzerinde crypto.subtle aramak. Güvensiz bağlamda yalnızca getRandomValues() kullanılabilir.

Sık Sorulan Sorular

crypto.subtle neden undefined?

En olası neden sayfanın güvenli bağlamda olmamasıdır. subtle özelliği ve randomUUID() güvenli bağlam ister; sayfayı HTTPS üzerinden ya da yerel geliştirme için güvenli sayılan localhost adresinden açın.

Web Crypto API Web Worker içinde kullanılabilir mi?

Evet. MDN Crypto nesnesinin worker kapsamında WorkerGlobalScope.crypto olarak bulunduğunu belirtir. Büyük verileri şifrelemek veya özetlemek için işi bir worker'a vermek ana iş parçacığını rahatlatır; ileti alışverişi Web Workers ve postMessage yazısında.

AES-GCM, AES-CBC ve AES-CTR arasında hangisini seçmeliyim?

MDN kimlik doğrulamalı şifrelemeyi önerir ve bu üçünden yalnızca GCM'nin yerleşik kimlik doğrulama sunduğunu belirtir. CTR ve CBC'ye kimlik doğrulamayı elle eklemek mümkündür ama küçük ve ciddi hatalar yapmak kolaydır.

Özet aldığım veriyi geri elde edebilir miyim?

Hayır. Özet tek yönlü bir işlemdir; aynı girdi her zaman aynı özeti verir ama özetten girdi bulunamaz. Veriyi geri almanız gerekiyorsa özet değil şifreleme kullanmalısınız.

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: Web API öğrenme rehberi · DOMException ve güvenli bağlam hataları · IndexedDB kullanımı · File API ve Blob · Passkey ve WebAuthn · HTTPS ve TLS 1.3