编程 croc 深度解剖:三个英文单词就能安全传文件——PAKE 密钥交换、Relay 中继与断点续传的工程真相

2026-07-25 03:14:23 +0800 CST views 8

croc 深度解剖:三个英文单词就能安全传文件——PAKE 密钥交换、Relay 中继与断点续传的工程真相

本周 GitHub Trending 上,schollz/croc 这个"老项目"再次上榜。一个 2018 年就发布的命令行文件传输工具,为什么在 2026 年还能持续吸引开发者?因为它用最朴素的方式解决了一个至今没有"标准答案"的问题:如何在任意两台电脑之间,安全、快速、不折腾地传一个文件。

一、背景:传文件这件小事,为什么一直没做好

先做个灵魂拷问。现在让你把一个 2GB 的文件从公司的 Linux 服务器传到家里的 Mac,你会怎么做?

盘点一下常见方案的痛点:

  • scp / rsync:需要 SSH 打通,意味着目标机器要有公网 IP 或者做端口转发。两台都在 NAT 后面的家用设备?直接歇菜。
  • 微信 / 网盘:文件大小限制、上传再下载的双倍等待、隐私焦虑三连击。你的文件会在别人的服务器上躺多久,没人告诉你。
  • U 盘:物理距离限制,而且 2026 年了,谁的笔记本还有那么多 USB-A 口。
  • python -m http.server:明文传输,局域网还凑合,跨网段就得配一堆东西,而且没有认证——同一个网络里谁都能下载。
  • magic-wormhole:思路很好,但 Python 依赖链让它在很多环境下安装体验并不轻快,传输速度也常被诟病。

croc 的答案简单到令人发指:

# 发送端
$ croc send data.tar.gz
Sending 'data.tar.gz' (2.1 GB)
Code is: cabinet-rodeo-mayday

# 接收端(世界上任何角落的另一台电脑)
$ croc cabinet-rodeo-mayday
Accept 'data.tar.gz' (2.1 GB)? (Y/n)

三个英文单词,就是全部。没有 IP 地址、没有端口转发、没有账号注册、没有配置文件。而在这份"简单"背后,藏着一整套精巧的密码学和网络工程设计:PAKE 口令认证密钥交换、Relay 中继架构、多路复用 TCP、分块哈希断点续传

这篇文章我们把 croc 从头到尾拆开,看看一个"小工具"是如何把安全性和易用性同时做到位的——以及它的设计思想能给我们自己的系统设计带来什么启发。

二、核心问题:弱口令如何换来强加密

croc 最反直觉的地方在于:cabinet-rodeo-mayday 这种三个单词的口令,熵值撑死也就三四十个比特,按传统密码学观点属于"弱得不能再弱"的密码。用它直接派生加密密钥,离线暴力破解分分钟教你做人。

那 croc 凭什么敢宣称"端到端加密"?答案是 PAKE(Password-Authenticated Key Exchange,口令认证密钥交换)

2.1 PAKE 解决的是什么问题

先看没有 PAKE 的世界会发生什么。假设发送方和接收方直接用口令的哈希当 AES 密钥:

  1. 中间人(比如 Relay 服务器本身)截获密文;
  2. 拿一个词表(croc 的助记词表是公开的)离线穷举三单词组合;
  3. 每个候选口令派生一个密钥试着解密,成功即破解。

三个单词的组合空间大约在 2^30 ~ 2^40 之间,对 GPU 来说是午饭前的工作量。弱口令 + 可离线验证 = 必然被破解。

PAKE 的核心思想是切断"离线验证"这条路:

弱口令不直接参与加密,而是作为双方交互式密钥协商过程中的一个混入因子。协商产生的会话密钥是高熵的(比如 256 位),而任何没有参与实时交互的第三方,即使拿到全部通信内容,也无法离线验证任何一个口令猜测。

换句话说:攻击者想试一个口令,就必须真的和其中一方跑一次完整的在线协商。而 croc 的房间机制决定了——猜错一次,房间就没了(传输失败,发送方需要重新生成口令)。在线攻击的成本从"每秒亿次"直接掉到"每次传输一次",弱口令瞬间变得实用且安全。

