Şifreleme performans optimizasyonu: tarayıcıda hızlı kripto
Büyük aktarımlar için şifreleme performansını optimize edin. Akış şifreleme, Web Workers ve parça işleme teknikleri.
5 GB'lık bir dosyayı tarayıcıda arayüzü dondurmadan şifrelemek belirli bir teknik setini gerektirir: donanım hızlandırmalı AES-256-GCM için Web Crypto API (AES-NI ile 1-2 GB/s), belleği sınırlamak için 1-4 MB bloklarda parçalı işleme, ana iş parçacığını duyarlı tutmak için Web Workers, FileReader.readAsArrayBuffer() yerine File.stream() aracılığıyla akışlı okuma ve parçaların paralel şifrelenebilmesi için dikkatli nonce yönetimi. Saf JavaScript kripto kütüphaneleri Web Crypto'dan 10-20 kat daha yavaş çalışır ve yalnızca tarayıcının yerel olarak sunmadığı primitifler için ayrılmalıdır. Gerçek kullanıcı donanımında yüzlerce megabayt/saniye verime nasıl ulaşılır, işte bu rehber.
Önce Temel Ölçümler
Optimize etmeden önce ölçün. Chrome 120'deki 2024 MacBook Air M2'de sıkı bir döngüde Web Crypto aracılığıyla AES-256-GCM ile 1 GB tamponu şifrelemek yaklaşık 1,7 GB/s hızında çalışır. Orta düzey bir Android telefonda (Pixel 7) aynı işlem yaklaşık 600 MB/s hızında çalışır. Yazılım tabanlı AES kullanan 2015 Intel dizüstü bilgisayarda yaklaşık 250 MB/s.
Bunun gösterdiği: Web Crypto AES-GCM, çoğu şifreli dosya aktarımı akışında darboğaz değildir. Darboğaz genellikle dosya okuma, ArrayBuffer'lar arasındaki JavaScript derleme veya ağ yüklemesidir. Önce bunları optimize edin.
Uygulama içinde çalıştırılacak karşılaştırmalar:
const blob = new Uint8Array(1024 * 1024 * 100); // 100 MB
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 }, true, ["encrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const start = performance.now();
await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, blob);
console.log(`${(100 / (performance.now() - start) * 1000).toFixed(0)} MB/s`);
Chrome, Firefox, Safari'de çalıştırın. Apple Silicon üzerindeki Chrome en hızlısı olacak; Firefox biraz daha yavaş; Intel'deki Safari biraz geride. Mobil, masaüstü hızının yaklaşık yüzde 30-50'sinde çalışır.
JavaScript Kütüphaneleri Değil, Web Crypto Kullanın
AES-GCM, AES-CBC, PBKDF2, HMAC, RSA, ECDH, ECDSA ve SHA-256/384/512 için tarayıcının yerel Web Crypto API'si, mevcut olduğunda donanım hızlandırması kullanır (x86'da AES-NI, mobilde ARMv8 kripto uzantıları). @noble/ciphers veya saf JS crypto-js gibi JavaScript kütüphaneleri bu talimatlara erişemez ve tamamen yorumlayıcıda çalışır.
1 MB girişlerde AES-256-GCM için tipik hız oranı:
- Web Crypto (donanım hızlandırmalı): 1-2 GB/s
- libsodium.js WASM: 400-800 MB/s
- @noble/ciphers saf JS: 100-200 MB/s
- crypto-js saf JS: 30-80 MB/s
5 GB'lık bir dosya için Web Crypto ile saf JS arasındaki fark yaklaşık 3 saniye ile yaklaşık 50 saniyedir. Kullanıcı tarafından fark edilir. Web Crypto'nun desteklediği her şey için her zaman tercih edin. WASM kütüphanelerini (libsodium.js, argon2-browser) yalnızca Web Crypto'nun sahip olmadığı algoritmalar için (ChaCha20-Poly1305, Argon2id, eski tarayıcılarda X25519) kullanın.
Büyük Dosyalar için Parçalı İşleme
500 MB üzerindeki dosyalar, çoğu cihazda tek bir ArrayBuffer'a rahat sığmaz. Bunları 1-4 MB parçalara bölün ve her birini şifreleyin.
const CHUNK_SIZE = 4 * 1024 * 1024; // 4 MB
async function encryptLargeFile(file, key) {
const chunks = [];
let chunkIndex = 0;
for (let offset = 0; offset < file.size; offset += CHUNK_SIZE) {
const chunk = await file.slice(offset, offset + CHUNK_SIZE).arrayBuffer();
const iv = new Uint8Array(12);
new DataView(iv.buffer).setBigUint64(4, BigInt(chunkIndex++));
const ct = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, key, chunk
);
chunks.push({ iv, ct: new Uint8Array(ct) });
}
return chunks;
}
Neden 4 MB parça? Daha küçük parçalar (örn. 64 KB) küçük girişlerde Web Crypto API'den gelen çağrı başına genel giderin baskın olmasına neden olur. Daha büyük parçalar (örn. 64 MB) L2/L3 önbelleğe iyi sığmaz ve daha kötü bellek baskısına yol açar. 1-4 MB hem masaüstü hem mobilde tatlı noktayı yakalar.
UI'yi Duyarlı Tutmak için Web Workers
Ana iş parçacığında şifreleme, render'ı ve girdi yanıtını engeller. 500 ms'yi aşan her şey için Web Worker'a aktarın.
// worker.js
self.onmessage = async (e) => {
const { chunk, key, iv } = e.data;
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, key, chunk
);
self.postMessage(ciphertext, [ciphertext]);
};
// Ana iş parçacığı
const worker = new Worker('/worker.js');
worker.postMessage({ chunk, key, iv }, [chunk]);
worker.onmessage = (e) => { /* ct'yi işle */ };
ArrayBuffer'ları postMessage'a ikinci argümanla aktarın — bu, kopyalama yerine sahipliği taşır (sıfır kopya). Aktarma olmadan 4 MB'lık bir parçanın kopyalanması parça başına yaklaşık 20 ms ek yük getirir.
Web Crypto API, Worker'larda kullanılabilir; dolayısıyla gerçek şifreleme orada ana iş parçacığıyla özdeş performansla çalışır.
Paralel Parça Şifrelemesi
Nonce'lar çakışmadığı sürece parça bazlı AES-GCM şifreleme bağımsızdır. Nonce'ları parça indeksinden deterministik biçimde türetin; böylece birden fazla Worker genelinde parçaları paralel şifreleyebilirsiniz. Web Crypto o kadar hızlıdır ki darboğaz dosya okuma ve iş parçacıkları arası ileti geçişine kayacağından, tipik donanımda yaklaşık 4 Worker'dan sonra azalan getiriler ortaya çıkar. Karmaşıklığa bağlanmadan önce karşılaştırın; bazen ek yük nedeniyle tek Worker'lı sıralı şifreleme çok Worker'lı paralellikle aynı hızda olabilir.
Readable Streams ile Akış İşleme
Gerçekten büyük dosyalar (20 GB ve üzeri) için parçaları bile aynı anda belleğe yüklemekten kaçının. File.stream() kullanın:
const reader = file.stream().getReader();
const writer = uploadStream.getWriter();
let chunkIndex = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const iv = new Uint8Array(12);
new DataView(iv.buffer).setBigUint64(4, BigInt(chunkIndex++));
const ct = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, key, value
);
await writer.write(new Uint8Array(ct));
}
await writer.close();
Bu, bellek kullanımını bir parçayla sınırlar. Tarayıcı diskten okur, şifreler, yükleme akışına yazar, sonraki parçayı okur. Bellek kullanımı dosya boyutundan bağımsız olarak 10 MB'ın altında kalır.
Yükleme Eşzamanlılığı
Şifreleme, seri değil paralel olarak yüklemeyle çalışır. N. parça yüklenirken N+1. parça şifreleniyor. Boru hattı kullanın:
const encryptQueue = [];
const uploadQueue = [];
const MAX_IN_FLIGHT = 4;
// Şifreleyici üretir, yükleyici tüketir
async function pipeline() {
// İlk parçaların şifrelenmesini başlat
for (let i = 0; i < MAX_IN_FLIGHT; i++) startEncrypt(i);
// Her tamamlandığında yüklemeyi başlat ve sonraki şifrelemeyi kuyruğa al
// (sınırlı kuyruk bellek kullanımını öngörülebilir tutar)
}
TCP yavaş başlatma ve TLS el sıkışma genel gideri başlangıç yüklemelerini yavaşlatır. Tek bir kaynağa 4-8 eşzamanlı yükleme sürdürmek, tarayıcı sınırlarını (Chrome/Firefox'ta kaynak başına 6 bağlantı) tetiklemeksizin boruyu dolu tutar.
Eksik Primitifler için WebAssembly
Argon2id anahtar türetme veya ChaCha20-Poly1305 için Web Crypto'nun yerel desteği yoktur. WASM kütüphaneleri boşluğu doldurur:
- libsodium.js, Argon2id, XChaCha20-Poly1305,
crypto_secretstreamsağlar - argon2-browser yalnızca Argon2 sağlar ama daha küçük paket boyutu
- @noble/hashes, küçük paket boyutuyla saf JS Argon2 sağlar (daha yavaş)
WASM sürümleri, çoğu kripto iş yükünde yerel hızın yüzde 60-80'ine ulaşır. Dosya aktarımı için yükleme ve indirmede 1-2 saniyelik Argon2id türetme kabul edilebilir; 5 saniyelik saf JS türetme değildir.
WASM'ı dinamik olarak yükleyin; böylece başlangıç sayfa oluşturmasını engellemez:
const sodium = await import('libsodium-wrappers');
await sodium.ready;
İlerleme Bildirimi
Büyük şifrelemeler geri bildirim gerektirir; aksi hâlde kullanıcılar uygulamanın donduğunu düşünür. Şifrelenmiş baytları sayın ve ilerleme olayları gönderin:
let processed = 0;
for await (const chunk of chunks) {
await encryptChunk(chunk);
processed += chunk.size;
onProgress({ done: processed, total: file.size, pct: processed / file.size });
}
İnsanların göremediği boyama işlemlerinde döngü israfını önlemek için UI güncellemelerini requestAnimationFrame veya basit bir zaman damgası kontrolüyle yaklaşık 10 Hz'e kısıtlayın.
Bellek Tavanı ve GC Baskısı
Her ArrayBuffer artık referans edilmediğinde serbest kalır. 4 MB'lık 20 şifreli parça tutmak yaklaşık 80 MB sabitlenmiş bellek anlamına gelir. Sıkı bellek sınırlarına sahip mobil tarayıcılarda (iOS Safari, sekme başına yaklaşık 200-400 MB'la sınırlar) bu önemlidir.
Referansları derhal serbest bırakın:
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
chunks[i] = null; // GC'nin geri kazanmasına izin ver
const ct = await encrypt(chunk);
await upload(ct);
}
Bellekte tam şifreli parça dizisi tutmaktan kaçının; bunları yükleyiciye aktarırken referansları bırakın.
Gerçek Dünya Hedefleri
Bir tarayıcı sekmesinde 1 GB şifreli dosya aktarımı için: şifreleme süresi 1-3 saniye (donanım hızlandırmalı), 300 Mbps bağlantıda yükleme süresi 30 saniye, uygun akışla 50 MB altında tepe bellek, ana iş parçacığı hiçbir zaman 50 ms'den fazla engellenmez ve toplam yaklaşık 35 saniye duvar saati süresi (çoğunlukla ağ bağlantısıyla sınırlı). Bu hedefleri kaçırırsanız kullanıcılar fark eder. HexaTransfer'ın 10 GB kapasitesi tarayıcıda erişilebilirdir; çünkü Web Crypto artı parçalı akış tüm yolu verimli kılar.
hexatransfer.com'da deneyin — ücretsiz, hesap gerekmez, 10 GB'a kadar.
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