बड़ी फ़ाइलों का प्रोग्रेसिव एन्क्रिप्शन: स्ट्रीम और एन्क्रिप्ट
स्ट्रीमिंग API से बड़ी फ़ाइलें प्रोग्रेसिवली एन्क्रिप्ट करें। मेमोरी खत्म हुए बिना मल्टी-GB फ़ाइलें चंक्स में एन्क्रिप्ट करें।
Progressive (streaming) एन्क्रिप्शन एक फाइल को chunk by chunk process करता है, पूरा payload कभी memory में लोड किए बिना। ब्राउज़र में 10 GB upload के लिए यही वह technique है जो app को काम करने और crash होने के बीच का फ़र्क बनाती है। Pattern है: File.stream() से chunk read करें, unique nonce के साथ AES-256-GCM से encrypt करें, ciphertext को fetch के ReadableStream body के ज़रिए सीधे upload stream में pipe करें, buffer release करें, और आगे बढ़ें। File size चाहे जो हो, memory 4-16 MB bounded रहती है। libsodium का crypto_secretstream_xchacha20poly1305 truncation detection सहित proper streaming AEAD semantics add करता है।
Buffered Encryption क्यों Fail होती है
10 GB file पर FileReader.readAsArrayBuffer(file) ब्राउज़र memory में 10 GB allocate करता है। 32 GB RAM वाले desktop Chrome पर यह काम कर सकता है। 400 MB per-tab memory limit वाले mobile Safari पर, यह complete होने से पहले crash करता है। Firefox पर 2 GB से बड़ा ArrayBuffer internal V8 limits से टकराता है और RangeError throw करता है।
उस hardware पर भी जो allocation handle कर सके, 10 GB garbage collection block करता है और pathological paging trigger करता है। सही जवाब है: full buffer कभी allocate ही न करें।
Streaming Pattern
async function streamEncrypt(file, key, uploadURL) {
const CHUNK_SIZE = 4 * 1024 * 1024; // 4 MB
const reader = file.stream().getReader();
let chunkIndex = 0;
let buffer = new Uint8Array(0);
const uploadStream = new ReadableStream({
async pull(controller) {
while (buffer.length < CHUNK_SIZE) {
const { done, value } = await reader.read();
if (done) {
if (buffer.length > 0) {
await enqueueEncrypted(controller, buffer, chunkIndex++, key);
}
controller.close();
return;
}
const newBuf = new Uint8Array(buffer.length + value.length);
newBuf.set(buffer, 0);
newBuf.set(value, buffer.length);
buffer = newBuf;
}
const chunk = buffer.subarray(0, CHUNK_SIZE);
buffer = buffer.subarray(CHUNK_SIZE);
await enqueueEncrypted(controller, chunk, chunkIndex++, key);
}
});
await fetch(uploadURL, {
method: "POST",
body: uploadStream,
duplex: "half",
headers: { "Content-Type": "application/octet-stream" },
});
}
async function enqueueEncrypted(controller, chunk, index, key) {
const iv = new Uint8Array(12);
new DataView(iv.buffer).setBigUint64(4, BigInt(index));
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, chunk);
controller.enqueue(new Uint8Array(ct));
}
दो key APIs: File.stream() file contents का ReadableStream देता है; ReadableStream body के साथ fetch पूरा body buffer किए बिना upload stream करता है। Chrome 105+ में streaming request bodies के लिए duplex: "half" required है।
Memory use: किसी भी समय एक source chunk, एक buffered remainder, एक encrypted chunk। 4 MB chunk size के लिए peak ~12-16 MB।
Streams में Nonce Management
हर chunk को unique nonce चाहिए। तीन approaches:
Counter-based: 96-bit nonce में chunk index embed करें। Upper 32 bits को random prefix पर set करें (same key use करने वाली files में collisions से बचाने के लिए), lower 64 bits को chunk index पर।
const noncePrefix = crypto.getRandomValues(new Uint32Array(1));
function makeNonce(chunkIndex) {
const iv = new Uint8Array(12);
new DataView(iv.buffer).setUint32(0, noncePrefix[0]);
new DataView(iv.buffer).setBigUint64(4, BigInt(chunkIndex));
return iv;
}
Random per chunk: crypto.getRandomValues(new Uint8Array(12))। Per-file keys के लिए safe; chunks में birthday collisions ~2^48 पर होती हैं। हर chunk के ciphertext के साथ nonce store करें।
HKDF के ज़रिए derived: per-chunk keys derive करने के लिए HKDF उपयोग करें, फिर fixed nonce। ज़्यादातर cases के लिए overkill।
Fresh per-file key के लिए, counter-based simplest है और प्रति chunk separate nonce store करने की ज़रूरत नहीं।
Truncation Attacks और उनसे बचाव
Naive chunked AES-GCM में एक critical gap: attacker trailing chunks drop कर सकता है और surviving chunks ठीक से decrypt होते हैं। Detection के लिए chunks को bind करना ज़रूरी है।
Option 1: हर chunk के AAD में total chunk count include करें। Recipient verify करे कि count received से match करता है।
const aad = new TextEncoder().encode(JSON.stringify({
totalChunks,
fileSize: file.size,
}));
const ct = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv, additionalData: aad },
key,
chunk
);
Option 2: libsodium का crypto_secretstream_xchacha20poly1305 उपयोग करें। यह chunks को cryptographically chain करता है और TAG_FINAL marker emit करता है जिसे recipient verify करता है:
const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
// हर chunk के लिए TAG_MESSAGE से push करें
// Last chunk के लिए TAG_FINAL से push करें
const lastCt = sodium.crypto_secretstream_xchacha20poly1305_push(
state, lastChunk, null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);
Decrypt पर, recipient के pull calls chain verify करती हैं और missing tail chunks detect करती हैं। अगर आप libsodium.js ship करने के लिए तैयार हैं तो यह cleanest option है।
Recipient-Side Streaming Decrypt
Recipient side पर symmetric pattern:
async function streamDecrypt(downloadURL, key, onChunk) {
const response = await fetch(downloadURL);
const reader = response.body.getReader();
let buffer = new Uint8Array(0);
let chunkIndex = 0;
const ENCRYPTED_CHUNK_SIZE = 4 * 1024 * 1024 + 16; // plus GCM tag
while (true) {
const { done, value } = await reader.read();
if (done) break;
const newBuf = new Uint8Array(buffer.length + value.length);
newBuf.set(buffer);
newBuf.set(value, buffer.length);
buffer = newBuf;
while (buffer.length >= ENCRYPTED_CHUNK_SIZE) {
const ct = buffer.subarray(0, ENCRYPTED_CHUNK_SIZE);
buffer = buffer.subarray(ENCRYPTED_CHUNK_SIZE);
const iv = makeNonce(chunkIndex++);
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
onChunk(new Uint8Array(pt));
}
}
// Final partial chunk handle करें
if (buffer.length > 0) {
const iv = makeNonce(chunkIndex);
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, buffer);
onChunk(new Uint8Array(pt));
}
}
Recipient पर onChunk callbacks decrypted bytes को direct disk writes के लिए File System Access API में pipe कर सकते हैं, या browser-native download के लिए Blob में concatenate कर सकते हैं।
File System Access API से Disk पर लिखना
Very large downloads के लिए, full decrypted result Blob में लोड करना streaming का purpose defeat करता है। File System Access API (Chrome 86+, OPFS के ज़रिए partial Safari) recipient को local file pick करके chunks directly write करने देता है:
const handle = await window.showSaveFilePicker({
suggestedName: "decrypted-file",
});
const writable = await handle.createWritable();
await streamDecrypt(url, key, async (chunk) => {
await writable.write(chunk);
});
await writable.close();
Chunks disk पर immediately जाते हैं, memory bounded रहती है। UI realistic progress दिखाता है। Users mid-download cancel कर सकते हैं।
Firefox अभी desktop पर showSaveFilePicker support नहीं करता। Files under a few hundred MB के लिए in-memory Blob पर fall back करें, या multi-GB Firefox workflows के लिए Origin Private File System।
fetch से Upload Streaming
Chrome 105+ और Firefox 127+ duplex: "half" के साथ streaming request bodies support करते हैं। S3-compatible multipart uploads के लिए, encrypted stream को 5-25 MB के parts में split करें (S3 minimum part size 5 MB है) और final CompleteMultipartUpload call से complete करें। यह सभी browsers पर काम करता है और free में resumability देता है।
Streams पर Progress Reporting
Bytes processed track करें:
let processed = 0;
const onChunk = (chunkSize) => {
processed += chunkSize;
updateProgressBar(processed / file.size);
};
Progress updates को 10-20 Hz पर requestAnimationFrame से throttle करें। 100 MB/s processing speed पर 10 GB files पर raw events की 100 updates per second होती हैं — UI को उतनी ज़रूरत नहीं।
10 GB File पर Benchmarks
2024 MacBook Pro (M3 Max) पर fast SSD के साथ: File.stream() से raw disk read 2.5 GB/s, Web Crypto पर AES-256-GCM 1.7 GB/s, combined pipeline 1.1 GB/s (serial chain से bounded), Gigabit Ethernet पर upload 115 MB/s (network-bound), file size चाहे जो हो peak memory 14 MB। Mobile numbers desktop के roughly 30-50%। Gigabit पर 10 GB file ~90 seconds, typical home connection पर ~15 minutes। एन्क्रिप्शन bottleneck नहीं — नेटवर्क है।
Error Recovery
10 GB upload के दौरान network interruptions सामान्य हैं। Strategies:
- Multipart के ज़रिए resumable uploads: हर part independent है; केवल failed part re-upload करें।
- tus protocol: open resumable upload standard; stream-native।
- Source file handle open रखें: अगर
File.slicerepeatable है, last successful chunk से restart करें।
संक्षेप में
पूरी file allocate न करें। Chunks में read करें, chunks में encrypt करें, chunks में upload करें, जाते-जाते हर chunk free करें। Truncation defeat करने के लिए AAD या streaming AEAD से chunks cryptographically bind करें। हर चीज़ में progress bar लगाएं। Mobile पर test करें, न कि केवल desktop पर। DPDP Act 2023 के अंतर्गत sensitive data के लिए streaming approach ensure करती है कि server पर plaintext कभी land न करे।
hexatransfer.com पर आज़माएं — मुफ्त, बिना अकाउंट, 10 GB तक।
एंड-टू-एंड एन्क्रिप्शन के साथ बड़ी फ़ाइलें सुरक्षित रूप से भेजें
एंड-टू-एंड एन्क्रिप्शन के साथ 10 GB तक की फ़ाइलें मुफ़्त में ट्रांसफ़र करें। अकाउंट की आवश्यकता नहीं। अपलोड से पहले आपकी फ़ाइलें ब्राउज़र में एन्क्रिप्ट की जाती हैं — कोई और उन्हें पढ़ नहीं सकता।
फ़ाइल भेजें