2.2 croc 的 PAKE 实现

croc 使用作者自己维护的 schollz/pake 库,这是一个基于椭圆曲线的 SPAKE2 风格实现(支持 SIEC、P-256、P-384、P-521 等曲线)。协商过程可以简化理解为:

发送方 (P)                              接收方 (Q)
  |                                        |
  |  持有弱口令 w = "cabinet-rodeo-mayday"  |  持有相同的 w
  |                                        |
  |  生成随机数 a,计算 X = aG + w·M        |
  |  ---------------- X ----------------> |
  |                                        |  生成随机数 b,计算 Y = bG + w·N
  |  <--------------- Y ------------------ |
  |                                        |
  |  K = a(Y - w·N) = abG                  |  K = b(X - w·M) = abG
  |                                        |
  |  双方各自派生出相同的高熵会话密钥 K       |

其中 M、N 是协议约定的公开曲线点,G 是基点。关键性质在于:

  • 口令 w 被"揉进"了椭圆曲线点运算,中间人看到的 X、Y 对每一个候选口令都"看起来同样合理",无法作为验证依据;
  • 只有双方口令一致时,各自算出的 K 才相同;口令不一致,协商得到的是两个不同的密钥,后续通信直接解密失败;
  • 最终会话密钥 K 的强度取决于随机数 a、b(256 位级别),与口令的弱熵完全解耦。

拿到会话密钥后,croc 用它对所有后续数据(文件元信息、文件内容、控制消息)做对称加密。Relay 服务器全程只能看到密文——它知道"有两个人在传东西"和流量大小,但既不知道文件名,也不知道内容。

2.3 一个值得抄的设计:口令即身份

传统方案里,"建立安全信道"和"确认对方身份"是两件事(TLS 证书负责前者,账号密码负责后者)。croc 用一个口令同时解决了两件事:

  • 口令匹配 → 密钥协商成功 → 信道安全
  • 口令是发送方线下(微信、口头、便签条)告诉接收方的 → 身份确认(拿到口令的人就是我要传的人)。

这个"带外传递短口令,换取带内强安全"的模式,非常适合一次性、点对点、无账号体系的场景。如果你在设计设备配对、临时会话授权这类功能,PAKE 是比"6 位数字验证码 + 服务端比对"更优雅、更安全的方案——后者服务端知道验证码,前者服务端什么都不知道。

三、架构分析:Relay 中继是怎么工作的

3.1 为什么需要 Relay

理想世界里两台电脑应该直连(P2P),但现实是绝大多数设备都躲在 NAT 后面。NAT 穿透(STUN/TURN/打洞)是出了名的"玄学"——运营商级 NAT、对称型 NAT 面前,打洞成功率并不乐观,WebRTC 那一整套 ICE 机制的复杂度就是明证。

croc 做了一个务实的取舍:不赌打洞,默认走中继;局域网内则自动直连。

3.2 Relay 的房间模型

croc 的 Relay 服务器(默认是官方的 croc.schollz.com,主端口 9009)本质上是一个"加密管道对接器",工作流程如下:

sender                  relay                   receiver
  |                       |                        |
  |--- 连接,注册 room --->|                        |
  |    (room id 由口令     |                        |
  |     派生,非明文口令)   |                        |
  |                       |<--- 持相同口令连接 -----|
  |                       |     定位到同一 room     |
  |                       |                        |
  |                       |  将两条 TCP 连接        |
  |                       |  pipe 对接              |
  |                       |                        |
  |<===== PAKE 协商 =====>|<===== (透传) =========>|
  |<===== 加密传输 ======>|<===== (透传) =========>|

