alexylon/ferrocrypt
GitHub: alexylon/ferrocrypt
一款用 Rust 编写的跨平台文件加密工具,支持对称与混合公钥两种模式,提供 CLI 和桌面界面,采用 XChaCha20-Poly1305、Argon2id 和 X25519 等现代密码学算法。
Stars: 14 | Forks: 2
FerroCrypt

[](https://crates.io/crates/ferrocrypt)
[](https://docs.rs/ferrocrypt/latest)

[](https://crates.io/crates/ferrocrypt-cli)
跨平台文件加密工具,提供 CLI 和桌面界面。使用 Rust 编写。
## 关于
FerroCrypt 以两种模式对文件和目录进行加密和解密:
- **对称** — 基于密码。使用 XChaCha20-Poly1305 结合 Argon2id 密钥派生和 HKDF-SHA3-256 子密钥扩展。使用相同的密码进行加密和解密。
- **混合** — 基于公私钥。将 X25519 密钥协商与 XChaCha20-Poly1305 数据加密相结合。每个文件获得一个唯一的随机密钥,并使用接收者的公钥进行封装。解密需要匹配的私钥及其密码。混合模式为接收者提供了机密性和完整性,但不验证发送者的身份;它不能替代数字签名。
这两种模式都会生成 `.fcr` 文件。解密基于文件头中的魔术字节,而不是文件扩展名 —— 重命名文件不会改变 FerroCrypt 对其的解析方式。
有关字节级的磁盘格式,请参见代码库中的 `ferrocrypt-lib/FORMAT.md`。
### 加密文件中存储了什么
每个 `.fcr` 文件都以文件头开头,后跟加密负载。文件头仅包含开始解密所需的元数据 —— 不会暴露文件名、时间戳或明文内容。所有文件头字段均进行三重复制以用于纠错。
| 文件 | 内容 |
|---|---|
| **对称 `.fcr`** | 格式标识符、版本、Argon2id 盐、HKDF 盐、KDF 参数(内存开销、迭代次数、并行度)、流 nonce、HMAC 身份验证标签 |
| **混合 `.fcr`** | 格式标识符、版本、密封的密钥信封(临时公钥 + 加密的随机密钥)、流 nonce、HMAC 身份验证标签 |
| **`private.key`** | KDF 参数、Argon2id 盐、AEAD nonce、经过身份验证的扩展区域、受密码加密的私钥(原始密钥从不以未加密形式存储;明文头和所有明文主体字段均绑定为 AEAD 关联数据,因此篡改将无法通过身份验证) |
| **`public.key`** | 原始 32 字节 X25519 公钥(非机密)。也可通过库 API 作为 Bech32 `fcr1…` 接收者字符串共享。 |
### 安全性
- **对称加密:** 通过 [`chacha20poly1305`](https://crates.io/crates/chacha20poly1305) crate([由 NCC Group 审计](https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/))实现 XChaCha20-Poly1305,并使用 Argon2id 密钥派生和 HKDF-SHA3-256 子密钥扩展
- **混合加密:** 通过 [`x25519-dalek`](https://crates.io/crates/x25519-dalek) 实现 X25519 ECDH 密钥协商,HKDF-SHA256 信封密钥派生,XChaCha20-Poly1305 信封加密
- HMAC-SHA3-256 文件头身份验证 —— 在负载解密开始前检测篡改;在混合模式下,首先打开信封以恢复 HMAC 密钥
- 流式加密 —— 明文直接流式传输到加密器;不会将明文临时归档写入磁盘
- 密码通过 `secrecy` crate 处理(在 `Debug`/`Display` 中隐藏,在 drop 时清零)
- 带有多数投票解码的三重复制文件头用于纠错(见下文的[为什么使用三重复制?](#why-triple-replication))
- 当前实现在 Unix 平台的加密/解密往返过程中保留了常规文件和目录的权限位。Setuid、setgid 和粘滞位在归档和提取时均被剥离。这是当前的行为,而不是跨平台兼容性的保证;在非 Unix 平台上,权限元数据可能是近似的
- 符号链接输入会被拒绝;目录加密不会跟随符号链接,防止意外包含所选树之外的文件。包含符号链接或其他特殊条目(套接字、FIFO、设备)的目录在加密时将被拒绝。硬链接将作为常规文件内容进行归档;硬链接关系不予保留。
- 目录提取针对 Linux 和 macOS 上的本地符号链接和目录组件竞争进行了强化。`.incomplete` 工作树内的每次写入均根植于目录文件描述符,并通过 `openat`/`mkdirat` 结合 `O_NOFOLLOW` 进行解析,因此并发替换路径组件为符号链接的本地攻击者无法将明文写入重定向到目标树之外。相反,提取操作会以类型化错误中止。Windows 和非 Linux/非 macOS 的 Unix 目标使用基于路径的提取方式,对树内竞争的防范较为宽松。
- 加密文件和生成的密钥文件在目标目录中以临时名称暂存,仅在成功时提升为最终名称。解密输出在 `.incomplete` 工作名称下暂存并在最后提升。失败的加密通常会清理其临时文件;失败的解密则有意保留 `.incomplete` 输出,因为当密文损坏时,其中可能包含唯一可恢复的明文。在 Linux 和 macOS 上,目录最终化使用严格的不覆盖路径;在 Windows 上,目录步骤更为保守且属于尽力而为。
- 带有魔术字节的版本化文件格式 —— 损坏或不兼容的文件会产生明确的错误
### 限制
- **文件元数据未完全保留。** FerroCrypt 始终保留文件内容和目录结构。当前实现还在 Unix 上保留了常规文件和目录的权限位,并剥离了 setuid、setgid 和粘滞位,但该行为是尽力而为,而非稳定的跨平台格式保证。FerroCrypt 不保留时间戳或所有权。在非 Unix 平台上,权限处理受平台限制,归档元数据可能是近似的。硬链接关系不予保留(硬链接文件作为独立副本归档)。符号链接和特殊条目会在加密时导致错误。目录加密是一项便利功能,而非完整的备份/归档格式。如果您需要忠实的文件系统备份/还原语义,请使用专用的归档工具并使用 FerroCrypt 加密其输出。
- **不向后兼容较旧的格式版本。** 下一个版本将使用对称加密文件格式 v3.0、混合加密文件格式 v4.0、`public.key` v3 和 `private.key` v4。这是 `main` 分支上的一个破坏性变更,尚未在已发布的 crate 版本中提供 —— 请参见 `CHANGELOG.md [Unreleased]` 和 `ferrocrypt-lib/FORMAT.md`。早期版本(crates.io 上的 v0.1.x / v0.2.x)生成文件和密钥无法被新格式系列解密或使用。这些版本使用不同的格式系列;在混合模式和密钥文件中,它们还使用不同的加密技术栈(RSA/OpenSSL)。如果您仍有使用旧版本加密的数据,请先使用该版本解密(可在 crates.io 上获得),然后在新版本发布后重新加密。
### 为什么使用三重复制?
文件头包含开始解密所需的盐、nonce 和 KDF 参数。如果文件头无法解析,工具将无法区分是密码错误、文件损坏还是不支持的格式 —— 每一种失败看起来都一样。加密负载体积更大,在不可靠的存储器上也更容易损坏,因此文件头复制通常无法拯救文件。它拯救的是**诊断信息**:通过纠正单份文件头副本的损坏,用户仍能获得具体的错误信息:
- “文件格式较新 (vX.Y)。请升级 FerroCrypt。” —— 而不是根本无法识别格式
- “FerroCrypt 文件中的未知加密类型:0xNN” —— 而不是将文件视为纯文本处理
- “文件具有无效的解密设置 (N KiB 内存)” / “解密需要 N KiB;限制为 M KiB” —— 而不是默默地分配无限内存
- “解密失败:密码错误或文件被篡改”(对称)/ “解密失败:私钥错误或文件被篡改”(混合) —— 而不是不知道是凭据错误还是文件头损坏
- “加密文件被截断” / “负载身份验证失败:数据被篡改或损坏” —— 而不是根本无法进入负载解密阶段
如果没有可读的文件头,所有这些情况都会退化为单一的通用“无效格式”错误。
该机制被故意设计得非常简单:存储每个文件头字段的三个相同副本,并逐字节选取多数值。如果三份副本中有两份一致,则恢复出正确的值。这在存储的文件头字节中提供了 33% 的容错率 —— 一份副本可能被完全破坏,而文件头仍能正确解析。
**为什么不用 Reed-Solomon?** 对于 FerroCrypt 的文件头,实现的简单性比最大化纠错能力更重要。文件头体积小且经过身份验证,只需要保持足够的可读性以识别格式、解析解密参数并生成特定的错误信息。三重复制保持了极小的实现体积且易于审计:纯字节比较,没有伽罗瓦域算术,没有多项式机制,极少出现漏洞的攻击面。
### 项目结构
| Crate | 描述 |
|---|---|
| `ferrocrypt-lib` | 核心加密库 ([crates.io](https://crates.io/crates/ferrocrypt)) |
| `ferrocrypt-cli` | CLI 二进制文件 ([crates.io](https://crates.io/crates/ferrocrypt-cli)) |
| `ferrocrypt-desktop` | 使用 [Slint](https://slint.dev/) 构建的桌面应用 |
## 安装
在 [GitHub Releases](https://github.com/alexylon/ferrocrypt/releases) 页面上提供了预构建的软件包:适用于 macOS、Linux 和 Windows 的 CLI 二进制文件,以及桌面应用程序包(macOS 的 `.app`、Debian/Ubuntu 的 `.deb`、Fedora/RHEL 的 `.rpm`、Windows 的 `.msi`)。
### CLI
从 crates.io 安装:
```
cargo install ferrocrypt-cli
```
或从源码构建:
```
cargo build --release
```
二进制输出:`target/release/ferrocrypt`(macOS/Linux)或 `target\release\ferrocrypt.exe`(Windows)。
### 桌面应用
从源码构建 —— 需要 [Rust](https://www.rust-lang.org/tools/install) 和 [cargo-bundle](https://github.com/nickelc/cargo-bundle):
```
cd ferrocrypt-desktop
cargo bundle --release # produces .app (macOS) / .deb + .AppImage (Linux) / .msi (Windows)
```
**仅限 Linux** —— 请先安装系统依赖项:
```
# Debian/Ubuntu
sudo apt update && sudo apt install libfontconfig-dev libfreetype-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libwayland-dev libssl-dev
# Fedora
sudo dnf install fontconfig-devel freetype-devel libxcb-devel libxkbcommon-devel wayland-devel openssl-devel
```
AppImage 输出还需要 `mksquashfs`。如果您只需要 `.deb`,请跳过此步骤:
```
# Debian/Ubuntu
sudo apt install squashfs-tools
# Fedora
sudo dnf install squashfs-tools
```
### 库
```
cargo add ferrocrypt
```
## Rust 版本支持
`ferrocrypt` 库 crate 当前目标是 **MSRV 1.87**。
此最低支持的 Rust 版本已在 `ferrocrypt-lib` 的 CI 中检查。
如果依赖项或语言改进需要,未来版本可能会提高此要求。
## CLI 用法
### 子命令
| 子命令 | 别名 | 用途 |
|---|---|---|
| `symmetric` | `sym` | 使用密码加密/解密 |
| `hybrid` | `hyb` | 使用公私钥加密/解密 |
| `keygen` | `gen` | 生成密钥对 |
| `fingerprint` | `fp` | 打印公钥的 SHA3-256 指纹 |
| `recipient` | `rc` | 将公钥打印为 Bech32 `fcr1…` 字符串 |
不带参数运行以启动交互式 REPL。在交互模式下可以使用别名。
密码永远不会在命令行中传递。当需要密码时(加密/密钥生成时需确认),CLI 会以隐藏输入的方式进行交互式提示。对于非交互式使用(脚本、CI),请设置 `FERROCRYPT_PASSPHRASE` 环境变量。
### 对称
```
# Encrypt (提示输入并确认密码)
ferrocrypt symmetric -i secret.txt -o ./encrypted
# Decrypt (提示输入密码)
ferrocrypt symmetric -i ./encrypted/secret.fcr -o ./decrypted
# Encrypt 自定义输出路径 (--output-path 不需要)
ferrocrypt symmetric -i secret.txt -s ./backup.fcr
```
### 混合
```
# Generate keys (提示输入并确认密码)
ferrocrypt keygen -o ./keys
# Print recipient 字符串 (用于分享)
ferrocrypt recipient ./keys/public.key
# Verify 公钥的 fingerprint
ferrocrypt fingerprint ./keys/public.key
# Encrypt 使用 recipient 字符串 (不需要密码)
ferrocrypt hybrid -i secret.txt -o ./encrypted -r fcr1...
# Encrypt 自定义输出路径 (--output-path 不需要)
ferrocrypt hybrid -i secret.txt -s ./secret.fcr -r fcr1...
# Encrypt 使用公钥文件 (不需要密码)
ferrocrypt hybrid -i secret.txt -o ./encrypted -k ./keys/public.key
# Decrypt 使用私钥 (提示输入密码)
ferrocrypt hybrid -i ./encrypted/secret.fcr -o ./decrypted -k ./keys/private.key
```
### 交互模式
```
$ ferrocrypt
FerroCrypt interactive mode
Commands: symmetric (sym), hybrid (hyb), keygen (gen), fingerprint (fp), recipient (rc), quit
ferrocrypt> sym -i secret.txt -o out
Passphrase:
Confirm passphrase:
ferrocrypt> quit
```
### 标志参考
#### `symmetric`
| 标志 | 描述 |
|---|---|
| `-i, --input-path` | 输入文件或目录 |
| `-o, --output-path` | 输出目录(使用 `--save-as` 时可选) |
| `-s, --save-as` | 自定义输出文件路径(仅限加密) |
| `--max-kdf-memory` | 接受的最大 KDF 内存开销,以 MiB 为单位(仅限解密) |
#### `hybrid`
| 标志 | 描述 |
|------|
| `-i, --input-path` | 输入文件或目录 |
| `-o, --output-path` | 输出目录(使用 `--save-as` 时可选) |
| `-k, --key` | 密钥文件路径:加密时为公钥,解密时为私钥 |
| `-r, --recipient` | 用于加密的 Bech32 接收者字符串 (`fcr1...`) |
| `-s, --save-as` | 自定义输出文件路径(仅限加密) |
| `--max-kdf-memory` | 接受的最大 KDF 内存开销,以 MiB 为单位(仅限解密) |
#### `keygen`
| 标志 | 描述 |
|---|---|
| `-o, --output-path` | 密钥对的输出目录 |
#### `fingerprint`
| 参数 | 描述 |
|---|---|
| `
` | 公钥文件路径 |
#### `recipient`
| 参数 | 描述 |
|---|---|
| `` | 公钥文件路径 |
## 桌面应用用法
选择文件或文件夹,然后选择加密模式。应用程序通过读取文件头来自动检测加密文件,无论其扩展名如何。
- **对称** — 输入密码。输出路径将自动填充为 `{name}.fcr`,并可通过“Save As”进行更改。解密使用目录选择器。
- **混合** — 使用现有公钥进行加密,或内联创建新密钥对。生成密钥后,应用程序将切换到加密模式,并预填充新的公钥。接收者的公钥指纹会与复制按钮一同显示,用于带外验证。密钥文件在选择时会进行验证,无效文件会在操作开始前显示错误。解密需要私钥及其密码。
在加密和密钥生成期间会显示密码强度指示器(基于 [Proton Pass](https://github.com/protonpass/proton-pass-common) 实现)。
## 解密错误
FerroCrypt 区分了几个不同的解密失败阶段,以便失败的解密过程能告诉您实际出了什么问题:
- **私钥解锁失败:密码错误或文件被篡改** — 混合私钥文件未通过 AEAD 身份验证。要么是密码无法解密它,要么是文件的某个明文字段(文件头、KDF 参数、盐、nonce 或扩展区域)在文件写入后被篡改了。AEAD 原语无法区分这两种情况。请先使用正确的密码重试;如果仍然失败,请重新生成密钥对并重新加密。
- **解密失败:密码错误或文件被篡改**(对称) — 密码无法解锁文件,或者文件头已被修改。未生成任何明文。
- **解密失败:私钥错误或文件被篡改**(混合) — 提供的私钥与文件加密所用的密钥对不匹配,或者文件头已被修改。未生成任何明文。
- **负载身份验证失败:数据被篡改或损坏** — 文件头身份验证成功,但随后的密文块未通过其身份验证标签。这通常意味着文件在中途损坏或被截断,或者攻击者修改了文件头之后的字节。在流式解密期间,在到达失败块之前,成功通过身份验证的早期块可能已经被写入磁盘上的 `.incomplete` 工作目录中。
- **加密文件被截断** — 加密流在其最终身份验证块之前结束,通常是因为下载不完整或复制中断。
这些失败都不会在最终输出路径上生成文件。部分明文可能已被写入同级 `.incomplete` 工作目录下(FerroCrypt 故意将其留在那里,因为当密文损坏时,它可能包含唯一可恢复的数据)。一旦该 `.incomplete` 目录被移除,重新尝试将会重新开始。
## 致谢
桌面应用使用 [Slint](https://slint.dev/) 构建。
密码强度评分改编自 [Proton Pass](https://github.com/protonpass/proton-pass-common) (GPLv3)。
[](https://forthebadge.com) 标签:Argon2id, FerroCrypt, GUI桌面应用, HKDF-SHA3-256, ProjectDiscovery, Rust, X25519, XChaCha20-Poly1305, 公钥加密, 加密工具, 可视化界面, 密码加密, 密码学, 密钥派生, 对称加密, 开源加密软件, 手动系统调用, 数据保护, 文件加密, 混合加密, 目录加密, 私钥解密, 网络安全, 网络安全, 网络流量审计, 自动化审计, 蓝队防御, 解密工具, 通知系统, 隐私保护, 隐私保护