Zero-knowledge upload. Every queued file is encrypted with AES-GCM-256 before upload begins; your server only ever sees ciphertext. Decryption requires the password and the per-file salt + IV emitted as headers. Compliance-friendly default for HIPAA / GDPR confidentiality workflows. None of Uppy, FilePond, Dropzone, or FineUploader offer this built-in.
Try it: drop a file, hit upload, then inspect the request body in DevTools → Network. The payload is unintelligible binary, and three response-side headers are sent:
X-Mu-Encryption-Algo, X-Mu-Encryption-Salt, X-Mu-Encryption-IV — record these alongside the ciphertext on the server.
Configuration
AjaxUploader.create('#uploader', {
uploadUrl: '/ajaxupload.axd/upload',
encrypt: true, // or 'aes-gcm-256'
encryptPassword: 'shhh',
// OR for a prompt-driven UI:
// encryptPassword: () => promptUser(),
// OR for a pre-derived key:
// encryptKey: cryptoKey,
encryptIterations: 200000 // PBKDF2 iterations (default)
});
Wire-protocol headers (sent on every chunk / single upload)
X-Mu-Encryption-Algo: aes-gcm-256
X-Mu-Encryption-Salt: <16 random bytes, base64url>
X-Mu-Encryption-IV: <12 random bytes, base64url>
X-Mu-Encryption-Iter: 200000
X-Mu-Encryption-Original-Name: <URL-encoded cleartext filename>
X-Mu-Encryption-Original-Size: <cleartext byte count>
Decryption (browser-side download viewer)
// Static helper: pass the ciphertext blob, password, salt, iv
const cleartext = await AjaxUploader.decryptFile(
ciphertextBlob,
password,
saltFromHeader,
ivFromHeader,
200000 // iterations, must match upload
);
// cleartext is a Blob — show it, save it, or pipe it to a viewer
Decryption (server-side, e.g. .NET 8+)
// PBKDF2 + AES-GCM are first-class in .NET 8's System.Security.Cryptography.
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 200000, HashAlgorithmName.SHA256);
var key = pbkdf2.GetBytes(32);
using var aes = new AesGcm(key, 16);
var ciphertext = encryptedFile.AsSpan(0, encryptedFile.Length - 16);
var tag = encryptedFile.AsSpan(encryptedFile.Length - 16);
var plaintext = new byte[ciphertext.Length];
aes.Decrypt(iv, ciphertext, tag, plaintext);
Algorithm details
- Cipher: AES-256-GCM (authenticated encryption — tamper-evident).
- Key derivation: PBKDF2-HMAC-SHA256, 200 000 iterations by default (tunable via
encryptIterations).
- Salt: 128-bit random per file. Stored alongside ciphertext; never reused.
- IV (nonce): 96-bit random per file. Stored alongside ciphertext; never reused with the same key.
- Auth tag: 128-bit GCM tag appended to ciphertext (handled automatically by SubtleCrypto).
Failure modes & safety
- Insecure context (http://):
SubtleCrypto is unavailable and the encrypt task fails fast with encryption_failed: SubtleCrypto unavailable. No silent fallback to cleartext — that's the right default for compliance use.
- Missing password: queued files fail with a clear error before any byte leaves the browser.
- Resume after reload: when paired with
persistState, encryption is preserved (the ciphertext file is what's persisted, not the cleartext).