几个工程细节值得注意:

  1. 房间定位不暴露口令。用于匹配房间的标识由口令派生(口令的前缀/哈希),Relay 拿不到能用于离线攻击的完整口令语料——真正的口令验证发生在端到端的 PAKE 协商中。
  2. Relay 是无状态的哑管道。它不解析协议内容,只负责把两条 TCP 连接的字节流互相转发。这让 Relay 的实现极其简单(几百行 Go 代码),也让水平扩展变得容易。
  3. 全双工中继 vs 上传后下载。网盘模式是"先完整上传,再完整下载",总耗时 = 上传时间 + 下载时间;croc 的中继是实时管道,发送和接收同时进行,总耗时 ≈ max(上行, 下行)。对大文件来说这几乎是 2 倍的体感差距。

3.3 多端口并行:朴素但有效的提速

croc 默认会在 Relay 的多个端口(9009-9013)上建立多条 TCP 连接并行传输数据块。这是个非常"接地气"的优化:

  • 单条 TCP 连接的吞吐受制于 窗口大小 / RTT,在高延迟链路(跨国中继)上很难跑满带宽;
  • 多条并行连接近似线性地提升了带宽利用率,等于绕开了单流拥塞控制的保守性;
  • 实现上不需要 QUIC/BBR 这些"重武器",纯标准库 TCP 就能落地。

官方给出的对比数据是:得益于压缩和多路复用,croc 比 magic-wormhole、rsync、scp 快 1.5x 到 4x。当然这个数字取决于链路条件,但"多流并行 + 流式压缩"的组合拳在高延迟场景下的收益是实打实的。

3.4 局域网发现:能直连绝不绕路

如果发送方和接收方在同一个局域网,croc 会通过本地广播做 peer 发现,直接建立本地连接,完全不经过外网 Relay。这个细节的价值在于:

  • 内网传输跑满千兆/万兆网卡,速度碾压任何走公网的方案;
  • 数据不出内网,满足很多企业的合规要求;
  • 对用户完全透明——命令一模一样,croc 自动选择最优路径。

"同一套交互,自动选择最优传输路径",这个设计比"让用户自己判断该用哪个工具"高明得多。

3.5 断点续传:分块哈希的幂等设计

传输 10GB 文件到 97% 时网断了,是最让人血压飙升的时刻。croc 的断点续传机制:

  1. 文件被切分为固定大小的块(chunk);
  2. 重传时,接收方对已落盘的部分做哈希比对,告知发送方"我已经有哪些块";
  3. 发送方只发送缺失的块。

本质上这是一个幂等的增量同步协议——重复执行同一次传输,已完成的部分不会重做。配合 --resume 的语义,网络抖动从"灾难"降级为"稍等片刻"。

四、代码实战

4.1 安装与基本使用

croc 是单二进制、零依赖、全平台(Windows/Linux/macOS/BSD,甚至 Android Termux):

# macOS
brew install croc

# Linux 一键安装
curl https://getcroc.schollz.com | bash

# Windows
winget install schollz.croc

# Go 用户
go install github.com/schollz/croc/v10@latest

日常用法速查:

# 发送文件/文件夹
croc send report.pdf
croc send my_project/

# 自定义口令(方便口头传达,但注意熵值)
croc send --code my-secret-2026 file.zip

# 直接发送一段文本(传个 token、URL 很方便)
croc send --text "ssh-rsa AAAAB3Nza..."

# 从管道读取
tar czf - ./logs | croc send --name logs.tar.gz -

# 接收端免确认(脚本自动化场景)
croc --yes cabinet-rodeo-mayday

# 指定接收目录
croc --out /data/incoming cabinet-rodeo-mayday

一个实用细节:接收命令里的口令位置支持环境变量 CROC_SECRET,避免口令进入 shell history:

export CROC_SECRET="cabinet-rodeo-mayday"
croc   # 不带参数,自动读取环境变量

在多人共用的服务器上,这能防止别人通过 ps aux.bash_history 看到你的传输口令——安全工具的安全性,往往败在这种边角细节上,croc 把它考虑到了。

4.2 自建 Relay:把基础设施握在自己手里

官方 Relay 是公益性质的共享资源,速度没有保证,敏感场景(尽管是密文)也未必允许流量出境。自建 Relay 只需要一台有公网 IP 的机器:

# 最简启动(默认监听 9009-9013)
croc relay

