Discuz! 經典的 authcode 函數曾廣泛用於 PHP 專案的 Token 生成、API 簽名、Session 加密等場景。雖然其設計巧妙,但基於自定義流加密算法,不符合現代密碼學最佳實踐。本文深度解析其原理、安全隱患,並提供基於 OpenSSL AES-GCM 的現代替代方案。
核心流程:
$key = md5($key) → 分為 $keya、$keyb(各 16 字節),$keyc(動態 4 字節,解密時從密文首部取得,加密時從 md5(microtime()) 取得)。$cryptkey = $keya . md5($keya . $keyc)(32 字節)。md5($plaintext . $keyb) 前 16 位)+ 明文。$cryptkey 初始化 256 字節 S-box,進行異或加密。$keyc . base64_encode($ciphertext)。| 問題 | 影響 |
|---|---|
| 自定義流加密 | 未經學術界驗證,可能存在未知弱點;RC4 本身已被棄用(RFC 7465) |
| 無完整性驗證 | 僅 16 位 MD5 校驗,極易碰撞,無法防範密文竄改(CBC 掛載攻擊等) |
| 金鑰派生弱 | 單輪 MD5,無鹽值、無迭代,易受彩虹表/暴力破解 |
| IV 固定/可預測 | $keyc 實際作為 IV,但熵來源不足(microtime 或密文首部) |
Base64 含 = 處理 | 移除 = 導致解碼歧義,需補齊 |
採用 openssl_encrypt 搭配 GCM 模式,同時提供機密性與完整性(AEAD)。
<?php
namespace Security;
use RuntimeException;
use InvalidArgumentException;
class ModernCrypto
{
private const CIPHER = 'aes-256-gcm';
private const KEY_BYTES = 32;
private const IV_BYTES = 12; // GCM 建議 96 位元組 IV
private const TAG_BYTES = 16; // 認證標籤長度
private string $key;
public function __construct(string $masterKey)
{
// 使用 HKDF-SHA256 派生金鑰,支援任意長度主金鑰
$this->key = $this->hkdf($masterKey, self::KEY_BYTES, 'auth-token-v1');
}
/**
* 加密並認證
* @param string $plaintext
* @param string|null $associatedData 額外認證資料(AAD),如用戶 ID、請求路徑
* @return string Base64 編碼:IV(12) + Ciphertext + Tag(16)
*/
public function encrypt(string $plaintext, ?string $associatedData = null): string
{
$iv = random_bytes(self::IV_BYTES);
$ciphertext = openssl_encrypt(
$plaintext,
self::CIPHER,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag,
$associatedData ?? '',
self::TAG_BYTES
);
if ($ciphertext === false) {
throw new RuntimeException('加密失敗: ' . openssl_error_string());
}
// 組合:IV + Ciphertext + Tag
return base64_encode($iv . $ciphertext . $tag);
}
/**
* 解密並驗證
* @throws InvalidArgumentException 格式錯誤或認證失敗(密文被竄改)
*/
public function decrypt(string $payload, ?string $associatedData = null): string
{
$data = base64_decode($payload, true);
if ($data === false) {
throw new InvalidArgumentException('無效的 Base64 編碼');
}
$minLen = self::IV_BYTES + self::TAG_BYTES;
if (strlen($data) < $minLen) {
throw new InvalidArgumentException('密文長度不足');
}
$iv = substr($data, 0, self::IV_BYTES);
$tag = substr($data, -self::TAG_BYTES);
$ciphertext = substr($data, self::IV_BYTES, -self::TAG_BYTES);
$plaintext = openssl_decrypt(
$ciphertext,
self::CIPHER,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag,
$associatedData ?? ''
);
if ($plaintext === false) {
throw new InvalidArgumentException('解密失敗:認證標籤驗證不通過(資料可能已損壞或金鑰錯誤)');
}
return $plaintext;
}
/**
* HKDF-SHA256 金鑰派生
*/
private function hkdf(string $ikm, int $length, string $info = ''): string
{
// PHP 7.1+ 可用 hash_hkdf()
if (function_exists('hash_hkdf')) {
return hash_hkdf('sha256', $ikm, $length, $info, '');
}
// 相容實作(RFC 5869)
$salt = str_repeat("\0", 32);
$prk = hash_hmac('sha256', $ikm, $salt, true);
$t = '';
$output = '';
$counter = 1;
while (strlen($output) < $length) {
$t = hash_hmac('sha256', $t . $info . chr($counter), $prk, true);
$output .= $t;
$counter++;
}
return substr($output, 0, $length);
}
}
// 使用範例
$crypto = new ModernCrypto('master-secret-key-from-env-or-kms');
$tokenData = json_encode([
'uid' => 12345,
'role' => 'admin',
'exp' => time() + 3600,
]);
// 加密:綁定用戶 ID 作為 AAD,防止 Token 被盜用於其他用戶
$encrypted = $crypto->encrypt($tokenData, 'uid:12345');
echo "Token: {$encrypted}\n";
// 解密驗證
try {
$decrypted = $crypto->decrypt($encrypted, 'uid:12345');
$data = json_decode($decrypted, true);
echo "用戶: {$data['uid']}, 角色: {$data['role']}\n";
} catch (InvalidArgumentException $e) {
echo "Token 無效: {$e->getMessage()}\n";
}
?>
| 場景 | 推薦方案 | 備註 |
|---|---|---|
| API Token / JWT 替代 | AES-GCM + AAD 綁定用戶/裝置 | 短期效期,含過期時間 |
| 資料庫敏感欄位加密 | AES-GCM(固定 IV 不可重用) | 每行獨立 IV,存儲 IV+密文+Tag |
| 跨服務加密通訊 | AES-GCM + 定期輪換金鑰 | 配合 KMS 管理主金鑰 |
| 舊系統相容 | 保留 authcode 解密器 | 僅用於解密歷史資料 |
authcode 是 PHP 早期生態的產物,以巧妙方式在無 OpenSSL 環境下實現了可用的加密。但在現代 PHP(7.1+)環境下,絕不應在新專案使用自定義加密算法。AES-GCM 提供經標準化驗證的認證加密,配合 HKDF 金鑰派生與 AAD 綁定上下文,是當前的最佳實踐。本文提供的 ModernCrypto 類可直接用於生產環境,並作為安全架構的基石。