क्लाइंट-साइड एन्क्रिप्शन ट्यूटोरियल: स्क्रैच से बनाएं
वेब ऐप में क्लाइंट-साइड एन्क्रिप्शन लागू करने का स्टेप-बाई-स्टेप ट्यूटोरियल। यूज़र डिवाइस छोड़ने से पहले ब्राउज़र में फ़ाइलें एन्क्रिप्ट करें।
Browser में client-side फ़ाइल encryption Web Crypto API का उपयोग करके लगभग 80 lines के JavaScript में होती है। Pattern यह है: browser में AES-256-GCM key generate करें, random 96-bit nonce से फ़ाइल encrypt करें, HTTPS/TLS 1.3 पर ciphertext upload करें, और resulting URL share करें जिसमें key fragment identifier में embedded हो (#key=...) — browsers इसे servers को कभी transmit नहीं करते। Recipient उसी fragment से browser में decrypt करता है। यह tutorial एक working implementation walk-through करता है, जिसमें बड़ी फ़ाइलों के लिए chunking, 600,000 iterations पर PBKDF2 के ज़रिए password-derived keys, और पहली कोशिश में आने वाली वे gotchas शामिल हैं।
Architecture एक Diagram में
[Sender Browser] [Server] [Recipient Browser]
Read File → AES key (random) Accepts POST GET ciphertext
Encrypt with AES-256-GCM Stores ciphertext blob Parse key from URL #fragment
POST ciphertext No key, no plaintext Decrypt in-browser
Build URL with #key=... Returns download URL Save File to disk
Server एक dumb blob store है। यह केवल ciphertext देखता है और decrypt नहीं कर सकता। Decryption key URL fragment में रहती है — browsers इसे HTTP request line में कभी नहीं भेजते। यह HexaTransfer सहित हर zero-knowledge फ़ाइल ट्रांसफर सर्विस का आधार है।
Step 1: Symmetric Key Generate करें
async function generateKey() {
return await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // extractable so we can export it into the URL
["encrypt", "decrypt"]
);
}
extractable: true flag ज़रूरी है क्योंकि हमें key को URL fragment में serialize करना है। अगर ऐसा flow बना रहे हैं जहाँ key केवल memory में रहे, तो false set करें।
Step 2: फ़ाइल को ArrayBuffer के रूप में पढ़ें
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);
});
}
यह पूरी फ़ाइल memory में load करता है। 500 MB से छोटी फ़ाइलों के लिए ठीक है। बड़ी फ़ाइलों के लिए streaming section पर जाएँ।
Step 3: Buffer Encrypt करें
async function encryptFile(key, plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
plaintext
);
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(ciphertext), iv.length);
return combined.buffer;
}
Nonce (IV) 96 bits (12 bytes) है, per NIST SP 800-38D। यह secret नहीं लेकिन प्रति key unique होना चाहिए। Random nonces यहाँ safe हैं क्योंकि हम प्रति फ़ाइल fresh key generate करते हैं। IV को ciphertext के साथ prepend करना एक common convention है; recipient decrypting से पहले इसे वापस split करता है।
Step 4: Ciphertext Upload करें
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;
}
Server एक binary blob receive करता है, उसे ID assign करता है, store करता है, और वह ID return करता है। कोई header filename reveal नहीं करता, कोई query parameter key carry नहीं करता।
Step 5: Fragment में Key के साथ Share URL Build करें
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 encoding (+ और / की जगह - और _) URL escaping issues से बचाता है। = padding aesthetics के लिए strip होता है।
Fragment (#...) यहाँ magic है। जब recipient URL load करता है, तो browser fragment client-side रखता है। /f/{fileId} के लिए HTTP GET में #keyBase64 request line में include नहीं होता, इसलिए server key कभी नहीं जानता। इसे खुद browser dev tools में किसी fragment-containing URL पर Network tab देखकर verify करें।
Step 6: Recipient-Side Decryption
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 का authentication tag decrypt() के दौरान check होता है। अगर ciphertext tampered था, तो call OperationError throw करता है — एक clean failure mode।
PBKDF2 के ज़रिए Password-Derived Keys
अगर user random key की जगह password supply करते हैं, तो PBKDF2 के ज़रिए AES key derive करें:
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 PBKDF2-SHA-256 iterations OWASP का 2023 baseline है। Salt 16 random bytes होना चाहिए और ciphertext के साथ stored हो (secret नहीं, बस unique होना चाहिए)। New code के लिए, argon2-browser जैसी library के ज़रिए Argon2id consider करें — यह GPU attacks को PBKDF2 से बहुत बेहतर resist करता है।
बड़ी फ़ाइलों को Stream करना
500 MB से बड़ी फ़ाइलों को chunk करना चाहिए। File.stream() के ज़रिए read करें, हर chunk encrypt करें, sequentially upload करें:
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);
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;
}
Chunk index से nonce derive करना state track किए बिना uniqueness guarantee करता है। Recipient side पर, chunks को order में decrypt करके concatenate करें।
True streaming AEAD के लिए, libsodium.js का crypto_secretstream_xchacha20poly1305 cleaner है और truncation attacks detect करता है। Web Crypto का 2026 में equivalent primitive नहीं है।
Testing और Gotchas
बचने वाली आम गलतियाँ:
- Keys या nonces के लिए
Math.random()use करना: हमेशाcrypto.getRandomValues()use करें। - Same key के साथ nonce reuse करना: GCM security तोड़ता है। Per-file random keys इसे safe बनाते हैं; per-chunk flows को प्रति chunk unique nonces चाहिए।
- HTTPS check न करना:
crypto.subtleinsecure origins पर undefined है। localStorageमें keys store करना: आपके origin पर कोई XSS इसे पढ़ सकता है। URL fragment pattern use करें।- Ciphertext के साथ IV include करना भूलना: decryption बिना useful error के fail होता है।
- Fragment accidentally third-party service को post करना: fragment sensitive हो तो केवल end-to-end channels से share करें।
Server-Side ज़िम्मेदारियाँ
Client-side encryption architecture में server का काम छोटा है: POST accept, blob store, ID return, blob के लिए GET serve, expiration पर delete। कोई crypto नहीं। Storage से परे server को क्या करना चाहिए:
- File size caps enforce करें (abuse रोकें)
- Uploads और downloads rate-limit करें
- Short retention set करें (HexaTransfer की तरह 7 days reasonable default है)
- केवल ज़रूरी log करें (upload timestamp, privacy-first तो no IPs)
- TLS 1.3 with HSTS serve करें
- CORS headers origins restrict करें
सब कुछ एक साथ
एक minimal working app एक HTML file plus 50-line Express backend में fit होती है। Total dependencies: client पर कोई नहीं (Web Crypto native है), server पर Express plus multer। Encryption उतनी ही strong है जितनी AES-256-GCM primitive है — क्योंकि literally वही use हो रहा है। कोई secret algorithm नहीं जो गलत हो, केवल primitives को correctly use करना है।
मुश्किल हिस्से edge cases हैं: large files, password-to-key flows, decryption fail होने पर recipient UX, expired links gracefully handle करना। Core cryptography straightforward है।
hexatransfer.com पर आज़माएं — मुफ्त, बिना अकाउंट, 10 GB तक।
एंड-टू-एंड एन्क्रिप्शन के साथ बड़ी फ़ाइलें सुरक्षित रूप से भेजें
एंड-टू-एंड एन्क्रिप्शन के साथ 10 GB तक की फ़ाइलें मुफ़्त में ट्रांसफ़र करें। अकाउंट की आवश्यकता नहीं। अपलोड से पहले आपकी फ़ाइलें ब्राउज़र में एन्क्रिप्ट की जाती हैं — कोई और उन्हें पढ़ नहीं सकता।
फ़ाइल भेजें