☰
PHP crc32()函数讲解
2026/10/8 10:36:39 网站建设 项目流程

前言


crc32()计算一段字符串的 CRC-32 校验值(cyclic redundancy check,循环冗余校验),返回一个 32 位整数。它最常见的用途是「快速判断两段数据是不是同一份」:缓存键的后缀、ETag、内容去重、短链接的 ID 前缀,以及检测传输或存储过程中有没有发生意外的字节损坏。


这里必须先纠正一个非常常见的误解:CRC-32 是校验和,不是哈希,更不是加密。它的设计目标是「发现随机错误」,而不是「抵抗蓄意篡改」。任何人都可以针对一个给定的 CRC-32 值构造出大量碰撞数据,因为 CRC 是线性的。所以它能告诉你「文件传丢了几个字节」,但绝不能告诉你「这个数据没有被改过」——后者要用hash('sha256', ...)或hash_hmac()。


另一个高频困惑是返回值有时是负数。这不是 bug,而是 32 位平台上整数符号性导致的,本文会给出统一的无符号表示办法。


一、签名与返回值


crc32(string $string): int

它接受字符串,返回整数。需要明确的几点:



  • CRC-32 是确定性的:相同输入永远得到相同输出,没有任何随机成分或 salt。这既是它有用的原因,也是它不能用于安全场景的原因。

  • 它使用的多项式与 gzip、PNG 等格式里的 CRC-32 一致,在hash()里对应的算法名是crc32b。

  • 输入为空字符串时结果是0。


在 64 位 PHP 上,int是 64 位有符号整数,而 CRC-32 的值域是 0 到 4294967295,完全放得下,所以结果永远是非负的。但在32 位 PHP上(很多老旧的 x86 服务器、部分嵌入式环境),int是 32 位有符号的,超过 2147483647 的校验值会显示为负数。这会导致「同一份代码在两台机器上算出不同的数」这种极其难查的问题,因为负数一旦被写进数据库或拼进缓存键,两张表就对不上了。


<?php // 适用于 PHP 7.0+
$s = 'hello world';
$c = crc32($s);

printf("默认输出 : %d\n", $c);
printf("无符号十进制 : %u\n", $c); // %u 按无符号解释,32/64 位都稳
printf("十六进制 : %08x\n", $c); // 固定 8 位小写十六进制

%u和%08x是跨平台统一表示的稳妥做法:%u把整数按无符号解释,%08x在 32 位和 64 位平台上都会给出 8 位十六进制(因为高 32 位在两种平台上都是 0 或被符号扩展后取低 32 位)。


二、与 hash() 的 crc32b 等价


PHP 的hash()扩展里有一个算法名crc32b,它和crc32()是同一种算法的两种出口:一个给整数,一个给十六进制。


<?php // 适用于 PHP 7.0+
$s = 'hello world';

$intValue = crc32($s);
$hexValue = hash('crc32b', $s);

var_dump($hexValue === sprintf('%08x', $intValue)); // bool(true)

这个等价关系有两个实际价值。第一,需要把校验值作为字符串参与比较或存储时,用十六进制更不容易被平台符号性影响。第二,hash_file()支持crc32b这个算法名,可以在不把整个文件读进内存的情况下算文件的 CRC:


<?php // 适用于 PHP 7.0+
// 相对大文件:边读边算,PHP 内部按块处理
$path = __DIR__ . '/data.bin';
if (is_readable($path)) {
$fileCrc = hash_file('crc32b', $path); // 十六进制字符串
printf("%s -> %s\n", basename($path), $fileCrc);
} else {
echo "文件不存在或不可读\n";
}

注意文件路径要用__DIR__拼接绝对路径,而不是相对路径——相对路径取决于「当前工作目录」,在 CLI 和 Web 两种运行方式下完全可能不同。


三、典型用法与它适合的边界


适合:


<?php // 适用于 PHP 8.0+
// 1) 给缓存键加一个短后缀,降低不同内容撞到同一个键的概率
$key = 'article:' . $id . ':' . sprintf('%08x', crc32($body));

// 2) 内容去重:先比长度和 CRC,再比完整内容(CRC 只用来快速淘汰)
function sameContent(string $a, string $b): bool
{
if (strlen($a) !== strlen($b)) {
return false;
}
if (crc32($a) !== crc32($b)) {
return false; // 绝大多数不同的内容在这里就被排除了
}
return hash_equals(hash('sha256', $a), hash('sha256', $b)); // 最终裁决
}

