首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

Discuz! authcode 經典加解密演算法深度剖析:從異或運算到 RC4 改良版

     編輯:PHP 綜合知識

前言:為什麼要研究一個 20 年前的函數?

Discuz! authcode 函數自 2001 年誕生以來,成為 PHP 開源圈最被廣泛複製、移植、討論的加密工具。儅今主流框架皆採用 OpenSSL / Sodium 等現代加密庫,但理解其設計思想對學習對稱加密原理、金鑰派生、完整性驗證、防重放攻擊極具教學價值。

核心原理:流式加密 + 密鑰派生 + 完整性保護

本質上是一個基於 RC4 改良的流式加密演算法,結合動態金鑰(Key C)、時間戳、HMAC 風格完整性校驗,實現:

  • 相同明文 + 相同金鑰 → 每次產生不同密文(語義安全性)
  • 密文攜帶過期時間與驗證碼,解密時自動檢測竄改與過期
  • 無需外部擴展,純 PHP 實現,相容性極強

逐段代碼解讀

1. 參數與金鑰預處理

function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
    $ckey_length = 4;                          // 動態金鑰長度
    $key = md5($key ? $key : $GLOBALS['discuz_auth_key']); // 主金鑰 MD5
    $keya = md5(substr($key, 0, 16));          // 加解密金鑰
    $keyb = md5(substr($key, 16, 16));         // 完整性驗證金鑰
    $keyc = $ckey_length
        ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length)
            : substr(md5(microtime()), -$ckey_length))
        : '';                                  // 動態金鑰
    $cryptkey = $keya . md5($keya . $keyc);    // 最終加密金鑰
}

設計亮點:

  • 主金鑰拆分為 $keya/$keyb 兩用途,體現關注點分離
  • $keyc 引入微秒時間熵,確保同明文輸出不同密文
  • 最終金鑰 $cryptkey 長度 32+32=64 字符,足夠 RC4 初始化

2. 明文預處理(加密時)

$string = sprintf('%010d', $expiry ? $expiry + time() : 0)  // 10位過期時間戳
         . substr(md5($string . $keyb), 0, 16)               // 16位完整性校驗碼
         . $string;                                          // 原始明文

解密時則從第 $ckey_length 位開始 Base64 解碼,還原上述結構。

3. RC4 核心:S-box 初始化與亂序

$box = range(0, 255);
$rndkey = array();
for ($i = 0; $i <= 255; $i++) {
    $rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
// KSA: Key-Scheduling Algorithm
for ($j = $i = 0; $i < 256; $i++) {
    $j = ($j + $box[$i] + $rndkey[$i]) % 256;
    $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp;
}

標準 RC4 KSA,將 256 字節 S-box 依金鑰打亂。

4. PRGA:偽隨機流產生與異或加密

for ($a = $j = $i = 0; $i < $string_length; $i++) {
    $a = ($a + 1) % 256;
    $j = ($j + $box[$a]) % 256;
    $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp;
    $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}

標準 RC4 PRGA,每次迴圈產生一字節關鍵流,與明文異或。

5. 解密驗證邏輯

if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0)
    && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
    return substr($result, 26); // 返回真實明文
}
return ''; // 驗證失敗返回空字串

三重防護:

  1. 時間戳為 0 表示永不過期,否則檢查是否過期
  2. MD5($plaintext . $keyb) 前 16 位作為完整性標籤
  3. 任何位元竄改、金鑰錯誤、過期皆導致驗證失敗

安全性評估與已知弱點

項目評價
算法強度RC4 已知存在偏差攻擊(Fluhrer-Mantin-Shamir),不建議用於高機密資料
金鑰管理單一主金鑰衍生所有子金鑰,缺乏前向安全性
完整性MD5 碰撞風險,且非標準 HMAC 結構
重放攻擊依賴時間戳防禦,時鐘不同步會失效
適用場景Token、Cookie 簽名、低敏感度 API 參數加密、教學演示

現代替代方案建議

// 推薦:PHP 7.2+ 原生 Sodium (libsodium)
$key = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($message, $nonce, $key);
// 存儲: base64_encode($nonce . $ciphertext)

// 或 OpenSSL AES-256-GCM (需 PHP 7.1+)
$iv = random_bytes(12);
$ciphertext = openssl_encrypt($msg, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);

結語

authcode 是 PHP 早期工程師在無現代加密庫條件下,用有限原語構建出的「足夠好」方案。其動態金鑰、時間戳嵌入、完整性校驗三大設計模式至今仍具參考價值。但新專案請務必使用經過密碼學專家審計的標準庫,不要在生產環境直接沿用此函數處理敏感資料。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved