The Deniability Problem
Coercion can reveal a passphrase and its encrypted message. A two-slot container can be used to present a plausible decoy, but that scenario does not itself establish what a format-aware adversary can learn.
Shadow Vault puts two independently encrypted messages at different offsets. It does not provide role privacy: a format-aware adversary can classify the disclosed role by trying the public real/decoy derivation domains and counters and checking which candidate authenticates. Revealing the decoy passphrase therefore discloses its configured decoy label. This does not recover the unopened plaintext or prove the meaning or importance of either message. A stronger deniability guarantee would require a reviewed format/design change; concealing UI labels cannot supply it.
The Two-Passphrase Model
Each passphrase and public role/counter domain derive a key, nonce and offset through Argon2id. Authentication identifies the configured real or decoy role for that passphrase. Opening one role does not provide the other independently chosen passphrase or its plaintext. Equal derivation work and generic failure messages do not conceal the successful role from an offline reader.
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 expose their own slots; correlated passwords can ease separate guessing, and reuse across containers adds key/nonce reuse risks
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, independently generated passphrases are required to protect both messages. Guessing one opens its own slot; this does not imply automatic recovery of the other independently protected message. For real-world deniable encryption, use VeraCrypt with hidden volumes.