ultrathinker/matrishka

GitHub: ultrathinker/matrishka

将任意文件加密嵌入外层视频中,外层视频正常播放而隐藏内容通过密码经本地 HTTP 回环实时解密流式传输,实现隐蔽存储与可否认性。

Stars: 0 | Forks: 0

# Matrishka [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![.NET 10](https://img.shields.io/badge/.NET-10-512BD4.svg) ![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20Windows%20%7C%20macOS-informational.svg) Matrishka 的招牌绝活是**视频嵌套**:它将一个加密视频嵌入到另一个视频中。 外层视频可以在任何媒体播放器中正常播放,而隐藏的视频会通过本地 HTTP 回环实时解密, 并流式传输到播放器——任何数据都不会以明文形式写入磁盘。但隐藏的不仅仅是视频:你可以 将**任何文件**(文档、压缩包、照片等)存入外层视频中,并在之后通过密码进行恢复(files 模式支持 使用一个主密码打包多个文件)。关于该功能能防范和不能防范的威胁,请参阅下方的**威胁模型**部分。 ## 截图 | 选择操作 | 隐藏视频 | 提取 | |:---:|:---:|:---:| | ![Landing screen](https://static.pigsec.cn/wp-content/uploads/repos/cas/63/630351f5bb7272267e3497681ba5e80505865f22e507c53efe3bcc107e7590a4.png) | ![Hide videos](https://static.pigsec.cn/wp-content/uploads/repos/cas/2e/2e12104d03971c117f0b70f537f8dddbc0614460fd0be25fb45f6872121883f0.png) | ![Extract](https://static.pigsec.cn/wp-content/uploads/repos/cas/e7/e755bff03438747b362bdae0652d7efa8d39ee2eabb4a8d550194e8fddd5327b.png) | 同一个可执行文件也是一个完整的 CLI(见下文)—— GUI 只是对其进行了封装。 ## 状态 v0.3.0 —— 当前的文件格式(外层视频 + slot 区域 + 74 字节尾部 + 加密的 TOC, 设计上没有 magic bytes)。纯净的 `Release` 构建,在 Linux、 Windows 和 macOS 上通过 `.github/workflows/ci.yml` 中的 CI 矩阵实现了 **79/79 项测试全部通过**。CLI 和 Avalonia GUI 均通过单个二进制文件分发。 ## 下载 / 构建 目前还没有预编译的发布产物——要运行 matrishka,请从 源码构建二进制文件。为任何平台生成自包含单文件可执行程序的推荐方式是使用 [`build.sh`](build.sh): ``` ./build.sh # builds for all platforms: linux-x64, linux-arm64, win-x64, osx-x64, osx-arm64 ./build.sh linux-x64 # …or just one target ``` 该脚本只是封装了对 `src/Matrishka.App/Matrishka.App.csproj` 执行的 `dotnet publish -r --self-contained true -p:PublishSingleFile=true`, 并将输出精简为纯粹的可执行文件。 结果:`dist//matrishka`(在 Windows 上为 `matrishka.exe`),大小约 58 MB,目标 机器上无需安装 .NET runtime。你可以将其放在 `PATH` 中的任意位置并运行。 **开发构建**(需要 .NET 10 SDK): ``` dotnet build -c Release # 位于 src/Matrishka.App/bin/Release/net10.0/matrishka(.exe) 的二进制文件 ``` 有关运行测试套件的信息,请参阅下方的[测试](#tests)部分。 ## 支持的容器 | 格式 | 扩展名 | 封装方式 | 隐蔽性 | |---|---|---|---| | **MP4 家族** (ISO BMFF) | `.mp4` `.mov` `.m4v` `.3gp` `.3g2` | 16 字节 `free` box | 高(有效的 MP4 填充) | | **Matroska / WebM** (EBML) | `.mkv` `.webm` | 9 字节 EBML Void | 中(多数播放器会忽略) | | **AVI** (RIFF) | `.avi` | 8 字节 RIFF JUNK | 中(多数播放器会忽略) | 格式通过嗅探前 16 字节内容来检测(文件扩展名仅供参考)。`secret` 可以是**任意二进制内容**——它不必与外层视频的格式匹配。 ## GUI 运行不带参数的 `matrishka` 即可打开图形界面(基于 Avalonia,跨平台)。窗口包含四个大卡片——Hide、Open、Save、Find——并支持从文件管理器进行拖放操作。 同一个二进制文件同时提供 UI 和 CLI 服务;其分发逻辑为: ``` matrishka → opens the GUI window matrishka pack ... → runs CLI (any non-zero args) ``` ## CLI ``` matrishka pack --secret --password [--secret --password ...] [--label ] ... [--master ] [-o ] [--kdf-strength fast|normal|paranoid] matrishka play [-p ] [--index ] [--player ] matrishka extract [-p ] [--index ] [--all] matrishka info [-p ] matrishka scan [-p ] matrishka add --secret --new-password --open-password [--open-password ...] [--label ] [--kdf-strength fast|normal|paranoid] matrishka forget --forget-password --keep-password [--keep-password ...] [--kdf-strength fast|normal|paranoid] matrishka repack [-p ...] [--add-secret --add-password ...] [--add-label ...] [--master ] [--kdf-strength fast|normal|paranoid] matrishka recover-cover [output] ``` 如果在需要密码的地方省略了 `-p` / `--password`,系统会提示 输入密码并带有掩码显示(支持 Backspace 和 Escape)。 ### pack Secrets 通过可重复使用的 `--secret` 选项传递,每个 secret 需与一个可重复使用的位置参数 `--password` 配对(数量必须匹配)。可选的 `--master` 会加密一个 TOC,允许使用主 密码列出 / 提取 / 播放文件中的每一个 secret。 ``` # 一个 secret,没有 master —— 最简单的情况 matrishka pack ozero.mp4 --secret family.mkv --password mypass -o trojan.mp4 # 在 master 密码下的三个 secrets matrishka pack ozero.mp4 \ --secret a.mkv --password pa --label "alice" \ --secret b.mkv --password pb --label "bob" \ --secret c.mkv --password pc --label "carol" \ --master secret-master -o trojan.mp4 # 最弱的 KDF,用于在微小的测试文件上进行快速迭代 matrishka pack cover.mp4 --secret s.bin --password p --kdf-strength fast -o out.mp4 ``` 外层容器会被自动检测(MP4 / MOV / MKV / WEBM / AVI),secret 可以是任意 二进制数据(往返过程会恢复出完全一致的字节)。输出文件会被标记为**只读**, 以防止媒体服务器对其进行意外修改。 ### play / extract / info ``` matrishka play trojan.mp4 # decrypts on-the-fly via 127.0.0.1 → mpv matrishka play trojan.mp4 -p master --index 2 # master mode: play the 2nd secret matrishka extract trojan.mp4 recovered.mkv # decrypts to a real file (not read-only) matrishka extract trojan.mp4 outdir/ -p master --all # master mode: extract every secret into a dir matrishka info trojan.mp4 # structural info (no password) matrishka info trojan.mp4 -p master # master mode: list every secret + label + size ``` `play` 通过带有 Bearer token 认证的 `127.0.0.1` HTTP 回环服务器进行解密, 并启动播放器。要求 `PATH` 中存在 `mpv`(可以使用 `--player /path/to/mpv` 覆盖)。 token 通过 HTTP header 传递,因此它永远不会出现在进程命令行、 历史记录或 watch_later 文件中。设有 24 小时的安全超时。 `info` 会读取顶层容器结构以及 v3 尾部(外层视频大小、KDF 参数、 `slots_padded`、是否存在 master TOC)。对于 MP4,它会遍历所有 box;对于 MKV/AVI,它 仅确认格式。如果提供了主密码,它还会列出每个 secret 及其 标签和大小。 ### add / forget / repack 这些命令会重建文件(磁盘上的格式是一次性写入的)。`add` 追加一个 secret,`forget` 移除一个,而 `repack` 是通用的“使用这些 密码打开,可选地添加 / 移除 / 重新设置主密码,并写入新文件”的路径。这三个命令都 支持 `--kdf-strength` 参数。 ``` # 向现有文件 add 一个新的 secret(提供你想要保留的每个密码) matrishka add trojan.mp4 \ --secret new.mkv --new-password pnew \ --open-password pa --open-password pb --open-password pc # 移除用 "pb" 打开的 secret matrishka forget trojan.mp4 --forget-password pb \ --keep-password pa --keep-password pc # 完全重建:保留两个 secrets,再 add 两个,设置一个全新的 master matrishka repack trojan.mp4 fresh.mp4 \ -p pa -p pc \ --add-secret d.mkv --add-password pd --add-label "dave" \ --add-secret e.mkv --add-password pe --add-label "eve" \ --master new-master ``` ### recover-cover ``` matrishka recover-cover trojan.mp4 # writes _cover. next to the source matrishka recover-cover trojan.mp4 cover.mp4 # …or an explicit path ``` 从 matrishka 文件中重建原始的外层视频字节。**无需密码**—— 外层视频在设计上就是明文(它必须能在任何媒体播放器中播放)。MKV/AVI 与源文件在字节上是完全一致的; 如果原始文件的最后一个顶层 box 大小为 size=0,MP4 可能会有约 8 字节的差异 (我们在打包时将其重写为显式大小)——恢复出的 MP4 仍能正常播放。 ### scan ``` matrishka scan ~/Movies -p mypass ``` 递归遍历文件夹,并尝试使用提供的密码对每个视频文件进行验证。 该格式**没有** magic byte 或快速失败的签名,因此每个候选文件都需要付出完整的约 1 秒 Argon2id 计算成本——这就是可否认性的权衡。每次命中都会报告为 individual(单个 secret)或 master(secret 计数)。 ## 文件格式 (v0.3) ``` [0 .. cover_size) — original cover (unmodified) [cover_size .. cover_size + W) — container wrapper: W = 16 B `free` (MP4), 9 B Void (MKV), 8 B JUNK (AVI) [ body ] [ secret regions, tightly packed ] — one AEAD region per secret (no gaps between them) [ encrypted TOC region (only if --master)] — nonce(12) | ciphertext(N) | mac(32) [ random padding ] — until (body + tail) is a multiple of 1 MiB [ tail (read from EOF backward) ] [ slot_area ] — slots_padded × 108 B (always a power of 2, ≥ 256) [ global_salt : 16 B ] — single salt used by every Argon2id run [ tail_mac : 32 B ] — HMAC-SHA256 over the 42 B below (master-only) [ toc_offset : 8 B BE ] — body-local offset to the TOC region [ toc_size : 4 B ] [ slots_padded : 2 B ] — power of 2 in [256, 16384] [ flags : 1 B ] — bit0 = has_master_toc [ cover_size : 7 B BE ] — 56-bit, caps cover at ~72 PB [ kdf_params : 4 B ] — [ver=3, argon2_t, m_log2_MB, argon2_p] ← EOF ``` **固定尾部为 74 字节**;总尾部大小为 `74 + slots_padded × 108`。body + tail 会被填充到 1 MiB 边界,因此总文件大小绝不会将 secret 的大小 精确泄露到一兆字节以内。 **没有 magic,没有 CRC。** 这是设计使然:任何结构标记都是一种取证签名,会 破坏可否认性的承诺。其代价是“这是一个 matrishka 文件吗?”这个 问题只能通过尝试密码(约 1 秒的 Argon2id)来回答。 **Slot 内部结构(每个 108 字节):** `salt(16) || nonce(12) || enc_payload(48) || mac(32)`。 加密的 payload 是 `body_master(32) || body_offset(8) || body_length(8)`。已填充的 slot 隐藏在 `slots_padded − populated` 个随机诱饵 slot 中;扫描总是 遍历所有 slot 并总是只执行一次解密,因此它泄露了 `slots_padded`(已经在 尾部公开),但不会泄露已填充的数量或匹配的索引。 **TOC 区域(仅在设置了 `--master` 时写入):** 在 master 的 master key 下加密,每个 secret 包含一个 TLV 属性 payload(`Label`、`Mime`、`MtimeMs`)。 如果没有 master,该格式仍会保留 TOC 的 offset/size,并填充随机字节, 使得尾部看起来完全一致。 **Crypto pipeline:** 1. **Argon2id** 对每个 secret 基于 `(password, global_salt)` 运行一次 → 生成每个 secret 的 `master`。如果设置了 `--master`,Argon2id 会再基于 `(master_pw, global_salt)` 运行一次 → `master_master`。强度可以通过 `--kdf-strength` 选择: | preset | t | m | p | ≈ time | |---|---|---|---|---| | `fast` | 2 | 32 MB | 2 | ~0.5 s | | `normal` | 3 | 64 MB | 2 | ~1 s | | `paranoid` | 4 | 256 MB | 4 | ~4 s | `normal` 是默认值。文件最末尾的四个明文字节 (`kdf_params`)记录了所选的 preset,因此读取端 始终使用打包端写入的参数——不需要单独的协商。 2. **HKDF-Expand (SHA-256)** 使用带有长度前缀的 域分离标签(字节稳定,带有 `-v3` 后缀)派生出其余的密钥: | label | used for | |---|---| | `matrishka-toc-enc-v3` | AES key for the master TOC region | | `matrishka-toc-mac-v3` | HMAC key for the master TOC region | | `matrishka-tail-mac-v3` | HMAC key for the 42-byte tail (master-only) | | `matrishka-slot-test-v3` | constant-time slot-match test | | `matrishka-slot-enc-v3` | AES key for the 48-byte slot payload | | `matrishka-slot-payload-mask-v3` | MAC/verify key for the slot payload | | `matrishka-body-enc-v3` | AES-256-CTR key for a secret's body region | | `matrishka-body-mac-v3` | HMAC-SHA256 key for the per-chunk body MACs | | `matrishka-body-nonce-v3` | AES-CTR nonce for a secret's body region | 所有三个 body key 都绑定到了该 secret 的 slot 索引上,因此即使两个 secret 意外 共享了同一个 `body_master`,它们也会获得截然不同的 keystream。 3. **Body AEAD:** 1 MiB 的 AES-256-CTR chunk,每个 chunk 后面跟着一个 32 字节的 `HMAC-SHA256(chunk_index_be64 || ciphertext)` 标签。MAC 输入中的 chunk 索引 可防止重新排序 / 替换攻击。 **每个文件的额外开销:** `wrapper (0/8/9/16) + 74 B fixed tail + slots_padded × 108 B + 每个 secret 每 1 MiB chunk 32 B + 填充到下一个 1 MiB`。对于单个较小的 secret,固定开销约为 28 KB(主要由最小 256 个 slot 的表决定); 对于 1 GB 的 secret,开销约为其大小的 0.003%。 **防篡改性:** 每个 chunk、每个 slot、TOC 区域,以及(带有 master 的)固定尾部 header 都带有 HMAC-SHA256 标签,并使用恒定时间比较进行验证。Bit-flip(比特翻转)、 交换或替换都会被立即检测到——提取操作会失败并明确提示 “HMAC verification failed”,而不是产生乱码输出。在 master-open 路径上,如果尾部 header 被篡改,会引发 `TailTamperedException`(与“密码错误”不同)。 ## 威胁模型 **可防御:** - 普通观察者(在 NAS 上翻看你电影的朋友) - 简单的取证工具(`file`、`binwalk`) - 自动化的媒体服务器——存在一些注意事项(见下文) - 针对加密区域的比特翻转 / 篡改攻击(每个 chunk 的 HMAC 会检测到任何修改) **无法防御:** - 针对文件尾部的取证级熵分析 - 政府强制手段(存在 `matrishka` 二进制文件会破坏可否认性) - 如果外层视频是公开已知的,可能会遭受已知明文攻击 - 重新编码 / 重新封装外层视频容器(这必然会破坏 payload) ## 警告 - ⚠ **不要将 matrishka 文件存储在媒体服务器的媒体库中**(Plex、Jellyfin、Emby、Kodi自动整理、Handbrake 的批量转码)。这些操作会重新封装容器并破坏隐藏的 payload。 - ⚠ **忘记密码 = 数据丢失。** 无法恢复。 - ⚠ **MP4 外层视频是最隐蔽的。** MKV/AVI 会将尾部封装在一个小的原生元素中(EBML Void / RIFF JUNK),但大部分的 payload 仍然会作为尾部数据跟随其后。宽容的播放器仍然可以播放它们,但 `binwalk` 会标记出这些尾部数据。 - ⚠ **不要使用公开已知的电影作为外层视频**来存放关键的 secret——因为这可能会引发已知明文的大小差异攻击。 ## 已知限制 - **密码会在进程内存中驻留。** 无论是 `--password`(System.CommandLine 字符串) 还是 GUI 密码输入(Avalonia `TextBox.Text` 返回 `System.String`)产生的都是 CLR 字符串,这些字符串无法被可靠地覆盖——GC 可能会移动、复制或保留它们。 我们会尽早将密码复制到一个自动清零的 `SecureBuffer` 中,但 原始字符串可能会一直存活到进程退出。请将运行中的 `matrishka` 进程视为在其生命周期内一直持有密钥材料;不要在多用户 机器上使其处于无人值守的运行状态,也不要生成 core dump。要完全修复此问题,需要一个定制的 Avalonia 密码控件来暴露 `char[]`——请参阅下文的 *Roadmap*。 ## 限制 - 最大 secret 大小:60 GB(受限于 AES-CTR 计数器)。 - 每个外层视频最多可包含 16 384 个 secret(`MaxSlotsPadded`);`slots_padded` 始终是 ≥ 256 的 2 的幂。 - 外层视频通过内容进行嗅探,因此重命名的文件仍然有效;但扩展名对于 `scan` 很重要(仅扫描已知的视频扩展名)。 ## 测试 ``` dotnet test ``` 79 项测试涵盖: - **Crypto:** NIST SP 800-38A AES-CTR 向量、RFC 5869 HKDF 向量、Argon2id 确定性、分块 AEAD 往返测试、比特翻转检测、MAC 篡改检测(chunk + 尾部)、chunk 索引重排拒绝 - **Parsers:** MP4 box 遍历器(多 mdat、扩展大小、size=0 修复)、EBML / RIFF 嗅探 - **Tail / format:** `TailHeader` 往返读/写、字段边界验证(TOC offset/size、外层视频大小、`slots_padded`)、slot 数量规范检查、尾部 MAC 篡改拒绝 - **Integration:** 所有容器格式上的打包/提取往返测试、错误密码拒绝、Range header 解析(3 种形式)、HTTP 回环服务器(认证、范围、跨 chunk 读取) - **Regressions:** `SecureBuffer.FromCharArray` 清零输入、MP4 预嗅探过滤器、`MpvLauncher` 在失败时不会泄露临时配置 ## Roadmap Matrishka 仍处于 1.0 版本之前的阶段,正在不断发展。大致方向: - **现在** — CLI + Avalonia GUI、分块 AEAD、多容器(MP4/MKV/AVI)、多 secret 打包。 - **下一步** — 一个暴露 `char[]` 的定制 Avalonia 密码控件(参见*已知限制*)、更多容器格式、针对大文件流式传输的强化。 - **未来** — 服务器模式(将解密的流通过局域网暴露给机顶盒)、配套的移动端查看器。 欢迎提供建议和 PR — 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 贡献 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。安全问题:请遵循 [SECURITY.md](SECURITY.md) — **不要**开启公开的 issue。 ## License [MIT](LICENSE) © 2026 universeissilent42. 应用图标是 OpenMoji “Nesting Dolls” emoji(CC BY-SA 4.0)的衍生作品; 请参阅 [`src/Matrishka.App/Assets/icon/NOTICE.md`](src/Matrishka.App/Assets/icon/NOTICE.md)。 第三方依赖许可证列在 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) 中。
标签:DNS 反向解析, HTTP工具, 信息隐藏, 多媒体处理, 批量测试, 数据加密, 视频隐写, 跨平台工具