Google 早期提供 Toolbar PageRank 查詢接口,需計算特定 `ch` 參數(checksum)。隨著演算法更新、64 位系統普及、PHP 整數溢出行為差異,舊代碼頻繁失效。本文深度解析演算邏輯並提供跨平台兼容實現。
[
'timeout' => 10,
'header' => "User-Agent: " . self::USER_AGENT . "\r\n",
]
]);
$response = @file_get_contents($queryUrl, false, $ctx);
if ($response === false) return false;
// 解析回應: Rank_1:1:9
if (preg_match('/Rank_\d+:\d+:(\d+)/', $response, $m)) {
return (int)$m[1];
}
return false;
}
/**
* 核心 Checksum 計算 (兼容 32/64 位)
*/
private static function computeChecksum(string $url): string {
$url = 'info:' . $url;
$len = strlen($url);
// 初始化魔數 (32 位無符號整數)
$a = 0x9e3779b9;
$b = 0x9e3779b9;
$c = 0xe6359a60; // Google 魔數
// 以 12 字節為塊處理
$k = 0;
while ($k + 12 <= $len) {
$a += self::strToUInt32($url, $k);
$b += self::strToUInt32($url, $k + 4);
$c += self::strToUInt32($url, $k + 8);
self::mix($a, $b, $c);
$k += 12;
}
// 處理剩餘字節
$c += $len;
$rem = $len - $k;
$offset = $k;
switch ($rem) {
case 11: $c += (self::uint32($url[$offset + 10]) << 24);
case 10: $c += (self::uint32($url[$offset + 9]) << 16);
case 9: $c += (self::uint32($url[$offset + 8]) << 8);
case 8: $b += (self::uint32($url[$offset + 7]) << 24);
case 7: $b += (self::uint32($url[$offset + 6]) << 16);
case 6: $b += (self::uint32($url[$offset + 5]) << 8);
case 5: $b += self::uint32($url[$offset + 4]);
case 4: $a += (self::uint32($url[$offset + 3]) << 24);
case 3: $a += (self::uint32($url[$offset + 2]) << 16);
case 2: $a += (self::uint32($url[$offset + 1]) << 8);
case 1: $a += self::uint32($url[$offset]);
}
self::mix($a, $b, $c);
// 返回 "6" + 10 位數字 (無符號 32 位整數)
return '6' . str_pad(self::uint32ToString($c), 10, '0', STR_PAD_LEFT);
}
/** 字符串 4 字節轉無符號 32 位整數 (小端序) */
private static function strToUInt32(string $str, int $offset): int {
return self::uint32($str[$offset])
| (self::uint32($str[$offset + 1]) << 8)
| (self::uint32($str[$offset + 2]) << 16)
| (self::uint32($str[$offset + 3]) << 24);
}
/** 字符轉無符號 8 位整數 */
private static function uint32(string $char): int {
return ord($char) & 0xFF;
}
/** 關鍵:強制模擬 32 位無符號整數運算 (解決 64 位/負數溢出) */
private static function toUInt32(int $val): int {
// PHP 8.0+ 可用: $val & 0xFFFFFFFF
// 兼容舊版: 手動處理溢出
$val &= 0xFFFFFFFF;
// PHP 整數溢出會轉 float,需修正
if ($val > 0x7FFFFFFF) {
$val -= 0x100000000; // 轉為有符號負數表示,但位模式不變
}
return $val;
}
/** Jenkins Hash 混合函數 (全程 32 位運算) */
private static function mix(int &$a, int &$b, int &$c): void {
$a = self::toUInt32($a - $b - $c); $a ^= self::rshift($c, 13);
$b = self::toUInt32($b - $c - $a); $b ^= ($a << 8) & 0xFFFFFFFF;
$c = self::toUInt32($c - $a - $b); $c ^= self::rshift($b, 13);
$a = self::toUInt32($a - $b - $c); $a ^= self::rshift($c, 12);
$b = self::toUInt32($b - $c - $a); $b ^= ($a << 16) & 0xFFFFFFFF;
$c = self::toUInt32($c - $a - $b); $c ^= self::rshift($b, 5);
$a = self::toUInt32($a - $b - $c); $a ^= self::rshift($c, 3);
$b = self::toUInt32($b - $c - $a); $b ^= ($a << 10) & 0xFFFFFFFF;
$c = self::toUInt32($c - $a - $b); $c ^= self::rshift($b, 15);
}
/** 邏輯右移 (無符號右移) */
private static function rshift(int $val, int $bits): int {
if ($val >= 0) return $val >> $bits;
// 負數在 PHP 中算術右移會保留符號位,需轉為無符號邏輯右移
return ($val & 0xFFFFFFFF) >> $bits;
}
/** 無符號 32 位整數轉 10 位十進制字符串 */
private static function uint32ToString(int $val): string {
// 確保為無符號值
$val = $val & 0xFFFFFFFF;
// PHP 8.0+ 直接返回 (string)$val 即為無符號十進制
// 舊版 PHP 負數需手動轉換
if ($val > 0x7FFFFFFF) {
// 利用 BCMath 或 sprintf('%u') (需編譯支持)
// 這裡用純 PHP 模擬: 2^32 = 4294967296
return (string)($val + 4294967296);
}
return (string)$val;
}
}
// 使用示例
$pr = GooglePR::get('https://www.example.com');
var_dump($pr); // int(3) 或 false
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 64 位系統位移保留高位 | `$a >> 13` 在 64 位整數上不截斷高 32 位 | 每步運算後 `& 0xFFFFFFFF` 強制截斷至 32 位 |
| 負數溢出轉 float 精度丟失 | PHP 整數超範圍自動轉 float,32 位 Linux 下負數最大只到 -2^31 | 用 `& 0xFFFFFFFF` 保留位模式,輸出時手動加 `4294967296` 還原無符號值 |
| Windows/Linux `sprintf('%u')` 差異 | Windows 支持 `%u` 無符號格式化,Linux (glibc) 不支持 | 純 PHP 實現 `uint32ToString`,不依賴 `printf` |
| 字符串索引 `$str[$i]` 返回字符而非 ASCII 碼 | PHP 特性 | 用 `ord($str[$i]) & 0xFF` 獲取字節值 |
Google PR `ch` 參數計算涉及雙輪雜湊、32 位無符號整數運算、邏輯右移、跨平台溢出處理。通過 `& 0xFFFFFFFF` 強制截斷、手動還原無符號十進制、純 PHP 邏輯右移,可實現 Windows/Linux、32/64 位全兼容。理解這些底層細節,對處理其他歷史加密/雜湊算法移植極有參考價值。