The Deniability Problem
Standard encryption proves a message exists. Under coercion, possessing an encrypted file is itself incriminating. An adversary who compels you to surrender your passphrase gains the message — and knows there's nothing else to find.
Deniable encryption solves this: one container holds two independent encrypted messages at different offsets. You reveal the decoy passphrase. The adversary gets a plausible message. The real message stays unreadable and unprovable — the rest of the container is indistinguishable from random padding, so nothing in the bytes marks a second message as present. One honest caveat: Shadow Vault always writes exactly two slots and requires both passphrases, so an adversary who recognises the format knows a second slot exists. They still cannot read it, or prove it holds anything rather than padding, without the second passphrase. Against a format-aware adversary the deniability is about contents, not existence.
The Two-Passphrase Model
Each passphrase independently derives an encryption key, a nonce, and a slot offset via Argon2id. The real passphrase decrypts the real message. The decoy passphrase decrypts the decoy. A format-aware adversary knows the tool writes two slots, but without both passphrases cannot identify their contents or distinguish the unopened ciphertext from random padding.
Why Argon2id Is Load-Bearing
Argon2id is not incidental — it is the security foundation. Without memory-hard key derivation, the deniability model collapses: an attacker who can brute-force both passphrases simultaneously recovers both offsets and both messages.
Argon2id makes the first dictionary construction expensive, but this demo has no per-container salt: a table built for one parameter set can be reused across every Shadow Vault container at those settings. High-entropy, independently generated passphrases—not a claimed per-target work factor—are the defense.
Container Format
The container is one of four recognized fixed sizes and contains two encrypted slots at derived offsets in CSPRNG padding. It has no plaintext header or magic bytes. Each encrypted slot's plaintext includes a four-byte length field, hidden inside the authenticated ciphertext. Its bytes should look random, while the recognized size and known format still fingerprint it as a Shadow Vault container.
Passphrase → Argon2id(64B) → key[0..31] + nonce[32..43] + offset[44..63] → ChaCha20-Poly1305 AEAD
What This Cannot Protect Against
- Implementation bugs in this demonstration code
- Keyloggers or compromised devices capturing passphrases
- Coercion with violence (rubber-hose cryptanalysis)
- Metadata outside the container (filenames, timestamps, access logs)
- Browser memory not being securely wiped (passphrases enter as JS strings)
- Weak passphrases — deniability collapses if either passphrase can be brute-forced
Honest Limitations
Shadow Vault is a demonstration of deniable encryption concepts, not production-grade deniable storage. Browser-based cryptography is inherently weaker than native tools. Strong, unique passphrases are required for both messages — deniability depends on the attacker being unable to brute-force either one. For real-world deniable encryption, use VeraCrypt with hidden volumes.