// 3) 传输完整性:发送方带 CRC,接收方重算
$payload = 'some data';
$check = sprintf('%08x', crc32($payload));
printf("payload=%s crc=%s\n", $payload, $check);

第 2 点是 CRC 正确的用法:把 CRC 当作廉价的预筛选,最终的判定仍然交给密码学哈希。只比 CRC 就断言「内容相同」是不严谨的。


不适合:



  • 用户令牌、会话 ID、密码散列——用random_bytes()和password_hash(),CRC 是可逆推的。

  • 防篡改、数据签名——用hash_hmac()(带密钥)或sodium_crypto_generichash()。

  • 「防重复提交」的幂等键——如果用户能猜到该内容对应的 CRC,就能伪造。


四、输出编码与比较的正确姿势


CRC 是用来「比」的,不是用来「看」的,所以比较方式比计算方式更重要。


<?php // 适用于 PHP 8.0+
$expected = 'c87258b7'; // 别处传过来的十六进制校验值
$actual = hash('crc32b', 'hello world');

// ✅ 转成同一种规范形式再比,忽略大小写差异
if (hash_equals(strtolower($expected), strtolower($actual))) {
echo "校验一致\n";
}

// ❌ 用 == 比较字符串与数字,会触发 PHP 8 的语义变化
var_dump('c87258b7' == 0); // bool(false)(PHP 8 中字符串与数字比较已改规则)
var_dump((string) crc32('x') == crc32('x')); // 类型不同,不要这么比

PHP 8 修改了字符串与数字的比较规则:数字字符串与数字比较时按数值比较,非数字字符串与数字比较时,数字会被转成字符串再比。这条规则很绕,最稳的做法是两边都显式转成同一种类型,用hash_equals()或===,不要依赖隐式转换。


常见坑点



  1. ❌if (crc32($a) == crc32($b))就认定两段内容完全一样。


✅ CRC-32 的碰撞是很容易刻意制造的,它只适合「快速排除不同」。最终判定用hash('sha256', ...)配合hash_equals()。



  1. ❌ 把crc32()的结果当作「找不到」的哨兵值:$idx = crc32($key); if ($idx === 0) { /* 当作没有 */ }。


✅ 空字符串的 CRC 就是0,合法数据也会算出0。要区分「没有」,请单独用isset()/array_key_exists()判断,而不是用返回值。



  1. ❌ 在 32 位机器上直接输出:echo crc32('hello world');得到负数,写进数据库后再和 64 位机器算的值比较就不相等。


✅ 统一用sprintf('%08x', crc32($s))或sprintf('%u', crc32($s)),把结果规范化成字符串后再存储和比较。



  1. ❌ 用crc32()生成短链接 ID 或优惠券码,认为 32 位足够「不可猜」。


✅ 可枚举、可碰撞。需要不可猜就用bin2hex(random_bytes(16)),需要短就用base_convert()或哈希截断并且加上服务端的唯一性校验。



  1. ❌ 用crc32()校验「登录令牌有没有被改过」,即crc32($user . $secret)这种自拼的签名。


✅ 这是典型的「自己造 MAC」。用hash_hmac('sha256', $data, $secret),校验时用hash_equals()。



  1. ❌ 把hash('crc32b', $s)和hash('crc32', $s)当成同一个东西互换使用。


✅ 只有crc32b与crc32()的十六进制形式对应;hash()里还有其他名字相近的算法,别想当然地等价替换。



  1. ❌ 用crc32(file_get_contents($bigFile))校验大文件。


✅ 这会把整个文件读进内存。用hash_file('crc32b', $path),它按块读取。



  1. ❌crc32($arr)或crc32(null)直接调用。


✅ 参数是string类型,数组会抛TypeError;null在 PHP 8 会被转换成空字符串并算出0(还会出一条废弃提示),显式传''更清楚。


总结




问题结论



签名crc32(string $string): int

值域0 ~ 4294967295(32 位无符号)

32 位平台结果可能显示为负数,需用%u/%08x规范化

与hash()hash('crc32b', $s)等于sprintf('%08x', crc32($s))

文件版本hash_file('crc32b', $path)

是否安全否。不是哈希算法对抗攻击用的,更不是加密

正确用途缓存键后缀、内容去重的预筛选、传输完整性检测



把crc32()定位成「廉价的完整性探测工具」就不会用错:它能高效地发现意外损坏,但对蓄意篡改毫无抵抗力。凡是涉及安全语义的地方——令牌、签名、密码——一律换成random_bytes()、hash_hmac()或password_hash()。





需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询