原示例使用 MCRYPT_RIJNDAEL_256 (mcrypt),PHP 7.1 棄用,7.2 移除。**現代項目必須遷移至 OpenSSL (openssl_encrypt/decrypt) 或 Sodium (libsodium)**。本文提供 OpenSSL AES-256-CBC 版本(比 ECB 更安全,需 IV)。
<?php
class SecureCookie {
// 應用配置中應存儲 32 字節 Base64 編碼的密鑰
private static $key;
private static $cipher = 'AES-256-CBC'; // 推薦 CBC 模式
private static $options = OPENSSL_RAW_DATA;
public static function init($base64Key) {
self::$key = base64_decode($base64Key);
if (strlen(self::$key) !== 32) {
throw new InvalidArgumentException('Key must be 32 bytes (256 bits) after base64 decode');
}
}
/**
* 加密並設置 Cookie
* @param string $name Cookie 名
* @param mixed $value 任意可序列化數據
* @param int $expire 過期時間戳
* @param string $path 路徑
* @param string $domain 域名
* @param bool $secure 僅 HTTPS
* @param bool $httponly 防 JS 訪問
*/
public static function set($name, $value, $expire = 0, $path = '/', $domain = '', $secure = true, $httponly = true) {
$plain = serialize($value);
$ivLen = openssl_cipher_iv_length(self::$cipher);
$iv = random_bytes($ivLen);
$ciphertext = openssl_encrypt($plain, self::$cipher, self::$key, self::$options, $iv);
if ($ciphertext === false) throw new RuntimeException('Encryption failed');
// 存儲格式: iv(16字節) + ciphertext -> Base64
$cookieValue = base64_encode($iv . $ciphertext);
return setcookie($name, $cookieValue, $expire, $path, $domain, $secure, $httponly);
}
/**
* 讀取並解密 Cookie
* @return mixed|false 解密後的原始值,失敗返回 false
*/
public static function get($name) {
if (!isset($_COOKIE[$name])) return false;
$data = base64_decode($_COOKIE[$name], true);
if ($data === false) return false;
$ivLen = openssl_cipher_iv_length(self::$cipher);
if (strlen($data) < $ivLen) return false;
$iv = substr($data, 0, $ivLen);
$ciphertext = substr($data, $ivLen);
$plain = openssl_decrypt($ciphertext, self::$cipher, self::$key, self::$options, $iv);
if ($plain === false) return false;
$value = @unserialize($plain);
return ($value !== false || $plain === 'b:0;') ? $value : false; // 區分 false 與序列化失敗
}
public static function delete($name, $path = '/', $domain = '') {
return setcookie($name, '', time() - 86400, $path, $domain);
}
}
// --- 初始化 (僅需一次,如 bootstrap) ---
// 生成密鑰: base64_encode(random_bytes(32)) -> 存入配置文件
SecureCookie::init('YOUR_BASE64_ENCODED_32_BYTE_KEY_FROM_CONFIG');
?>
<?php
// 設置加密 Cookie (存儲用戶信息)
$userData = ['uid' => 12345, 'role' => 'admin', 'login_at' => time()];
SecureCookie::set('user_session', $userData, time() + 3600*24*7); // 7天
// 讀取並自動解密
$user = SecureCookie::get('user_session');
if ($user) {
echo "歡迎回來, UID: {$user['uid']}";
} else {
echo "會話無效或已篡改";
}
?>
AES-256-GCM (PHP 7.1+) 或 sodium_crypto_aead_xchacha20poly1305_ietf_encrypt (libsodium),同時提供加密與認證標簽。random_bytes 生成隨機 IV,隨密文存儲(Base64 編碼前綴)。Secure=true (僅 HTTPS)、HttpOnly=true (防 XSS 竊取)、SameSite=Strict/Lax (防 CSRF)。unserialize 可能觸發對象注入 (POP 鏈)。**建議改用 JSON**:json_encode/decode 替代 serialize/unserialize,僅存儲純數據結構。| mcrypt | OpenSSL 替代 | 備註 |
|---|---|---|
| MCRYPT_RIJNDAEL_256 | AES-256 (Rijndael 256 位塊 = AES) | AES 固定 128 位塊,mcrypt 支持 256 位塊,不兼容。需重新加密數據。 |
| MCRYPT_MODE_ECB | 不推薦使用 ECB | ECB 無 IV,相同明文產生相同密文,嚴重洩露模式。必須改用 CBC/GCM。 |
| mcrypt_create_iv | random_bytes() | CSPRNG,更安全。 |
| mcrypt_encrypt/decrypt | openssl_encrypt/decrypt | API 更簡潔,性能更好。 |