# 指定端口与传输口令(给 Relay 加一层准入控制)
croc relay --ports 9009,9010,9011,9012,9013 --pass mypassword

生产环境建议用 systemd 托管:

# /etc/systemd/system/croc-relay.service
[Unit]
Description=croc relay server
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/croc relay --ports 9009,9010,9011,9012,9013
Restart=always
RestartSec=5
User=croc
# 基本的安全加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now croc-relay
# 防火墙放行
sudo ufw allow 9009:9013/tcp

也可以用 Docker:

docker run -d --restart=always \
  -p 9009-9013:9009-9013 \
  --name croc-relay \
  schollz/croc relay

客户端指定自建 Relay:

# 命令行指定
croc --relay "relay.example.com:9009" send big.iso

# 或者环境变量固化,团队成员一次配置永久生效
export CROC_RELAY="relay.example.com:9009"

一台 1C1G 的最低配 VPS 就能跑 Relay——因为它只做字节转发,不做加解密、不落盘,CPU 和内存开销都极低,瓶颈只在带宽。这是"哑管道"架构设计带来的直接红利。

4.3 把 croc 嵌入你自己的 Go 程序

croc 本身是一个 Go 库,可以直接嵌入自己的工具链。比如给内部运维平台加一个"安全取回服务器文件"的功能:

package main

import (
	"log"

	"github.com/schollz/croc/v10/src/croc"
	"github.com/schollz/croc/v10/src/models"
	"github.com/schollz/croc/v10/src/utils"
)

