提到 Linux 下的文件完整性检查,网上十篇教程有八篇在讲 md5sum 和 sha256sum,真正愿意把 cksum 讲明白的文章反而不多。这其实有点可惜:cksum 是 POSIX 标准命令,代码量小、零依赖,几乎任何一台 Linux 机器上都有,最小化安装的容器里都能找到它的身影。我年前帮同事排查一个 3GB 安装包解压报错的问题,下载工具显示任务完成,可解压到一半就报 CRC 错误,最后就是用 cksum 前后一对比,发现文件在下载过程中被截断了。从那以后我的习惯就变了:碰到大文件传输、U 盘拷贝、备份归档这类场景,先顺手跑一遍 cksum 记录下校验值和字节数,后面再做对照。
这篇笔记我打算按自己的使用逻辑来写:先说清楚 cksum 能解决什么问题、输出里的三个字段分别是什么意思,再做几个可以直接照着敲的实例,然后是踩过坑之后总结出来的排错思路,最后把 cksum 和 md5sum、sha256sum 的选型差异以及一堆注意事项拉成清单。无论你是刚开始学 Linux 的新手,还是天天跟服务器打交道的运维,这篇应该都能给你省点时间。
1. 为什么你需要一个“冷门”但系统自带的校验命令
1.1 一次让我印象深刻的解压事故
先说说开头提到的那件事。同事从内网服务器拉了一个离线安装包,3GB 左右,下载工具显示进度 100%。但解压到一半,终端直接刷出一屏 CRC 错误,压缩包整个作废。重新下载一次不仅耗时,还不能确定问题到底出在网络传输、磁盘写入还是源文件本身。
当时我的处理方式很朴素:先到源服务器上对原始文件跑一条cksum,把校验值和字节数记下来,再到同事机器上跑同样的命令。两个值一对,字节数差了大概几百 KB,说明文件根本没传完整,下载工具显示的 100% 并不可靠。这个结论一分钟内就出来了,比重新下载快得多,也比讨论“为什么下载工具会误报”更有意义。
这个案例想说明一个点:文件完整性检查不是你觉得自己文件坏了才要去做的事,而应该在下载、拷贝、归档这些操作之前就留下一个“基准值”。cksum 就是用来生成这个基准值的最轻量工具。
1.2 完整性检查的三层威胁模型
我在实际工作里习惯把“文件完整性”拆成三个层次,因为不同层次的威胁决定了你要用哪个工具。
第一层是传输或存储过程中的偶然损坏。网络丢包、磁盘坏道、内存位翻转、U 盘老化,这些都会让文件里的某些字节变了。这是最常见的情况,也是 cksum 最适合处理的层次。第二层是误操作导致的内容变化,比如编辑配置文件时手滑多打了个空格,或者脚本意外覆盖了文件。这类问题也能被 cksum 发现,因为任何一字节的变化都会反映在 CRC 校验值上。第三层是恶意篡改,有人故意修改文件内容,甚至伪造校验值来掩盖痕迹。这一层 cksum 就力不从心了。
为什么?因为 cksum 默认用的 CRC 校验值只有 32 位,它的设计目标是检测随机错误,而不是对抗有意的碰撞构造。一个懂技术的人完全可以构造出两个内容不同但 CRC 相同的文件。所以如果场景涉及安全、审计、防篡改,应该用 SHA-256 这一类加密哈希工具,而不是 cksum。这个边界必须先搞清楚,否则容易把 cksum 用错地方。
1.3 cksum 的生存现状:老、稳定、到处都有
cksum 是很老的命令,很早就进入了 POSIX 标准,这意味着所有遵循 POSIX 的 Unix 系统都有它。Linux、macOS、各种 BSD、Solaris 这些系统上你都能直接敲cksum,不需要安装任何东西。在内网环境、离线服务器、最小化容器镜像里,这个优势会被放大到极致——你甚至可能没有 md5sum,但大概率有这个命令。
另外还有一个很多人没注意到的点:处理大文件时,cksum 的默认 CRC 算法计算量小,速度通常比 sha256sum 快不少。现代 x86 CPU 基本都有 CRC32 指令,做这种计算属于“硬件级操作”。如果你只是想知道“文件拷过去坏没坏”,完全没必要动用 SHA-256 这样的重型武器,杀鸡用牛刀。
2. 搞懂输出和算法:三个字段、两种 CRC、一个 -a 参数
2.1 三个字段到底代表什么
cksum最基础的用法就是后面跟文件名,不加任何参数。命令跑完之后,终端会输出一行内容,里面用空格分成三个部分:
$ cksum install.iso <校验值> <字节数> <文件名>第一列是 CRC 校验值,一个十进制整数,范围在 0 到 4294967295 之间。它本质上是把文件内容当作一大串数据,通过特定的多项式计算出来的“指纹”。第二列是文件的字节数,这个字段很容易被人忽略,但它是 POSIX 标准特意保留的。第三列是文件名,如果你从标准输入读取数据,这一列会显示为-。
我特别想强调第二列的意义。CRC 校验值只有 32 位,碰撞概率大约是 43 亿分之一。虽然这个概率已经很低,但在传输超大文件或者做批处理时,多一层字节数判断就多一分把握。就算 CRC 理论上撞了,字节数不一样也能立刻发现问题。这就是为什么 cksum 输出里包含字节数,而不是像 md5sum 那样只输出哈希和文件名。
2.2 默认的 POSIX CRC-32,和 gzip、zip 的 CRC-32 不是一回事
这是 cksum 最容易被误解的地方。你可能会在某个压缩包的说明里看到“CRC32: 9C0F43E1”这样的十六进制值,然后在 Linux 上跑cksum 文件名,发现对不上。请记住一条结论:cksum 使用的 CRC 算法遵循 POSIX 标准,和 zlib 里那套被 gzip、zip、png 等大量软件使用的 CRC-32 并不是同一个东西,两者在初始值、位处理方式、结果后处理等细节上有差异。
所以在实际使用中,不要拿 cksum 的结果去跟 unzip 输出的 CRC 值、或者 Windows 下某些压缩软件显示的 CRC32 做比对,它们算法不同,没有可比性。这不算 cksum 的缺陷,只是它归属于另一个标准体系。你只需要保证“生成基准值”和“验证值”用的是同一个工具、同一套算法,就不会有问题。
2.3 -a 参数:同一个命令还能算 md5、sha256
GNU coreutils 8.6 之后,cksum提供了一个容易被忽视的选项:-a或--algorithm,可以指定算法。支持的算法包括crc、md5、sha1、sha224、sha256、sha384、sha512,更新版本还能选sm3、blake2b。
比如你想快速算一个文件的 SHA-256 值,可以直接:
$ cksum -a sha256 config.yml输出内容和 sha256sum 的结果本质上是同一个摘要,只是格式上略有差异。这个参数在某些特殊环境里很实用:当你的脚本已经大量使用cksum,又不想额外引入sha256sum的依赖时,可以用-a来统一入口。但这里有一个必须注意的坑:BusyBox 和老版本的 coreutils 里,cksum不一定支持-a。我的建议是,在跨环境脚本里不要默认依赖这个特性,优先用最基础的默认 CRC 模式。
3. 从单文件到批处理:最常用的几个实例拆解
3.1 最基础用法:直接给文件算值
先创建一个测试文件,我习惯用echo生成一个小文件来验证命令行为:
$ echo "hello cksum" > demo.txt $ wc -c demo.txt 12 demo.txt然后跑cksum:
$ cksum demo.txt <校验值> 12 demo.txt输出里的第二列正好是 12,和wc -c看到的字节数一致。这说明 cksum 是按字节处理内容的,不会因为文本文件或二进制文件而改变行为。你可以拿/dev/null试一下,会得到0 0 /dev/null,零字节文件的校验值就是 0。这个特性在脚本里可以用来快速判断一个文件是不是空文件。
3.2 读取标准输入:管道场景下的校验
cksum不接文件名时,会从标准输入读取内容。最常见的用法是配合管道:
$ echo "hello cksum" | cksum <校验值> 12 -注意第三列是-,表示数据来源是标准输入。这个能力在归档场景里很好用。比如你想给一个目录的内容整体做个校验,而不是逐个文件去算,可以先把目录打成 tar 流再送进 cksum:
$ tar cf - /var/log | cksum这样你得到的是整个目录内容对应的“流级”校验值。归档前后各跑一次,只要两个值一致,就能断定目录内容没有发生变化。这个思路比遍历几百个文件逐个比对高效得多。
3.3 一次校验多个文件
cksum后面可以跟多个文件,结果是一行一个文件:
$ cksum demo.txt /etc/hosts /etc/passwd <校验值> 12 demo.txt <校验值> 198 /etc/hosts <校验值> 1723 /etc/passwd批量输出的顺序和你在命令行里的书写顺序一致。这个特性可以配合通配符使用:
$ cksum *.log *.tar.gz如果你需要把结果保存下来,直接重定向到文件里:
$ cksum *.log *.tar.gz > CHECKSUMS.cksum这个文件就相当于一个“校验清单”,后面我会专门写怎么用它做验证。
3.4 文件内容变化后校验值的变化
很多人对校验值不敏感,是因为没见过实际变化。做个很直观的测试:
$ echo "hello" > a.txt $ cksum a.txt <校验值A> 6 a.txt然后给文件加一个词再试:
$ echo "hello world" > a.txt $ cksum a.txt <校验值B> 12 a.txt内容从 6 字节变成 12 字节,CRC 校验值完全变了。这里我顺便提醒一个容易踩的细节:echo默认会在行尾加一个换行符,所以echo "hello"写进文件的实际是hello\n这 6 个字节。如果你用echo -n "hello" > a.txt,文件只会是 5 个字节,校验值当然也不同。文件末尾多一个换行符都会导致校验值变化,这在对比两端文件时很重要。
4. 排错实录:两边算出的 cksum 值不一致,问题出在哪
4.1 第一步:先看字节数,再看 CRC
当你拿着源文件的校验值去对比另一台机器上的拷贝,发现两边对不上时,先别急着怀疑 CRC 算法。我的排查顺序非常固定:先看输出的第二列字节数。字节数都不一样,那就说明文件长度都变了,内容必然有差异,可以直接放弃“CRC 碰撞导致误判”这种小概率幻想。
如果字节数一样,但 CRC 值不同,说明文件总长度没变,但中间某些字节内容被改了。这个时候我会用cmp做逐字节对比:
$ cmp -l file1 file2cmp -l会列出第一个不同字节的位置和具体数值。如果它没有任何输出,说明两个文件内容完全相同。如果它报错退出,就说明存在差异。这个工具在排查时特别高效,因为它能直接告诉你差异发生在文件的哪个位置。
4.2 跨平台传输的换行符陷阱
最容易导致字节数对不上但截图不明显的问题,就是换行符差异。Windows 文本文件的换行是CRLF两个字节,Linux 和 macOS 是LF一个字节。如果你用 FTP 的 ASCII 模式传文本文件,FTP 会自作主张把换行符转成目标系统的格式,结果就是文件长度变了,cksum 必然对不上。
我遇到过的情况是:一个配置文件从 Windows 机器传到 Linux 服务器,两边字节数差了 20 多个,用hexdump一看,每隔几行就多出0d 0a,也就是 CRLF。当时处理办法也很简单:用 FTP 的 binary 模式重新传,或者传完之后用dos2unix把换行符统一回来。这个问题在 Windows 和 Linux 之间互相传文件时特别常见,排查 cksum 不一致的时候一定要把这个因素考虑进去。
4.3 哪些东西改了不影响校验值
和直觉相反,有几种改动不会影响 cksum 的结果:
第一,修改文件权限。chmod只是改了 inode 里的元数据,文件内容没有变,cksum 值不变。第二,修改文件的所有者和所属组。同样属于元数据,不影响内容指纹。第三,重命名文件或移动文件路径。cksum 只读内容,不关心文件名。第四,修改文件的时间戳。touch改变的是 atime、mtime 这类时间信息,不影响内容。
所以在排查时,如果两边 cksum 对不上,不要去想“是不是有人改了权限”,先从内容层面找原因。反过来,如果你改了权限后发现 cksum 值变了,那一定是文件内容在某个环节被悄悄动过,而不是权限本身引起的。
这里还要提一个软链接的问题。cksum在遇到软链接时,默认会跟随链接去读取最终目标文件的内容,所以算出来的结果是目标文件的指纹,而不是链接本身。如果目标文件不存在,会直接报No such file or directory。这一点在一些打包场景里容易被误解,有人以为软链接的校验值应该指向“链接文本”,不是这样的。
4.4 生成或传输过程的隐性损坏
如果上面这些常规原因都排除了,两边字节数一致但 CRC 还是不同,那就要考虑更底层的因素了。比如文件在生成过程中被并发写入。我曾经在一台服务器上看到一个日志文件在滚动归档时被压缩进程同时读取,导致归档包里的内容和源内容对不上。遇到这种场景,正确的做法是先确保文件处于“静止状态”,再生成基准值。
另外,磁盘坏道也可能导致读取结果不稳定。一个文件第一次读出来是 A 值,第二次读出来变成 B 值,这两次 cksum 不一样,那就说明存储介质本身出问题了。此时不要再反复重跑 cksum,赶紧备份能读出来的数据,然后检查磁盘健康状态。cksum 在这里扮演的角色不是证明文件“好”,而是第一个跳出来告诉你“坏了”,这就是它的价值。
5. cksum、md5sum、sha256sum 怎么选:一张表说清楚
5.1 三者的核心差异
很多初学者分不清这三个工具,以为都是算校验和,随便用哪个都行。但它们的定位和适用场景差异很明显,我把核心区别整理成了一张表:
| 对比维度 | cksum | md5sum | sha256sum |
|---|---|---|---|
| 计算算法 | POSIX CRC-32 | MD5 | SHA-256 |
| 输出内容 | CRC值 + 字节数 + 文件名 | 哈希值 + 文件名 | 哈希值 + 文件名 |
| 是否包含字节数 | 包含 | 不包含 | 不包含 |
| 抗碰撞能力 | 32位,可被刻意构造 | 已发现实际碰撞,不适合安全场景 | 当前安全 |
| POSIX 可移植性 | 几乎所有 Unix 系统自带 | GNU 系为主,非所有 Unix | GNU 系为主,非所有 Unix |
| 典型用途 | 快速校验传输、拷贝是否损坏 | 一般完整性校验 | 软件发布、安全校验、防篡改 |
很明显,cksum 最大的价值在“快速”和“可移植”,但也有明显的短板:不能用于对抗恶意篡改。MD5 曾经是完整性校验的主流,但现在已经能在可接受的时间内构造碰撞,所以官方的安全校验清单基本都改用 SHA-256 了。sha256sum 是目前最稳妥的安全校验工具,缺点是在没有硬件加速的老机器上,算大文件时会比较慢。
5.2 我对选型的一点实际建议
我的选择原则很简单,就一句话:看你要防的是“意外损坏”还是“恶意篡改”。
只是担心文件在传输或拷贝过程中损坏,比如从服务器下载大文件、往 U 盘里拷资料、做备份归档,用 cksum 就够了,速度快,命令简单,任何环境都能用。如果是下载官方发布的 Linux 内核、软件安装包这些场景,官方通常会给出 SHA-256 校验值,这时候直接用 sha256sum 去和官方值比对,不要用 cksum。如果你需要长期存档并防止有人刻意篡改文件,也必须用 sha256sum 或更强的算法。
还有一种混合用法我在生产环境里经常用:发布软件时同时生成两份校验文件,一份是 cksum 清单,负责日常快速核验;一份是 sha256sum 清单,负责安全审计。日常用前者,出问题或做安全合规时用后者,两不耽误。
6. 把 cksum 用进日常:两个可以直接抄的校验脚本
6.1 生成校验清单
单个文件校验很容易,但碰到几十个文件、几百个文件时,手工一个个看输出太累。最实用的做法是把校验结果保存成清单文件。比如在一个包含多个压缩包的目录里:
$ cd /data/backup $ cksum *.zip *.tar.gz > CHECKSUMS.cksum生成出来的CHECKSUMS.cksum每一行都是“CRC值 字节数 文件名”,这就是后面校验的依据。这里有一个我踩过的坑:生成清单前,确认一下当前目录下没有名为CHECKSUMS.cksum的旧文件,否则cksum *.zip *.tar.gz不会把清单本身算进去,问题不大;但如果你的通配符是cksum *,清单文件本身也会被算进去,下一次校验时清单内容变了,会引发一串莫名其妙的 FAIL。
6.2 根据清单批量验证
生成清单之后,校验的操作我通常用一个 bash 脚本完成,逻辑很直接:逐行读取清单,对每个文件重新算一次 cksum,再和清单里的值比对。
#!/usr/bin/env bash # 用法: ./check_cksum.sh CHECKSUMS.cksum while read -r sum bytes file; do if [ ! -f "$file" ]; then echo "MISS: $file" continue fi actual=$(cksum "$file" | awk '{print $1, $2}') if [ "$actual" = "$sum $bytes" ]; then echo "OK: $file" else echo "FAIL: $file" fi done < "$1"这个脚本有个巧妙的地方:read -r sum bytes file会把文件名部分剩余的所有内容都塞进file变量。所以即使文件名里包含空格,只要空格不在文件名的开头或结尾,也能正确处理。验证时awk '{print $1, $2}'拿到的正好是当前文件的 CRC 和字节数,然后和清单里的两个字段比较,两个都一致才输出 OK。
实际跑一遍,你会看到类似这样的输出:
$ ./check_cksum.sh CHECKSUMS.cksum OK: release-1.0.tar.gz OK: release-1.0.zip FAIL: debug-notes.txt MISS: old-package.tar.gzFAIL 说明文件内容变了,MISS 说明文件直接不存在。这两类情况分别处理就行。
6.3 文件名包含空格、CRLF 换行这些边界情况
脚本虽然简单,但边界情况还是要说清楚。如果文件名以空格开头或结尾,上面的read默认会把分隔符吃掉,导致file变量里的路径缺了空格,校验直接报 MISS。更极端的场景是文件名里包含换行符,这在 Linux 下是合法的,但任何“按行解析”的清单方案都会在这类文件上翻车。
针对这类场景,我的建议是:在日常归档时统一规范文件名,避免空格开头结尾,也避免换行符。如果确实要处理特殊文件名,可以通过find ... -print0配合xargs -0来规避换行问题,但那套逻辑对普通用户来说太重了,不如先把文件名规范化来得省心。
还有一个和 Windows 相关的坑:如果你在 Windows 上用记事本编辑过CHECKSUMS.cksum,保存的换行符是 CRLF,在 Linux 上用read读取时,最后一列文件名后面会粘一个\r,导致文件找不到。解决办法很简单,先跑一次dos2unix CHECKSUMS.cksum,或者用sed -i 's/\r$//' CHECKSUMS.cksum把回车符去掉。
7. cksum 注意事项清单(建议收藏版)
7.1 三条最容易被忽视的规则
第一条,别拿 cksum 当安全工具。前面反复提过,CRC 是检错码,不是加密哈希。只要有预谋的攻击者,构造一个 CRC 相同的文件并不是难事。所以但凡涉及“防篡改”的场景,请转向 sha256sum。
第二条,不要拿 cksum 的 CRC 值和压缩软件显示的 CRC32 做比对。两种 CRC 算法不同,结果不可互换。如果你需要跟 zip 包里的 CRC 核对,应该用unzip -v这样的工具,或者用 Python 的zlib.crc32,而不是 cksum。
第三条,别对正在被写入的文件跑 cksum。日志文件、数据库文件、正在下载的临时文件,内容每时每刻都在变化,第一次和第二次跑出来的值可能不一样,很容易造成误判。生成基准值的正确时机是文件已经确定不再变化之后。所以我的习惯是先拷贝或下载到一个临时目录,确认任务彻底结束后再校验。
7.2 性能与适用边界的补充
cksum 默认的 CRC 模式消耗的资源很少,但也不是完全没有开销。对一个 10GB 级别的大文件做校验,耗时主要取决于磁盘读取速度,其次才是 CPU 计算。在老的机械硬盘上跑,瓶颈是 I/O;在 NVMe SSD 上跑,几十秒内就能跑完一个大文件。
如果是在交叉编译的嵌入式环境里,或者目标系统只有 BusyBox,你可能会发现cksum默认能跑,但-a sha256报不支持。这个兼容性差异在写部署脚本时要格外注意。我的做法是做一个能力检测,先跑cksum --help看看有没有-a,没有就用默认模式,避免脚本在不同环境里行为不一致。
还有一个常见的操作误区:对目录直接跑 cksum 会报错。比如cksum /etc会得到Is a directory的提示。想对目录做整体校验,要么用 tar 流打包后校验,要么先find递归出文件列表再逐个处理。直接对着目录跑是行不通的。
7.3 什么场景下我会绕过 cksum
如果是比较两台服务器上的同一个文件是否一致,而且两台机器都能登录,直接用cmp比 cksum 更直接,不需要先取基准值。cksum的独特优势在于“离线对比”:只需要一份历史校验值,不需要源文件在场。比如你只有笔记本上的文件,却知道服务器上同一文件的 cksum 值,就能判断两边是否一致。
如果是做软件发布的安全校验,官方给了 SHA-256 值,那么无论 cksum 多快多方便,我都会用 sha256sum 去对数。这不仅是习惯问题,也是安全边界问题。CRC 只负责检测“意外”,不负责对抗“蓄意”。
如果是做长期归档,我更倾向于同时记录 cksum 和 sha256sum 两份结果。cksum 用于平时抽查存储介质是否老化,sha256sum 用于未来某一天需要跟外部审计方证明文件完整性时使用。日常轻量核对跑 cksum 就够了,重武器留在真正需要的时候。
这个工具写进脚本、写进工作流之后,最大的感受是省心。以前每次传完大文件,总有种“到底成没成”的不确定感,现在多敲一条命令、多留一个清单,心里就有底。Linux 下类似的命令还有不少,像 cksum 这样看起来不起眼、真到用时才觉得值得学好的,大概就是这类系统自带小工具的朴素价值。