İçeriğe atla
HexaTransfer
Bloga dön
Teknik incelemeler

JavaScript Dosya Şifreleme: Adım Adım Eğitim

JavaScript kullanarak browser'da dosyaları şifreleyin. AES şifreleme, anahtar türetme ve güvenli dosya işlemeyi kapsayan pratik eğitim.

JavaScript, Web Crypto API sayesinde bir dosyayı tamamen tarayıcıda şifreleyebilir; sunucu hiçbir işleme dahil olmaz. Standart iş akışı şu şekilde çalışır: dosyayı ArrayBuffer olarak okuyun, PBKDF2-SHA256 (600.000 iterasyon) ile şifreden 256 bitlik bir AES-GCM anahtarı türetin, rastgele 12 baytlık bir IV ile şifreleyin ve tuz, IV ile şifreli metni indirilebilir bir Blob içinde paketleyin. window.crypto.subtle tüm modern tarayıcılarda yerel olarak desteklenir; 2-3 GB'a kadar dosyalar için herhangi bir üçüncü taraf kütüphanesi kullanmadan modern bir dizüstü bilgisayarda on saniyeden kısa sürede tamamlanır.

Önemli Algoritma Seçimleri

AES-CBC değil, AES-GCM tercih edin. GCM tek geçişte kimlik doğrulamalı şifreleme sağlar ve 128 bitlik etiketle kurcalamayı algılarken, CBC çoğu eğitimin hatalı uyguladığı ayrı bir HMAC adımı gerektirir. 256 bitlik anahtar kullanın — AES-NI desteği olan donanımlarda 128 bite kıyasla performans farkı ihmal edilebilir düzeydedir. Argon2id'i WebAssembly üzerinden çalıştıramıyorsanız parola tabanlı anahtar türetme için PBKDF2-SHA256 kullanın; Argon2id daha güçlüdür ancak 50 KB'lık ek indirme yükü getirir.

Bunlardan kaçının: ECB modu (temelden bozuk), elle yazılmış dolgu (on yıllık CBC dolgu oracle saldırıları), MD5 veya SHA-1 (çakışma güvenliği kırılmış) ve varsayılan olarak PKCS7 ile CBC kullanan crypto-js paketi.

Dosyayı Belleğe Okuma

File API bayt almak için üç yol sunar:

const buf = await file.arrayBuffer();          // tüm dosya
const stream = file.stream();                  // akış
const text = await file.text();                // UTF-8 çözümlenmiş

500 MB'ın üzerindeki dosyalar için arrayBuffer() genellikle mobil Safari'de başarısız olur. Bunun yerine akış kullanın:

async function* chunks(file, size = 4 * 1024 * 1024) {
  for (let off = 0; off < file.size; off += size) {
    yield new Uint8Array(await file.slice(off, off + size).arrayBuffer());
  }
}

Her dilim diskten gecikmeli okunur, bu nedenle bellek kullanımı sınırlı kalır.

Kullanıcı Parolasından Anahtar Türetme

Ham parolayı asla doğrudan encrypt'e geçirmeyin. Önce bir anahtar türetin:

async function deriveKey(password, salt) {
  const enc = new TextEncoder();
  const material = await crypto.subtle.importKey(
    'raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
  );
  return crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
    material,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
}

Her dosya için crypto.getRandomValues(new Uint8Array(16)) ile yeni 16 baytlık bir tuz oluşturun. Tuzu şifreli metnin yanında saklayın — aynı tuzu farklı dosyalarda yeniden kullanmak PBKDF2'nin amacını ortadan kaldırır. OWASP'ın Aralık 2026 parola depolama kılavuzu PBKDF2-SHA256 için 600.000 iterasyon önermektedir; bu orta düzey bir telefonda yaklaşık 500 ms'lik anahtar türetme süresine karşılık gelir.

Dosyayı Şifreleme

Anahtar hazır olduğunda, şifreleme her arabellek için tek bir subtle.encrypt çağrısıdır:

async function encryptBuffer(key, plaintext) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv, tagLength: 128 },
    key,
    plaintext
  );
  return { iv, ciphertext };
}

Aynı (iv, anahtar) çiftini yeniden kullanırsanız GCM felaket boyutunda başarısız olur — anahtar akışı çakışması her iki düz metni de açığa çıkarır. 96 bitlik rastgele IV, bir anahtarla yaklaşık 2^48 güvenli şifreleme sağlar; bu dosya şifreleme için yeterlidir. Aynı anahtarla birçok parçayı şifrelemek istiyorsanız IV'ü bir sayaç ve rastgele 32 bitlik ön ekten türetin.

Çıktıyı Paketleme

Şifre çözücü için tuz, IV ve şifreli metin gerekir. Bunları küçük bir başlıkla tek bir blob'a paketleyin:

