rclone cryptcheck 命令详解:验证加密远端数据完整性的原理、参数与实战
rclone cryptcheck 是 rclone 为 crypt 加密远端(encrypted remote)专门提供的完整性校验命令,相当于能够校验密文校验和的 rclone check。本文以 cryptcheck 命令参考文档 为主体,结合 cmd/cryptcheck/cryptcheck.go、backend/crypt/crypt.go 与 fs/operations/check.go 的源码实现,讲解它的核心原理、两种调用形态、全部校验选项与结果解读,帮助你在“明文数据 ↔ 加密远端”之间做出不下载全量数据的高效完整性校验。
为什么普通 rclone check 校验不了加密远端
rclone crypt 是一种“覆盖型后端”(overlay backend),它本身不直接访问存储,而是把加解密逻辑叠加在任意其他后端之上。文件在存储端落盘的是密文,文件名和文件内容都已发生变换。
普通 rclone check 的比较逻辑是:在源与目标之间取公共支持的哈希做比对。而 crypt 后端对外暴露的 Hashes() 返回的是空集合(见 backend/crypt/crypt.go#L579-L582),因为加密后的密文哈希与用户掌握的明文哈希根本对不上——云端存储的是密文,它的校验和自然也是针对密文计算的。
因此,rclone 在 crypt 远端之外专门实现了 cryptcheck 命令,其定位就是“等价于 rclone check,但能够校验加密远端的校验和”。该命令自 v1.36 引入,命令源文件为 cmd/cryptcheck/cryptcheck.go。
工作原理:读取 nonce → 原地重加密 → 比对密文哈希
cryptcheck 之所以能工作,依赖于一个巧妙的设计:加密文件的头部保存了本次加密所使用的 nonce(随机数)。只要拿到这个 nonce,就能用同一把密钥、同一个 nonce,把本地明文重新加密一遍,从而在本地“复现”云端密文的字节流,再计算它的哈希与云端存储的哈希比对。
完整校验逻辑位于 cmd/cryptcheck/cryptcheck.go#L66-L116 的 cryptCheck 函数,步骤可以拆解如下:
1. 校验目标确实是 crypt 远端,并选定哈希算法
fcrypt, ok := fdst.(*crypt.Fs)
if !ok {
return fmt.Errorf("%s:%s is not a crypt remote", fdst.Name(), fdst.Root())
}
funderlying := fcrypt.UnWrap()
hashType := funderlying.Hashes().GetOne()
if hashType == hash.None {
return fmt.Errorf("%s:%s does not support any hashes", ...)
}
fs.Infof(nil, "Using %v for hash comparisons", hashType)
- 第二个参数
encryptedremote:path必须是crypt类型远端,否则直接报错is not a crypt remote; - 通过
UnWrap()取得被加密包装的下层后端,并要求下层后端至少支持一种校验和(GetOne()会从支持列表中选一种),否则报does not support any hashes; - 命令启动时会打印
Using MD5 for hash comparisons之类的日志,告知本次采用哪种哈希。
2. 从加密文件头部读出 nonce
在校验函数中,目标对象被断言为 *crypt.Object 并解包成底层密文对象,取其存储的哈希作为“云端真实值”;同时调用 fcrypt.ComputeHash(...) 计算“本地重加密值”。关键实现在 backend/crypt/crypt.go#L811-L852:
- 使用
RangeOption{Start: 0, End: int64(fileHeaderSize) - 1}对密文对象做有限范围的读取,只下载文件头部区域; - 通过解密器读取该头部中的 nonce(注释中明确写道:“opening the file is sufficient to read the nonce in / use a limited read so we only read the header”);
- 若配置了
NoDataEncryption(仅加密文件名、不加密内容),则直接返回源文件的哈希,无需重加密。
这意味着在绝大多数文件上,cryptcheck 并不需要把加密远端上的文件整体下载下来——只读头部那几十字节即可取得 nonce。
3. 用该 nonce 重加密本地明文并即时算哈希
得到 nonce 后,computeHashWithNonce(backend/crypt/crypt.go#L780-L809)打开明文源文件,用 cipher.newEncrypter(in, &nonce) 把数据边加密边灌入多哈希器,最终得到本地“复现密文”的哈希值。
源码注释坦诚地写道:“Note that we break lots of encapsulation in this function.”——为了在外部对象上重放加密流程,这个函数特意打破了大量封装,是理解
cryptcheck底层原理时最值得研读的一段代码。
4. 两值比对并汇入统一报表框架
本地重加密哈希与云端密文哈希不一致时,返回差异;一致则计为匹配。整个流程交由通用框架 operations.CheckFn(fs/operations/check.go#L212-L238)驱动:它对源与目标做“march”式目录对拍,逐对对象先比较大小(比较的是逻辑明文层的尺寸),再调用 cryptcheck 注入的自定义哈希比对闭包,最后统一汇总统计。
一句话概括整个调用链:
rclone cryptcheck → cryptCheck(类型断言 + 选哈希)→ operations.CheckFn(march 对拍调度)→ 注入式 Check 闭包(UnWrap + 读头部 nonce + ComputeHash 重加密比对)。
基本用法:两种调用形态与适用场景
形态一:本地目录 vs 加密远端(推荐)
rclone cryptcheck /path/to/files encryptedremote:path
源侧是本地明文目录,目标侧是加密远端。此时校验只读取加密远端每个文件的头部 nonce,再对本地文件做一次流式重加密,源文件由本地磁盘读取,因此传输量最小、性能最好。
形态二:远端明文 vs 加密远端
rclone cryptcheck remote:path encryptedremote:path
源侧也可以是另一个远端上的明文数据。正如官方文档所强调,这种用法需要把 remote:path 下的所有文件先下载到本地才能完成重加密,因此会产生全量下载流量。只有当明文侧确实存放在远端时才应选用。
参数位置约定
命令固定接收两个参数:rclone cryptcheck source:path cryptedremote:path。从 cmd/cryptcheck/cryptcheck.go#L57-L63 的 Run 实现可看到,cmd.CheckArgs(2, 2, ...) 强制要求恰好两个参数,并且第二个参数必须是 crypt 远端(见上文第 1 步的类型断言)。
运行结束后,cryptcheck 会汇总并记录 encryptedremote: 的校验状态,例如匹配文件数、发现的差异数、缺失文件数等。
差异报告输出选项:把结果写到文件或 stdout
cryptcheck 继承自通用 check 命令的一批报表选项由 cmd/check/check.go#L42-L50 的 AddFlags 注册。这些选项接收一个文件名作为值;若值为 -,则输出到 stdout。对应实现见 cmd/check/check.go#L79-L135 的 GetCheckOpt:文件名为空时跳过,为 - 时绑定 os.Stdout,否则 os.Create 创建报告文件并在结束时统一关闭。
| 选项 | 作用 |
|---|---|
--differ string |
把所有“源与目标都存在但内容不同”的文件路径写入该文件 |
--missing-on-dst string |
把“目标中缺失(只在源中)”的文件路径写入该文件 |
--missing-on-src string |
把“源中缺失(只在目标中)”的文件路径写入该文件 |
--match string |
把所有匹配一致的文件路径写入该文件 |
--error string |
把所有“读取或哈希出错”的文件路径写入该文件 |
--combined string |
生成一个包含全部文件路径及状态符号的组合报告 |
--one-way |
仅单向校验:只检查源文件在目标中是否存在且一致 |
--combined:diff 风格的符号化报告
--combined 生成的文件格式类似 diff 输出,每行由一个符号、一个空格和文件路径组成,符号含义如下:
| 符号 | 含义 |
|---|---|
= path |
该路径在源和目标中都存在,且完全一致 |
- path |
该路径在源中缺失(只存在于目标中) |
+ path |
该路径在目标中缺失(只存在于源中) |
* path |
该路径在源和目标中都存在,但内容不同 |
! path |
读取或哈希该路径的源/目标时发生错误 |
生成逻辑集中在 fs/operations/check.go#L64-L76 的 report 方法:每个状态既写入对应的单项报告文件(如 --differ、--missing-on-src),也会同步写入 --combined 指定的输出。因此这些选项可以任意组合使用,一份符号报告 + 若干份明细清单即可完整呈现校验结果。
--one-way:单向校验
默认情况下 check 系命令做的是“双向”校验:源中多出的文件(missing-on-dst)与目标中多出的文件(missing-on-src)都会被视为差异。这对“两边本应完全镜像”的场景是合适的。
但如果你只是想确认“源里每个文件在目标里都完好”,可以加 --one-way 只做单向检查:只检查源文件在目标中是否匹配,目标中额外多出的文件不会被报告。在 fs/operations/check.go#L78-L101 的实现中,DstOnly 分支遇到 OneWay 为真时直接返回不再递归,这正是“忽略目标多余文件”行为的来源。
全部选项参考
cryptcheck 的帮助信息由三组选项组成。cryptcheck 专有选项之外,其余为与其它命令共享的选项,全局选项见 flags 全局帮助。
cryptcheck 专有选项
--combined string Make a combined report of changes to this file
--differ string Report all non-matching files to this file
--error string Report all files with errors (hashing or reading) to this file
-h, --help help for cryptcheck
--match string Report all matching files to this file
--missing-on-dst string Report all files missing from the destination to this file
--missing-on-src string Report all files missing from the source to this file
--one-way Check one way only, source files must exist on remote
注意:与 rclone check 不同,cryptcheck 没有 --download(下载全量内容比对)和 --checkfile 等选项,因为它的校验方式本身就是“重加密哈希”,不需要也不应该走全量下载比对。
Check Options
--max-backlog int Maximum number of objects in sync or check backlog (default 10000)
该选项控制同步或校验过程中待处理对象队列的最大长度(默认 10000),当源目录中对象数量巨大、而 --checkers 并行处理跟不上时,用于防止内存中的待办对象无限增长。
Filter Options
这些过滤选项用于裁剪参与校验的文件集,例如只校验特定子集、排除某种模式或按文件年龄筛选:
--delete-excluded Delete files on dest excluded from sync
--exclude stringArray Exclude files matching pattern
--exclude-from stringArray Read file exclude patterns from file (use - to read from stdin)
--exclude-if-present stringArray Exclude directories if filename is present
--files-from stringArray Read list of source-file names from file (use - to read from stdin)
--files-from-raw stringArray Read list of source-file names from file without any processing of lines (use - to read from stdin)
--files-from0 stringArray Read list of source-file names from file using NUL as separator (use - to read from stdin)
-f, --filter stringArray Add a file filtering rule
--filter-from stringArray Read file filtering patterns from a file (use - to read from stdin)
--hash-filter string Partition filenames by hash k/n or randomly @/n
--ignore-case Ignore case in filters (case insensitive)
--include stringArray Include files matching pattern
--include-from stringArray Read file include patterns from file (use - to read from stdin)
--max-age Duration Only transfer files younger than this in s or suffix ms|s|m|h|d|w|M|y (default off)
--max-depth int If set limits the recursion depth to this (default -1)
--max-size SizeSuffix Only transfer files smaller than this in KiB or suffix B|K|M|G|T|P (default off)
--metadata-exclude stringArray Exclude metadatas matching pattern
--metadata-exclude-from stringArray Read metadata exclude patterns from file (use - to read from stdin)
--metadata-filter stringArray Add a metadata filtering rule
--metadata-filter-from stringArray Read metadata filtering patterns from file (use - to read from stdin)
--metadata-include stringArray Include metadatas matching pattern
--metadata-include-from stringArray Read metadata include patterns from file (use - to read from stdin)
--min-age Duration Only transfer files older than this in s or suffix ms|s|m|h|d|w|M|y (default off)
--min-size SizeSuffix Only transfer files bigger than this in KiB or suffix B|K|M|G|T|P (default off)
Listing Options
--default-time Time Time to show if modtime is unknown for files and directories (default 2000-01-01T00:00:00Z)
--fast-list Use recursive list if available; uses more memory but fewer transactions
其中 --fast-list 适合对象存储后端,通过一次性递归列举来换取更少的 API 事务数;--default-time 则用于当目标系统无法提供修改时间时展示的兜底时间值。
并行模型与性能特征(--checkers)
校验是按文件并行的,默认并行度是 8,可通过全局 --checkers 选项调整(对应参数说明见 --checkers 帮助)。从源码看,这个默认并行度通过 ci.Checkers 决定并发令牌桶大小(fs/operations/check.go#L217-L221),每个配对对象的哈希比对以 goroutine 形式执行,令牌桶限制同时进行的比对数量。
性能上值得注意的三点:
- 加密远端侧开销极小:对绝大多数文件只读取头部
fileHeaderSize字节以取得 nonce,随后Hash()读取的是云端已存储的校验和,不下载文件本体; - 明文源侧是主要开销:本地源文件需要被完整读取一遍并流式重加密;若源也在远端,则会先发生全量下载(形态二);
- 校验粒度:同一对文件仅比对哈希,不做逐字节内容下载比对,因此整体比
--download式全量校验轻量得多。
从代码可以推断:目标后端原生支持校验和(如 MD5、SHA1、XXHASH 等其一)是 cryptcheck 生效的硬前提,云端校验和一般由服务端直接计算,无需把密文拉回本地。
结果状态日志与退出码
运行结束时,fs/operations/check.go#L240-L271 的 reportResults 会以 Logf 汇总如下信息,直接说明校验结果:
N files missing(源或目标缺失的文件数);N differences found(发现差异的文件数);N errors while checking(校验中出错数);N hashes could not be checked(因底层不支持等原因无法比对哈希的文件数);N matching files(匹配一致的文件数)。
更值得关注的是退出码语义:当差异数大于 0 时,reportResults 会返回一个已计数的 FsError(内容形如 N differences found),命令以非零状态退出。这一点对脚本化非常友好——你可以直接根据退出码判断加密备份是否完好,例如在 CI 或定时任务里以非零退出触发告警。
在 bisync 中的实际复用
cryptcheck 的价值不只体现在命令行直接使用上,rclone 的 bisync 命令在目标侧检测到 crypt 远端时,会自动选用与 cryptcheck 等价的校验逻辑,而不是退化为仅比对大小。相关实现见 cmd/bisync/checkfn.go#L25-L53:
Crypt detected! Using cryptcheck instead of check. (Use --size-only or --ignore-checksum to disable)
其选型策略是:先看源与目标是否有公共哈希;没有公共哈希且目标为 crypt 时,解包取底层后端支持的哈希并调用基于 nonce 重加密的 CryptCheckFn;该函数注释明确指出它“比普通 Check 更健壮、更准确”,因为它会回退到 CryptCheck 或 DownloadCheck,而不是默默降级成 --size-only。这从另一个侧面印证了:凡是涉及 crypt 远端的完整性校验,重加密哈希比对都是 rclone 生态中的首选方案。
相关命令与延伸阅读
- rclone check:普通完整性校验命令 —— 非加密场景下比较大小与 MD5/SHA1 等哈希,其文档明确引导加密场景使用
cryptcheck; - Crypt 加密远端使用指南 —— nonce、文件头、加解密流程与配置方式的完整说明;
- rclone cryptcheck 命令参考文档 —— 本文的依据文档,包含自动生成的完整选项帮助;
- rclone 主命令文档 —— 查看全部命令、选项与后端的帮助入口;
- 命令源码 cmd/cryptcheck/cryptcheck.go 与共享报表选项 cmd/check/check.go。
一句话实践建议:定期用 rclone cryptcheck /本地/明文目录 encryptedremote:备份目录 校验加密备份,配好 --differ、--combined 报告路径,并把它接入退出码判断——这是在不信任的云端存储上维护“密文完好、可解密还原”低成本高可信度的校验方案。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00