İstemci tarafı şifreleme öğreticisi: sıfırdan oluşturun
Web uygulamasında istemci tarafı şifreleme uygulamak için adım adım öğretici. Dosyaları cihazdan ayrılmadan önce tarayıcıda şifreleyin.
Tarayıcıda istemci taraflı dosya şifrelemesi, Web Crypto API kullanan yaklaşık 80 satır JavaScript alır. Örüntü şudur: tarayıcıda AES-256-GCM anahtarı üretin, dosyayı rastgele 96 bit nonce ile şifreleyin, şifreli metni HTTPS/TLS 1.3 üzerinden yükleyin ve elde edilen URL'yi sunuculara hiçbir zaman iletilmeyen parça tanımlayıcısına (#key=...) gömülü anahtarla paylaşın. Alıcı aynı parçayı kullanarak tarayıcıda şifresini çözer. Bu öğretici; büyük dosyalar için parçalama, 600.000 iterasyonla PBKDF2 aracılığıyla parola türetilen anahtarlar ve ilk denemeleri bozan tuzaklar dahil olmak üzere çalışan bir implementasyonu adım adım ele alır.
Tek Şemada Mimari
[Gönderici Tarayıcı] [Sunucu] [Alıcı Tarayıcı]
Dosyayı Oku → AES anahtarı POST kabul eder Şifreli metin al
AES-256-GCM ile şifrele Şifreli metin depolar Anahtarı URL #parçasından çıkar
Şifreli metni POST et Anahtar yok, düz metin Tarayıcıda şifresini çöz
#key=... ile URL oluştur yok Dosyayı diske kaydet
İndirme URL'si döner
Sunucu, yalnızca şifreli metni depolayan bir nesne deposudur. Yalnızca şifreli metni görür; şifreyi çözemez. Şifre çözme anahtarı URL parçasında yaşar; tarayıcılar bunu özel biçimde işler: HTTP istek satırında hiçbir zaman gönderilmez. Bu, HexaTransfer dahil her sıfır bilgi dosya aktarım hizmetinin temelidir.
Adım 1: Simetrik Anahtar Üretme
async function generateKey() {
return await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // URL'ye dışa aktarmamız gerektiğinden dışa aktarılabilir
["encrypt", "decrypt"]
);
}
extractable: true bayrağı zorunludur; çünkü anahtarı bir URL parçasına serileştirmemiz gerekir. Anahtarın yalnızca bellekte yaşadığı bir akış oluşturuyorsanız (örneğin yapıştır-ve-gönder aracı), false olarak ayarlayın.
Adım 2: Dosyayı ArrayBuffer Olarak Okuma
async function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(file);
});
}
Bu, dosyanın tamamını belleğe yükler. 500 MB altındaki dosyalar için uygundur. Daha büyük dosyalar için doğrudan akış bölümüne atlayın.
Adım 3: Tamponu Şifreleme
async function encryptFile(key, plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
plaintext
);
// Alıcının çıkarabilmesi için IV'yi şifreli metnin önüne ekle
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(ciphertext), iv.length);
return combined.buffer;
}
Nonce (IV) NIST SP 800-38D uyarınca 96 bit (12 bayt) uzunluğundadır. Gizli değildir; ancak anahtar başına benzersiz olmalıdır. Burada rastgele nonce'lar güvenlidir; çünkü dosya başına taze bir anahtar üretiyoruz. IV'yi şifreli metnin önüne eklemek yaygın bir kuraldır; alıcı şifre çözmeden önce bunu geri ayırır.
Adım 4: Şifreli Metni Yükleme
async function uploadCiphertext(ciphertext) {
const response = await fetch("/api/upload", {
method: "POST",
body: ciphertext,
headers: { "Content-Type": "application/octet-stream" },
});
const { fileId } = await response.json();
return fileId;
}
Sunucu bir ikili blob alır, bir ID atar, saklar ve bu ID'yi döndürür. Hiçbir başlık dosya adını açığa çıkarmaz; hiçbir sorgu parametresi anahtarı taşımaz. Sunucunun hard diski yarın çalınsa, bir saldırgan anlamsız verilerle karşılaşır.
Adım 5: Parçada Anahtarla Paylaşım URL'si Oluşturma
async function buildShareURL(fileId, key) {
const rawKey = await crypto.subtle.exportKey("raw", key);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
return `${location.origin}/f/${fileId}#${keyBase64}`;
}
Base64url kodlaması (+ ve / yerine - ve _) URL kaçış sorunlarını önler. = dolgusu estetik amaçlı kaldırılır.
Parça (#...) burada sihirlidir. Alıcı URL'yi yüklediğinde, tarayıcı parçayı istemci tarafında tutar. /f/{fileId} için HTTP GET, istek satırına #keyBase64 dahil etmez; dolayısıyla sunucu anahtarı hiçbir zaman öğrenmez. Bunu kendiniz doğrulayın: herhangi bir parça içeren URL'de tarayıcı geliştirici araçlarını açın ve Ağ sekmesini izleyin.
Adım 6: Alıcı Taraflı Şifre Çözme
async function downloadAndDecrypt() {
const fileId = location.pathname.split("/").pop();
const keyBase64 = location.hash.slice(1);
const rawKey = Uint8Array.from(
atob(keyBase64.replace(/-/g, "+").replace(/_/g, "/")),
c => c.charCodeAt(0)
);
const key = await crypto.subtle.importKey(
"raw", rawKey, "AES-GCM", false, ["decrypt"]
);
const response = await fetch(`/api/download/${fileId}`);
const combined = new Uint8Array(await response.arrayBuffer());
const iv = combined.slice(0, 12);
const ciphertext = combined.slice(12);
const plaintext = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv }, key, ciphertext
);
const blob = new Blob([plaintext]);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "downloaded-file";
a.click();
}
GCM'nin kimlik doğrulama etiketi decrypt() sırasında kontrol edilir. Şifreli metin kurcalanmışsa, çağrı OperationError fırlatır; temiz bir başarısızlık modudur.
PBKDF2 Aracılığıyla Parola Türetilen Anahtarlar
Kullanıcılar rastgele anahtar yerine parola giriyorsa, AES anahtarını PBKDF2 üzerinden türetin:
async function deriveKey(password, salt) {
const passwordKey = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(password),
"PBKDF2", false, ["deriveKey"]
);
return await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 600000,
hash: "SHA-256",
},
passwordKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}
600.000 iterasyonlu PBKDF2-SHA-256, OWASP'ın 2023 taban çizgisidir. Salt 16 rastgele bayt olmalı ve şifreli metinle birlikte depolanmalıdır (gizli değil, yalnızca benzersiz olması gerekir). Yeni kodlar için argon2-browser gibi bir kütüphane üzerinden Argon2id'yi göz önünde bulundurun; GPU saldırılarına çok daha iyi direnir.
Büyük Dosyaları Akışla İşleme
500 MB üzerindeki dosyalar parçalanmalıdır. File.stream() ile okuyun, her parçayı şifreleyin, sırayla yükleyin:
async function encryptStream(file, key) {
const reader = file.stream().getReader();
const chunks = [];
let chunkIndex = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const iv = new Uint8Array(12);
// Benzersizliği garantilemek için nonce'a parça indeksini kodla
new DataView(iv.buffer).setBigUint64(4, BigInt(chunkIndex++));
const ct = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, key, value
);
chunks.push({ iv, ct });
}
return chunks;
}
Nonce'u parça indeksinden türetmek, durum takibi yapmaksızın benzersizliği garanti eder. Alıcı tarafında sıralı şifre çözme ve birleştirme yapılır.
Gerçek akış AEAD için libsodium'un crypto_secretstream_xchacha20poly1305'i libsodium.js aracılığıyla daha temizdir ve kesme saldırılarını algılar. Web Crypto'nun 2026'da eşdeğer bir primitifi bulunmuyor.
Test Etme ve Tuzaklar
Kaçınılması gereken yaygın hatalar:
- Anahtar veya nonce için
Math.random()kullanmak: her zamancrypto.getRandomValues()kullanın. - Aynı anahtarla nonce yeniden kullanımı: GCM güvenliğini bozar. Dosya başına rastgele anahtarlar bunu güvenli kılar; parça bazlı akışlar parça başına benzersiz nonce gerektirir.
- HTTPS kontrolü yapmamak:
crypto.subtlegüvensiz kaynaklarda tanımsızdır. Geliştirme sırasında localhost veya öz imzalı sertifika ile test edin. - Anahtarları
localStorage'da depolamak: kaynak üzerindeki herhangi bir XSS okuyabilir. Bunun yerine URL parçası örüntüsünü veya dışa aktarılamaz anahtarları kullanın. - IV'yi şifreli metinle birlikte eklemeyi unutmak: faydalı bir hata mesajı olmaksızın şifre çözme başarısız olur. Her zaman önüne ekleyin veya birlikte serileştirin.
- Parçayı yanlış işlemek: URL'yi (parçayla birlikte) yanlışlıkla üçüncü taraf bir hizmete göndermeyin. Parça hassassa yalnızca uçtan uca kanallar üzerinden paylaşın.
Sunucu Taraflı Sorumluluklar
İstemci taraflı şifreleme mimarisinde sunucunun görevi küçüktür: POST kabul et, blob depola, ID döndür, blob için GET sun, süre sonunda sil. Kripto yok. Sunucunun depolama ötesinde yapması gerekenler:
- Dosya boyutu sınırlarını zorla (kötüye kullanımı önle)
- Yükleme ve indirme hız sınırı uygula
- Kısa saklama ayarla (HexaTransfer gibi 7 gün makul bir varsayılandır)
- Yalnızca gerekli olanı günlüğe kaydet (yükleme zaman damgası; gizlilik önceliğiyse IP adresi kaydetme)
- HSTS ile TLS 1.3 üzerinden sun
- API yalnızca kendi etki alanlarınızdan çağrılıyorsa kaynakları kısıtlayan CORS başlıkları ekle
Hepsini Bir Araya Getirme
Minimal çalışan uygulama, bir HTML dosyası ve 50 satırlık bir Express arka ucuna sığar. Toplam bağımlılık: istemcide hiçbiri yok (Web Crypto yerel), sunucuda Express ve multer. Şifreleme, AES-256-GCM primitiviyle aynı güçtedir; çünkü tam olarak onu kullanıyorsunuz. Yanlış gidecek gizli bir algoritma yok; yalnızca doğru kullanılacak primitifler var.
En zor kısımlar kenar durumlardır: büyük dosyalar, parola-to-anahtar akışları, şifre çözme başarısız olduğunda alıcı UX, süresi dolmuş bağlantıları zarif biçimde işleme. Temel kriptografi açıktır.
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