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

PHP 加密解密工具類:authcode 經典算法解析與現代替代方案

     編輯:PHP 相關知識

PHP 加密解密工具類深度解析

Discuz! 經典的 authcode 函數曾廣泛用於 PHP 專案的 Token 生成、API 簽名、Session 加密等場景。雖然其設計巧妙,但基於自定義流加密算法,不符合現代密碼學最佳實踐。本文深度解析其原理、安全隱患,並提供基於 OpenSSL AES-GCM 的現代替代方案。

authcode 算法原理解析

核心流程:

  1. 金鑰派生:$key = md5($key) → 分為 $keya、$keyb(各 16 字節),$keyc(動態 4 字節,解密時從密文首部取得,加密時從 md5(microtime()) 取得)。
  2. 加密金鑰構建:$cryptkey = $keya . md5($keya . $keyc)(32 字節)。
  3. 明文預處理:加密時前綴 10 位時間戳(過期檢查)+ 16 位雜湊驗證碼(md5($plaintext . $keyb) 前 16 位)+ 明文。
  4. RC4 風格流加密:基於 $cryptkey 初始化 256 字節 S-box,進行異或加密。
  5. 輸出:$keyc . base64_encode($ciphertext)。

安全隱患與不建議繼續使用的原因

問題影響
自定義流加密未經學術界驗證,可能存在未知弱點;RC4 本身已被棄用(RFC 7465)
無完整性驗證僅 16 位 MD5 校驗,極易碰撞,無法防範密文竄改(CBC 掛載攻擊等)
金鑰派生弱單輪 MD5,無鹽值、無迭代,易受彩虹表/暴力破解
IV 固定/可預測$keyc 實際作為 IV,但熵來源不足(microtime 或密文首部)
Base64 含 = 處理移除 = 導致解碼歧義,需補齊

現代替代方案:AES-256-GCM 認證加密

採用 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";
}
?>

遷移指南:從 authcode 升級

  1. 雙寫期:新舊系統並行,新增資料用 AES-GCM,舊資料保留 authcode 解密入口。
  2. 資料遷移:批次解密舊密文,重新用 AES-GCM 加密存回。
  3. 下線舊代碼:確認無舊格式密文後移除 authcode 相關代碼。

適用場景對照表

場景推薦方案備註
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 類可直接用於生產環境,並作為安全架構的基石。

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