DreadpiratePickles/lockbox-lane
GitHub: DreadpiratePickles/lockbox-lane
一款端到端加密的文件分享工具,文件在客户端使用 AES-256-GCM 加密,服务器永远无法接触明文和密钥。
Stars: 0 | Forks: 0
# 🔒 Lockbox Lane
### 在你的机器上进行加密后再分享文件,确保在任何数据离开之前就已加密。
     
*如果服务器能够读取它,那它就不是私密的——只不过是读取起来有点麻烦而已。*
Lockbox Lane 是一个小型的防御性文件分享工具。文件在**客户端**进行加密
(在你的浏览器中使用 WebCrypto,或在本地使用 CLI),采用 AES-256-GCM 加密。服务器只
接收密文和公共 IV——**绝不接收密钥**。解密密钥通过分享链接的 URL *片段*(`#key=…`)进行传输,浏览器不会将该片段发送给服务器。服务器只是一个
姿态良好的储物柜;它无法读取自己存储的内容。
它附带了一个匹配的**本地加密 CLI**(`encrypt-file` / `decrypt-file` / `inspect` /
`keygen`),因此你可以从终端或
脚本中生成和打开完全相同的 AES-256-GCM 信封——这对于备份、带外传输以及解释为什么*“服务器稍后对其进行了加密”*
不等同于*“服务器从未见过它”*非常有用。
## 为什么会有这个项目
大多数“安全上传”工具都是*静态*加密的——但这发生在服务器已经接收到
你的明文**之后**。这可以防止磁盘被盗;但对于被攻破或好奇心强的
服务器毫无作用。Lockbox Lane 展示了用于简单分享的端到端加密:在传输前加密,加密存储,仅在接收者的浏览器中解密。
## 它是如何工作的
### 分享流程(浏览器)
```
Sender's browser Server (this app) Recipient's browser
──────────────── ───────────────── ───────────────────
pick file
generate AES-256 key ──┐
generate 96-bit IV │
encrypt (AES-GCM) ──────┘
│ ciphertext + iv
▼ POST /api/files
store blob on disk (uploads/
.bin)
store metadata in SQLite (id, size, iv,
expiry, download cap, count)
◀──────────────── { id, share_path }
build link:
/share/#key=&name=
│
│ key rides in the URL fragment — never sent in any HTTP request
▼
open /share/
GET /api/files/
◀────────────────────────── (server checks expiry
+ download cap, then
──────────────────────────▶ returns ciphertext + iv header)
decrypt in-browser with
key from the fragment
→ download plaintext
```
密钥在浏览器中生成、导出,并**仅**放置在分享
URL 的片段中。片段不会在 HTTP 请求中传输,因此服务器永远看不到密钥。任何拥有
*完整*分享 URL(包括片段)的人都可以解密——请将完整链接视为机密。
### 信封(CLI 和浏览器共享同一格式)
CLI 和浏览器都生成具有相同结构的 AES-256-GCM 输出,因此由其中一方生成的密钥可以
打开另一方生成的密文:
```
{ "version": 1, "iv": "", "ciphertext": "" }
```
- **密钥:** 256 位,base64url(无填充)。
- **IV:** 每次加密生成全新的 96 位随机值(对于给定的密钥绝不重用)。
- **认证标签:** 128 位,由 GCM 附加到密文后。解密时会对其进行验证,因此错误的密钥
*或*被篡改的文件都会安全失败并返回相同的消息——根据设计,你无法分辨具体是哪种情况。
### 数据模型
`files(id PRIMARY KEY, stored_name, size_bytes, iv, created_at, download_count, expires_at, max_downloads)`
- `id` 是 128 位的 `secrets.token_urlsafe` 熵——这个不可猜测的 id **就是**访问控制。
- `expires_at` / `max_downloads` 为 `NULL` 时表示“无限制”(因此旧的 v1 行读取不变;数据库在首次使用时自动迁移)。
### HTTP API
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `/` | 浏览器上传/加密界面。 |
| `POST` | `/api/files` | 存储加密的 blob + IV(已验证)。可选过期时间/下载上限。 |
| `GET` | `/api/files` | 列出 blob 元数据。除非 `LOCKBOX_LANE_ENABLE_LISTING=true`,否则**仅限本地回环**。 |
| `GET` | `/api/files/` | 下载密文;IV 在 `X-Lockbox-IV` 头中返回。强制执行过期时间+上限(失效后返回 `410`)。 |
| `GET` | `/share/` | 浏览器解密/下载界面。 |
## 安装
```
cd lockbox-lane
python -m venv .venv
# Windows: .venv\Scripts\activate POSIX: source .venv/bin/activate
pip install -r requirements.txt
pip install -e . # optional; provides the `lockbox-lane` entry point
cp .env.example .env # optional; tune limits and paths
```
不想进行可编辑安装?可以使用 `PYTHONPATH=src python -m lockbox_lane.cli …` 运行所有内容。
**要求:** Python 3.11+,以及 `flask`、`cryptography`、`rich`(已在上方安装)。浏览器端
需要任何支持 WebCrypto 的浏览器(所有现代浏览器都支持)。
## CLI 用法
全局选项位于子命令**之前**:`--db ` 和 `--storage `。
渲染选项位于子命令**之后**:`--json`(向标准输出输出纯 JSON,无装饰),
`--plain` / `--no-color`(无样式文本;自动遵循 `NO_COLOR` 和非 TTY 输出)。
每次调用都会打印一个简洁的品牌标题,然后是结果。`--json` 输出始终是
无任何装饰的纯 JSON,因此可以安全地通过管道传输。
### `keygen` — 生成新密钥
```
lockbox-lane keygen
```
```
╭───────────── new 256-bit key ─────────────╮
│ ZPjf_BR9vBBZ2tmKCfWIV2fC4siabkiBKzHYJKcj7M0 │
╰────────────────────────────────────────────╯
Pre-share this out-of-band, then: encrypt-file --key
```
### `encrypt-file` — 将文件加密到信封中
```
lockbox-lane encrypt-file --infile report.pdf --outfile report.lockbox --key-out report.key
```
```
╭──────────────── encrypted ────────────────╮
│ input report.pdf │
│ plaintext 48.2 KB │
│ envelope report.lockbox (64.3 KB) │
╰────────────────────────────────────────────╯
key written report.key (mode 0600 where supported)
```
- 省略 `--key` 可生成新密钥;提供 `--key` 可重用预共享密钥。
- `--key-out FILE` 将密钥写入 `0600` 权限的文件,并**确保其不会出现在标准输出中**。如果没有此参数,
密钥将在高亮面板中打印一次,并带有“请勿将其泄露到聊天/工单/日志中”的提醒。
- 除非你传递 `--force`,否则拒绝覆盖 `--outfile` / `--key-out`。
### `decrypt-file` — 打开信封
```
lockbox-lane decrypt-file --infile report.lockbox --outfile report.pdf --key "$(cat report.key)"
```
```
╭─────────── decrypted & verified ───────────╮
│ report.pdf (48.2 KB recovered) │
╰─────────────────────────────────────────────╯
```
密钥错误或文件被修改都会安全失败:`error decryption failed: wrong key or the file was modified`(退出代码 1)。
### `inspect` — 无需密钥检查信封
```
lockbox-lane inspect --infile report.lockbox
```
```
╭──────────────── envelope ─────────────────╮
│ version 1 │
│ iv 12 bytes │
│ ciphertext 64.3 KB │
│ plaintext (est.) 64.3 KB │
╰────────────────────────────────────────────╯
```
### `list` — 显示已存储的 blob
```
lockbox-lane --db shares.sqlite3 --storage uploads list
```
```
stored encrypted blobs
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
┃ id ┃ size ┃ downloads ┃ status ┃ expires ┃ created ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│ H_6JPLrOrlFncmO-OIVCsQ │ 4 B │ 1/1 │ exhausted │ — │ 2026-07-05T07:56:12 │
│ 6D3tLVnKuJ2fJkVUkzTvmA │ 6 B │ 0 │ active │ — │ 2026-07-05T07:56:12 │
└────────────────────────┴──────┴───────────┴─────────┴─────────┴─────────────────────┘
```
状态采用颜色编码:`active`(绿色)、`expired`(红色)、`exhausted`(黄色)。
`list --json` 输出原始行以供脚本使用。
### `purge` — 删除已存储的 blob
```
lockbox-lane purge --expired # safe GC: only expired/exhausted blobs
lockbox-lane purge # ALL blobs; prompts for confirmation
lockbox-lane purge --yes # ALL blobs, no prompt (for scripts)
```
删除*所有* blob 需要交互式确认 `yes`,或使用 `--yes`。在 `--json`/非交互模式下,如果没有
`--yes` 将会拒绝执行,而不是进行猜测。
### `serve` — 运行分享 Web 应用
```
lockbox-lane serve --host 127.0.0.1 --port 5068
```
```
╭──────────────────── serving ─────────────────────╮
│ http://127.0.0.1:5068 │
│ db lockbox_lane.sqlite3 │
│ storage uploads │
│ ciphertext is stored at rest; keys never reach the │
│ server. Ctrl+C to stop. │
╰────────────────────────────────────────────────────╯
```
打开 URL,选择一个文件(可选择设置过期时间和/或下载上限),点击 **Encrypt and
upload**(加密并上传),然后复制生成的分享链接。在任何标签页或浏览器中打开该链接,然后点击
**Decrypt and download**(解密并下载)。
## 安全与授权
- **默认仅限本地回环。** `serve` 绑定到 `127.0.0.1`。除非你传递 `--allow-public` 并承认你已获授权在该
网络上暴露它,否则将拒绝绑定任何非本地回环地址(例如 `0.0.0.0`)。`--debug`(它会启用 Werkzeug 调试器——这对任何能访问它的人来说等同于远程代码执行)在本地回环之外将被完全拒绝。
- **在任何实际部署中使用 HTTPS。** 本地 HTTP 仅用于实验室测试。请在前面放置一个 TLS 终结
反向代理,否则密文和 IV 将明文传输(在没有
密钥的情况下依然无法读取,但完整性/元数据需要 TLS 保护)。
- **将输入视为敌意内容。** 上传的 IV 在
存储之前会被验证为纯净的 12 字节 base64url(稍后它们会被回显到响应头中)。信封文件会经过防御性解析。
数据库中的 `stored_name` 受到保护以防止路径遍历。上传受到大小限制
(`LOCKBOX_LANE_MAX_UPLOAD_MB`)并按 IP 进行速率限制。
- **响应加固。** 每个响应都设置了 `Content-Security-Policy`(包含每次请求的脚本
nonce——没有 `unsafe-inline` 脚本)、`Referrer-Policy: no-referrer`(这样分享 URL 就永远不会泄露)、
`X-Content-Type-Options: nosniff`、`X-Frame-Options: DENY`,以及针对
密文的 `Cache-Control: no-store`。
- **元数据列表默认仅限本地回环**——远程调用者会收到 `404`,因此他们无法
枚举每一个分享 id。仅在你了解
暴露风险的情况下才启用 `LOCKBOX_LANE_ENABLE_LISTING=true`。
- **机密信息不会进入日志。** 密钥最多只打印一次(或写入 `0600` 权限的文件)。没有任何内容
会记录密钥,并且当密钥被写入文件时,`--json` 会完全隐藏该密钥。
- **此工具*无法*防范的情况:** 被攻破的端点、由
恶意主机提供的恶意 JavaScript,或任何你手动将完整分享 URL 交给的人。端到端加密将信任从
服务器转移到了端点和持有链接的人身上——它并没有消除信任。
### 配置(环境变量 / `.env`)
| 变量 | 默认值 | 含义 |
| --- | --- | --- |
| `LOCKBOX_LANE_DB` | `lockbox_lane.sqlite3` | SQLite 元数据库路径。 |
| `LOCKBOX_LANE_STORAGE` | `uploads` | 加密 blob 目录。 |
| `LOCKBOX_LANE_HOST` / `LOCKBOX_LANE_PORT` | `127.0.0.1` / `5068` | `serve` 绑定地址。 |
| `LOCKBOX_LANE_MAX_UPLOAD_MB` | `100` | 最大上传大小。 |
| `LOCKBOX_LANE_MAX_EXPIRY_MIN` | `43200` (30 天) | 客户端可请求的过期时间上限。 |
| `LOCKBOX_LANE_UPLOAD_RATE` / `LOCKBOX_LANE_DOWNLOAD_RATE` | `30` / `240` | 每个 IP 每分钟的请求数。 |
| `LOCKBOX_LANE_ENABLE_LISTING` | `false` | 向远程调用者公开 `GET /api/files`。 |
## 开发与测试
```
cd lockbox-lane
PYTHONPATH=src python -m pytest -q
```
测试快速且依赖极少——没有网络请求,没有 sleep。它们涵盖了加密往返和
故障模式、密钥/IV/信封验证、过期和阅后即焚语义、路径遍历
防护,以及 Flask 应用(通过 `test_client`):IV 验证、安全头、耗尽分享时的 `410` 错误,
以及仅限本地回环的列表门禁。
快速手动冒烟测试:
```
echo "the eagle lands at noon" > sample.txt
lockbox-lane encrypt-file --infile sample.txt --outfile sample.lockbox --key-out sample.key
lockbox-lane inspect --infile sample.lockbox
lockbox-lane decrypt-file --infile sample.lockbox --outfile sample.out --key "$(cat sample.key)"
lockbox-lane serve --host 127.0.0.1 --port 5068
```
## 🏷️ 为什么叫“Lockbox Lane”?
就像一排保险箱所在的街道,只有你掌握唯一的钥匙。加密在你的浏览器中进行,在任何数据离开你的机器之前就已经完成,因此服务器存储的是它确实无法打开的密文。这是“我们不会查看你的文件”唯一诚实的版本——它不是隐私政策中的承诺,而是一种在架构上根本无法进行查看的设计。
## 🔬 这个工具是如何构建的
**目的。** 一个值得信赖的端到端加密文件投递处,密钥永远不会
触及服务器——现在支持阅后即焚 / 过期分享以及
纵深防御,因此服务器确实只是一个密文储物柜。
**工作原理。** `encrypt` 生成密文信封和密钥;服务器
只负责存储和提供信封。下载操作以原子方式强制执行每个分享的过期时间和
下载上限,一旦分享过期或耗尽,就会返回 HTTP 410。
`keygen` 通过带外方式预共享密钥;`inspect` 在没有
密钥的情况下读取信封结构;`purge --expired` 对过期的 blob 进行垃圾回收。
**出色的 CLI。** 采用颜色编码的 `list` 表格(active=绿色,expired=红色,
exhausted=黄色),显示人类易读的大小和已用/上限下载量,每个
子命令的结果面板,没有 `--force` 拒绝覆盖,以及在执行破坏性
`purge` 时进行交互式确认(使用 `--yes` 用于自动化)。
**主要改进。** *功能性:* 每个分享的 `--minutes` 过期时间和下载
上限以原子方式执行,实现真正的阅后即焚;`keygen` 和 `inspect`
子命令;SQLite schema 可就地自行迁移旧的 v1 数据库。*安全性:*
`serve`绑定到本地回环,如果没有 `--allow-public` 则拒绝非本地回环,并且
调试器在本地回环之外被完全禁用;默认情况下仅限本地回环的元数据列表(关闭了
分享 id 枚举 / 密文收集漏洞);严格的 12 字节 base64url IV
验证,然后再将其回显到响应头中(阻止 header 走私);基于数据库存储名称的
路径遍历防护;基于 nonce 的 CSP,
针对密文的 `Cache-Control: no-store`,按 IP 进行速率限制;密钥最多打印
一次或写入 `0600` 权限的文件。
**示例。**
```
PYTHONPATH=src python -m lockbox_lane.cli list
```
## ⚖️ 授权使用与安全章程
**这些是用于你拥有或被明确授权评估的系统、文件、网络和人员的防御性工具。**
在运行任何东西之前请阅读本文:
- **授权不是可选的。** 网络钓鱼模拟、网络扫描、IP
信誉查询和 Web 应用探测都会触及他人的系统或
数据。请先获得书面授权。在
你声明授权之前,一些工具*拒绝执行*(`--yes`、`--authorized-training`、`--i-have-authorization`、
`--i-am-authorized` 等)。
- **默认安全。** 每个 Web UI 都绑定到 `127.0.0.1`(本地回环)。对外请求、
实时发送和主动扫描都受到显式标志的控制——试运行、
被动模式,或者拒绝并发出警告始终是默认设置。
- **没有意外的“自毁”操作。** Flask `--debug`(Werkzeug 交互式
调试器在任何 traceback 上都具备远程代码执行能力)会受到强烈警告,并且
在本地回环之外被完全拒绝。不受信任的输入受到大小限制、验证并
进行无危害渲染,因此带有恶意的文件或日志行不会导致崩溃——或接管——
你的终端。
- **输出中不含机密信息。** API 密钥保留在请求头中,密码来自
环境变量,任何敏感信息都不会被记录或打印。
这些不是攻击性工具。它们不包含任何漏洞利用、凭据
收集器或任何 payload。如果某个工具*可能*被滥用,那么在设计上它就具备了抵抗这种滥用的能力。
## 🧪 开发
```
python -m venv .venv && source .venv/bin/activate
pip install -e .
pytest -q # 18 tests, no network, no sleeps
lockbox-lane --help
```
## 📄 许可证
基于 **MIT License** 发布。详见 [LICENSE](LICENSE)。
防御性工具。没有漏洞利用,没有 payload,没有凭据收集器。
标签:Python, 客户端加密, 文件共享, 文档结构分析, 无后门, 端到端加密, 逆向工具