func main() {
	// 生成随机口令:1 个数字 + 3 个助记词
	secret := utils.GetRandomName()

	client, err := croc.New(croc.Options{
		IsSender:      true,
		SharedSecret:  secret,
		RelayAddress:  "relay.example.com:9009", // 自建中继
		RelayPorts:    []string{"9009", "9010", "9011", "9012", "9013"},
		RelayPassword: models.DEFAULT_PASSPHRASE,
		NoPrompt:      true, // 无人值守
		Overwrite:     true,
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("接收口令: %s", secret)

	// 收集待发送文件信息并开始传输(阻塞直到完成)
	filesInfo, emptyFolders, totalFolders, err := croc.GetFilesInfo(
		[]string{"/var/log/app/dump.tar.gz"}, false, false, []string{})
	if err != nil {
		log.Fatal(err)
	}
	if err = client.Send(filesInfo, emptyFolders, totalFolders); err != nil {
		log.Fatal(err)
	}
}

这段代码的价值场景:CI 产物分发、跨环境数据库 dump 搬运、给客户临时交付大文件——所有"不想为一次性传输搭建永久基础设施"的场合。相比自己拿 TLS + HTTP 糊一个传输服务,croc 库把密钥协商、加密、重试、续传全包了。

4.4 实战组合技

跨云迁移数据(两台服务器都无外网入站权限,只有出站):

# 云 A(源)
tar czf - /data/mysql-backup | croc send --name backup.tar.gz -

# 云 B(目标)
croc --yes <code> && tar xzf backup.tar.gz -C /data/restore

两边都只需要出站 TCP,任何安全组都拦不住。这是 croc 相对 scp 的杀手级优势:它把"网络可达性"的要求降到了最低(双方都能出站即可)

配合 cron 的定时单向同步(简易场景替代 rsync over SSH):

# 发送侧脚本:固定口令 + 无人值守
CROC_SECRET="nightly-$(date +%Y%m%d)-backup" croc --yes send /backup/daily.tar.zst

当然要提醒:可预测口令牺牲了安全性,这种用法只建议配合自建 Relay 在受控网络里使用。

五、性能与安全的边界讨论

5.1 性能:什么时候 croc 不是最优解

诚实地说,croc 不是万能的:

  • 持续同步场景:croc 是一次性传输工具,目录持续双向同步请用 Syncthing;增量备份请用 rsync/restic——它们的 rolling checksum 和快照模型是为此而生的。
  • 超高吞吐内网:万兆内网里加密开销开始显性化。确认环境可信时,--no-compress 关闭压缩(本就压不动的媒体文件还能省 CPU),极端场景下裸 nc + pv 依然是速度之王(当然是裸奔)。
  • 公共 Relay 的带宽:走官方 Relay 的跨国传输速度完全看缘分。任何认真使用的团队都应该自建 Relay,成本低到可以忽略。

压缩策略也值得一提:croc 默认启用压缩,对文本、日志、代码这类高冗余数据收益巨大;但对已压缩数据(视频、zip、加密文件)纯属浪费 CPU,记得手动关闭。"默认开启,允许关闭"是对小白友好、对专家不设限的正确默认值哲学。

5.2 安全:威胁模型里的明与暗

croc 的安全边界要讲清楚,避免神化:

防得住的:

  • 被动窃听者(包括 Relay 自己):全程密文,PAKE 保证无法离线破解口令;
  • 主动中间人:不知道口令就无法完成 PAKE 协商,协商失败连接直接中断;
  • 口令暴力猜测:在线攻击每次消耗一个房间,猜错即失效,实际攻击窗口趋近于零。

防不住的(需要用户自己兜底):

  • 口令泄露即全线失守:你把口令发在了被监控的聊天群里,那加密等于没加。口令的带外传递通道安全性是整个模型的前提;
  • 流量元数据:Relay 能看到双方 IP、传输时间和数据量。对元数据敏感的场景需要叠加 Tor/VPN(croc 支持 SOCKS5 代理:--socks5 localhost:9050);
  • 接收内容本身:croc 不做病毒扫描,接收陌生人的文件请保持基本警惕。

另外一个历史教训值得记录:croc 早期版本曾被安全研究者指出多个问题(如口令进入命令行参数可被 ps 窥探、接收文件可能覆盖本地文件等),后续版本通过 CROC_SECRET 环境变量、默认不覆盖、确认提示等机制逐一修补。这提醒我们:密码学原语正确 ≠ 工程整体安全,CLI 工具的攻击面往往在参数、历史记录、文件系统这些"不性感"的地方。

六、总结与展望

croc 值得每个工程师研究,不只因为它好用,更因为它是一个教科书级的"约束下的优雅设计"案例:

  1. PAKE 让弱口令换来强加密——把"人类可传达的短口令"和"密码学要求的高熵密钥"这对矛盾,用交互式协议漂亮地化解。这个思路适用于一切设备配对、临时授权场景。
  2. 哑管道 Relay 架构——中继只转发密文字节流,不理解协议、不持有密钥、不落盘。信任最小化的同时,运维成本也最小化。
  3. 自动路径选择——局域网直连、公网走中继,用户无感知。好工具把复杂度留给自己,把简单留给用户。
  4. 幂等的分块续传——把"传输"建模为可重入的状态同步,而不是一次性的字节搬运。
  5. 默认值哲学——默认加密、默认压缩、默认确认提示,每一个默认值都站在多数用户的最佳利益一侧,同时给专家留出开关。

展望后续,croc 所代表的"无账号、点对点、端到端加密"工具形态正在扩散:LocalSend 之于局域网、Syncthing 之于持续同步、magic-wormhole 的协议标准化尝试……在数据主权意识觉醒的当下,**"文件从我的设备直接到你的设备,中间没有任何人能看"**会从极客偏好变成大众预期。而 croc 用一个 Go 二进制证明了:实现这个预期,不需要庞大的平台,只需要正确的密码学和克制的工程。

下次要传文件的时候,试试这三个单词的魔法。


参考资料:schollz/croc GitHub 仓库与官方文档、schollz/pake 库、PAKE/SPAKE2 协议相关公开资料。

推荐文章

Vue中如何处理异步更新DOM?
2024-11-18 22:38:53 +0800 CST
Graphene:一个无敌的 Python 库!
2024-11-19 04:32:49 +0800 CST
PHP 如何输出带微秒的时间
2024-11-18 01:58:41 +0800 CST
html一些比较人使用的技巧和代码
2024-11-17 05:05:01 +0800 CST
程序员茄子在线接单