systemslibrarian/shadow-vault
GitHub: systemslibrarian/shadow-vault
一个基于浏览器的可否认加密演示工具,通过双密码机制让同一容器可解密出真实或诱饵两条消息,且无法从结构上判断哪个才是真的。
Stars: 0 | Forks: 0
# 影子金库
**可否认加密 — 两条消息,一个容器,无痕可循。**
**[在线演示](https://systemslibrarian.github.io/shadow-vault/)**
你被扣留了。他们要求你提供密码。你照做了。他们解密出一条看似合理的信息 — 一份购物清单、一篇日记、一张给朋友的便条。他们无法证明、无法检测、甚至无法测试的是:在同一容器的不同偏移位置隐藏着第二条加密信息,只有使用另一个他们不知道存在的不同密码才能解密。
影子金库是一个基于浏览器的可否认加密演示,使用固定大小的容器存放两条独立加密的信息。真实密码解密真实信息。诱饵密码解密诱饵信息。容器在结构上与随机字节无法区分 — 没有头部、没有魔数、没有长度字段、没有取证指纹。
## 工作原理
```
Passphrase A → Argon2id (64 bytes) → key[0..31] + nonce[32..43] + offset[44..47]
↓ ↓
ChaCha20-Poly1305 encrypt position in container
Passphrase B → Argon2id (64 bytes) → key[0..31] + nonce[32..43] + offset[44..47]
↓ ↓
ChaCha20-Poly1305 encrypt position in container
Container = CSPRNG random bytes (fixed size: 4KB–32KB)
+ real ciphertext at offset A
+ decoy ciphertext at offset B
+ everything else remains random padding
```
1. 固定大小的容器完全由加密随机字节填充
2. 每个密码通过 Argon2id 独立派生加密密钥、随机数和槽位偏移量
3. 每条信息使用 ChaCha20-Poly1305 加密并写入其派生的偏移位置
4. 结果看起来像随机数据 — 与 CSPRNG 填充无法区分
## 为什么 Argon2id 是关键
Argon2id 不是偶然的选择 — 它是可否认加密模型的安全基础。
如果没有内存硬密钥派生,获得容器的攻击者可以同时暴力破解两个密码,恢复两个偏移量和两条信息。使用高内存参数(每次派生 64MB+,按 RFC 9106)使这在计算上毫无希望。成本随每个密码尝试递增 — 攻击两个独立密码需要两倍的成本。
UI 明确展示了这一点:调低 Argon2id 参数,观察派生时间下降到危险水平。
## 密码学技术栈
| 组件 | 实现 | 参考 |
|-----------|---------------|-----------|
| 密钥派生 | Argon2id (Rust → WASM, `argon2` crate) | [RFC 9106](https://datatracker.ietf.org/doc/html/rfc9106) |
| 对称加密 | ChaCha20-Poly1305 AEAD (Rust → WASM, `chacha20poly1305` crate) | [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) |
| 内存清零 | 通过 `zeroize` crate 保证(编译器屏障) | [zeroize](https://docs.rs/zeroize) |
| 随机生成 | `getrandom` crate(由 Web Crypto API 在 WASM 中支持) | [W3C Web Crypto](https://www.w3.org/TR/WebCryptoAPI/) |
| 盐值派生 | SHA-256(从角色确定性派生,`sha2` crate) | [FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final) |
**所有密码操作都在 Web Worker 中运行** — 密钥材料永远不会离开 WASM 线性内存。RustCrypto crates(`argon2`、`chacha20poly1305`)是经过社区审计的实现。`zeroize` crate 使用 `volatile` 写入来保证敏感内存在释放前被清零,这是 JavaScript 的 `fill(0)` 无法保证的。
**Argon2id 参数(默认):**
- 内存:64 MB(65536 KiB)— RFC 9106 交互式使用最低要求
- 迭代次数:3 — RFC 9106 推荐值
- 并行度:4
- 输出:64 字节(密钥 + 随机数 + 偏移量种子)
**ChaCha20-Poly1305:** 通过 `chacha20poly1305` crate(RustCrypto)在 Rust 中实现。在每次应用加载时根据 RFC 8439 测试向量进行验证。所有密码操作通过 WASM 在 Web Worker 中运行 — 主线程从不处理密钥材料。
## 此工具无法防护的情况
- 本演示代码中的**实现漏洞**
- **键盘记录器**或被入侵的设备捕获密码
- **暴力胁迫**(酷刑逼供)
- **容器外的元数据** — 文件名、时间戳、访问日志、浏览器历史
- **浏览器内存** — 密码以 JavaScript 字符串(不可变、被垃圾回收)形式输入后才传到 WASM。Rust/WASM 确定性地清零所有密钥材料,但 JavaScript 中的密码字符串是不可避免的。
- **流量分析**(如果容器在网络上传输)
## 诚实的局限性
影子金库使用**经审计的 RustCrypto crates** 编译为 WASM,但集成和容器格式尚未经过正式的安全审计。基于浏览器的密码学继承浏览器的所有安全模型限制(扩展、开发者工具、操作系统级攻击)。
密码仍然通过 HTML `` 元素作为不可变的 JavaScript 字符串输入 — Rust/WASM 无法清零这些。其他所有内容(密钥、盐值、明文槽)通过 `zeroize` crate 确定性地清零。
**真实信息和诱饵信息都需要强且唯一的密码。** 可否认性取决于攻击者无法暴力破解任一密码。无论 Argon2id 参数如何设置,弱密码都会使整个安全模型崩溃。
解密后的信息会在 2 分钟后自动清除。敏感输入在空闲和页面卸载时清除。
此工具在严重威胁模型下比原生应用程序弱。对于现实世界的可否认加密,请使用带隐藏卷的 [VeraCrypt](
标签:Argon2id, ChaCha20-Poly1305, CMS安全, CSPRNG, JavaScript, meg, Poly1305, Web Crypto API, 信息安全, 加密容器, 可否认加密, 可视化界面, 密码学, 密钥派生函数, 对称加密, 手动系统调用, 抗审查, 数据隐藏, 浏览器加密, 端到端加密, 网络安全, 自动化审计, 通知系统, 随机数生成, 隐写术, 隐私保护, 零知识证明