function pack(salt, iv, ciphertext) {
  const magic = new TextEncoder().encode('ENC1');
  return new Blob([magic, salt, iv, new Uint8Array(ciphertext)]);
}

Başlığa sürüm ekleyin (ENC1, ENC2…) — böylece eski dosyaları bozmadan algoritmaları güncelleyebilirsiniz. İndirme şu şekilde sunulabilir:

const url = URL.createObjectURL(packed);
const a = document.createElement('a');
a.href = url; a.download = `${file.name}.enc`; a.click();
URL.revokeObjectURL(url);

Dosyaların Şifresini Çözme

Şifre çözme işlemi tersine döner ve parola yanlışsa ya da dosya değiştirilmişse OperationError fırlatır:

async function decryptFile(blob, password) {
  const buf = await blob.arrayBuffer();
  const view = new Uint8Array(buf);
  const magic = new TextDecoder().decode(view.slice(0, 4));
  if (magic !== 'ENC1') throw new Error('Unknown format');
  const salt = view.slice(4, 20);
  const iv = view.slice(20, 32);
  const ct = view.slice(32);
  const key = await deriveKey(password, salt);
  const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
  return new Blob([pt]);
}

Etiket hatası ile yapısal hata arasındaki farkı belirtmek yerine tek bir hata mesajı gösterin — "şifre çözülemedi, yanlış parola veya bozuk dosya". Bu, saldırganlara bilgi sızdıran dolgu oracle'a benzer bir güvenlik açığını ortadan kaldırır.

RAM Tükenmeden Büyük Dosyaları Şifreleme

500 MB'ın üzerindeki dosyalar için tüm dosya üzerinde arrayBuffer() çağırmayın. Parçaları bir sayaçtan türetilen benzersiz IV'lerle bağımsız olarak şifreleyin:

function ivForChunk(baseIV, index) {
  const iv = new Uint8Array(baseIV);
  const view = new DataView(iv.buffer);
  view.setUint32(8, index, false);
  return iv;
}

Dosyayı TransformStream üzerinden aktarın, her 4 MB'lık parçayı şifreleyin ve sonuçları File System Access API aracılığıyla diske işaret eden WritableStream'e yazın. 20 GB'lık bir dosya için bile bellek kullanımı 10 MB civarında kalır. Parçalı format, şifre çözücünün doğru şekilde yeniden birleştirebilmesi için başlığa parça boyutu ve sayısını kaydetmelidir.

Üretime Gönderilen Yaygın Hatalar

JavaScript kripto kod incelemelerinde üç hata tekrarlanmaktadır:

Birincisi, ham anahtarı kolaylık için localStorage'da saklamak. localStorage eşzamanlıdır, kökene bağlıdır ve herhangi bir XSS tarafından okunabilir. Bunun yerine IndexedDB'de çıkarılamaz bir CryptoKey kullanın.

İkincisi, IV veya tuzlar için Math.random() kullanmak. Math.random() tahmin edilebilirdir; her zaman crypto.getRandomValues kullanın.

Üçüncüsü, subtle.encrypt'in sabit zamanlı olduğunu varsaymak. Yerel tarayıcı uygulamalarında öyledir, ancak etrafındaki herhangi bir JavaScript sarmalayıcı büyük olasılıkla değildir. Kendi kodunuzu kritik yolun dışında tutun.

HexaTransfer tam olarak bu iş akışını uygular — 600.000 iterasyonda PBKDF2, AES-256-GCM, akışlı parçalar — bu nedenle sunucu yalnızca opak şifreli metin depolar. hexatransfer.com'da deneyin — ücretsiz, hesap gerekmez, maksimum 10 GB.

Uygulamayı Test Etme

Rastgele 10 MB'lık bir blob'u gidiş-dönüş yapan, bir baytı değiştiren ve şifre çözmenin hata fırlattığını doğrulayan bir test donanımı yazın. Hatalı biçimlendirilmiş başlıklara ve kesilmiş şifreli metinlere karşı bulanıklaştırma ekleyin — yaygın hata, çökmek yerine reddeden slice() üzerindeki sınır kontrollerinin eksikliğidir. Bir iPhone SE, orta düzey bir Android ve Chromebook'ta kıyaslama yapın; 2 saniyeden fazla süren anahtar türetme mobil kullanıcılar için çok yavaştır. Üretime geçmeden önce ikinci bir göz çiftiyle denetleyin — kripto kodu basit görünür ve testlerin kaçırdığı ince yollarla bozulur.

Uçtan uca şifreleme ile büyük dosyaları güvenle gönderin

Uçtan uca şifreleme ile 10 GB'a kadar dosya ücretsiz aktarın. Hesap gerekmez. Dosyalarınız yüklenmeden önce tarayıcınızda şifrelenir — başka kimse okuyamaz.

Dosya gönder