PHP 內建 openssl_encrypt、sodium_crypto_secretbox 等標準加密函數已足夠應對絕大多數場景。自定義加密 (如本文的 XOR 流式加密) 僅適合:**教學理解原理、低敏感度資料混淆、舊系統相容性維護**。**嚴禁** 用於金融交易、個資保護、醫療記錄等高價值資料。本文提供完整、可執行的自定義加密解密實作,並詳細說明其運作原理、安全性分析與生產環境替代方案。
<?php
/**
* PHP 自定義可逆加密解密工具類
* 警告: 非標準加密演算法,僅供教學/低敏感度場景使用
* 生產環境高敏感資料請使用 OpenSSL AES-GCM 或 Sodium
*/
class CryptoHelper {
/**
* 核心 XOR 運算 (加密/解密通用)
* @param string $data 待處理資料
* @param string $key 金鑰 (已經過 MD5)
* @return string 處理後資料
*/
private static function xorCrypt(string $data, string $key): string {
$key = md5($key);
$keyLen = strlen($key);
$result = '';
$ctr = 0;
for ($i = 0; $i < strlen($data); $i++) {
if ($ctr === $keyLen) $ctr = 0;
$result .= $data[$i] ^ $key[$ctr];
$ctr++;
}
return $result;
}
/**
* 加密
* @param string $plaintext 明文 (任意字串,含中文/二進位)
* @param string $key 使用者金鑰 (建議 16+ 字元高熵值)
* @return string 密文 (Base64,含完整性校驗)
*/
public static function encrypt(string $plaintext, string $key): string {
// 1. 生成隨機會話金鑰 (語意安全性)
$sessionKey = md5((string)random_int(0, PHP_INT_MAX) . microtime(true));
$sessionKeyLen = strlen($sessionKey);
// 2. 首輪: 明文與會話金鑰 XOR,並穿插會話金鑰字符
// 格式: [K1][C1⊕K1][K2][C2⊕K2]...
$firstRound = '';
$ctr = 0;
for ($i = 0; $i < strlen($plaintext); $i++) {
if ($ctr === $sessionKeyLen) $ctr = 0;
$firstRound .= $sessionKey[$ctr] . ($plaintext[$i] ^ $sessionKey[$ctr]);
$ctr++;
}
// 3. 次輪: 使用使用者金鑰加密首輪結果
$secondRound = self::xorCrypt($firstRound, $key);
// 4. 封裝: Base64 + 首尾 MD5 校驗碼
$payload = base64_encode($secondRound);
$integrity = md5($sessionKey) . $payload . md5($sessionKey . $key);
return $integrity;
}
/**
* 解密
* @param string $ciphertext 密文
* @param string $key 使用者金鑰 (需與加密時相同)
* @return string 明文
* @throws Exception 解密失敗 (格式錯誤/金鑰錯誤/資料損壞)
*/
public static function decrypt(string $ciphertext, string $key): string {
// 1. 格式檢查: 至少 64 字元 (32+32 MD5)
if (strlen($ciphertext) < 64) {
throw new Exception('密文格式錯誤:長度不足');
}
// 2. 移除首尾 MD5 校驗碼
$core = substr($ciphertext, 32, -32);
// 3. Base64 解碼 (嚴格模式)
$decoded = base64_decode($core, true);
if ($decoded === false) {
throw new Exception('Base64 解碼失敗:資料可能已損壞');
}
// 4. 次輪解密: 使用使用者金鑰
$firstRound = self::xorCrypt($decoded, $key);
// 5. 還原明文: 解析 [K][C⊕K] 配對
$plaintext = '';
for ($i = 0; $i < strlen($firstRound); $i += 2) {
if ($i + 1 >= strlen($firstRound)) {
throw new Exception('密文格式錯誤:首輪資料長度異常');
}
$k = $firstRound[$i]; // 會話金鑰字符
$xor = $firstRound[$i + 1]; // XOR 結果
$plaintext .= $xor ^ $k; // 還原: (C⊕K)⊕K = C
}
return $plaintext;
}
}
// --- 立即測試 ---
if (php_sapi_name() === 'cli' || basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
header('Content-Type: text/html; charset=utf-8');
$key = 'MySecretKey2024!@#$%^&*'; // 實際請從環境變數讀取
$testData = [
'簡單字串',
'中英混合: Hello 世界 123',
'特殊符號: !@#$%^&*()_+-=[]{}|;:,.<>?',
'JSON: {"user":123,"role":"admin","permissions":["read","write"]}',
'', // 空字串
str_repeat('A', 1000), // 長字串
];
echo '<h3>自定義加密解密測試</h3>';
echo '<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse; font-family:monospace; font-size:12px;">';
echo '<tr><th>原始明文</th><th>密文 (前40字元)</th><th>解密結果</th><th>耗時(ms)</th><th>狀態</th></tr>';
foreach ($testData as $plain) {
$start = microtime(true);
try {
$cipher = CryptoHelper::encrypt($plain, $key);
$decrypted = CryptoHelper::decrypt($cipher, $key);
$elapsed = round((microtime(true) - $start) * 1000, 2);
$ok = ($plain === $decrypted) ? '✅' : '❌';
echo "<tr><td>" . htmlspecialchars(mb_substr($plain, 0, 50)) . "</td>";
echo "<td>" . htmlspecialchars(substr($cipher, 0, 40)) . "...</td>";
echo "<td>" . htmlspecialchars(mb_substr($decrypted, 0, 50)) . "</td>";
echo "<td>$elapsed</td><td>$ok</td></tr>";
} catch (Exception $e) {
echo "<tr><td>" . htmlspecialchars(mb_substr($plain, 0, 50)) . "</td><td colspan='4' style='color:red'>" . $e->getMessage() . "</td></tr>";
}
}
echo '</table>';
}
?>
$sessionKey 確保相同明文每次加密結果不同。| 弱點 | 說明 | 風險等級 |
|---|---|---|
| 已知明文攻擊 (KPA) | 攻擊者若知部分明文 (如固定 JSON 結構),可推導會話金鑰片段,進而破解其他區塊 | 高 |
| 選擇明文攻擊 (CPA) | 攻擊者可任意加密,收集大量密文分析 XOR 模式 | 高 |
| 無認證加密 (AEAD) | MD5 校驗非加密綁定,理論上存在填充預言機攻擊面 | 中 |
| 金鑰重複使用 | 同一金鑰加密大量資料,XOR 流式密碼極易被流密碼分析破解 | 高 |
| 隨機數來源 | rand()/mt_rand() 非密碼學安全 (本碼已改用 random_int) | 中 |
| 側信道攻擊 | 解密過程中字串比較、Base64 解碼耗時可能洩漏資訊 | 低 |
<?php
class StandardCrypto {
private const CIPHER = 'aes-256-gcm';
private const KEY_BYTES = 32;
private const IV_BYTES = 12;
private const TAG_BYTES = 16;
public static function encrypt(string $data, string $key): string {
$key = hash('sha256', $key, true); // 32 bytes
$iv = random_bytes(self::IV_BYTES);
$tag = '';
$ciphertext = openssl_encrypt($data, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag, '', self::TAG_BYTES);
if ($ciphertext === false) throw new Exception('Encryption failed');
// 格式: IV (12) + TAG (16) + CIPHERTEXT
return base64_encode($iv . $tag . $ciphertext);
}
public static function decrypt(string $payload, string $key): string {
$key = hash('sha256', $key, true);
$raw = base64_decode($payload, true);
if (strlen($raw) < self::IV_BYTES + self::TAG_BYTES) throw new Exception('Invalid payload');
$iv = substr($raw, 0, self::IV_BYTES);
$tag = substr($raw, self::IV_BYTES, self::TAG_BYTES);
$ciphertext = substr($raw, self::IV_BYTES + self::TAG_BYTES);
$plaintext = openssl_decrypt($ciphertext, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag, '', self::TAG_BYTES);
if ($plaintext === false) throw new Exception('Decryption failed: wrong key or corrupted data');
return $plaintext;
}
}
?>
<?php
class SodiumCrypto {
public static function encrypt(string $data, string $key): string {
// 金鑰必須為 SODIUM_CRYPTO_SECRETBOX_KEYBYTES (32) 位元組
$key = sodium_crypto_secretbox_keygen(); // 實際應從安全配置讀取
// 或從字串派生: $key = sodium_crypto_generichash($key, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($data, $nonce, $key);
return base64_encode($nonce . $ciphertext);
}
public static function decrypt(string $payload, string $key): string {
$key = sodium_crypto_secretbox_keygen(); // 同加密端
$raw = base64_decode($payload, true);
$nonce = substr($raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($plaintext === false) throw new Exception('Decryption failed');
return $plaintext;
}
}
?>
$_ENV['APP_ENC_KEY'])、配置檔 (權限 600、Git 忽略)、HashiCorp Vault、AWS KMS、Azure Key Vault。本文提供的 CryptoHelper 類別實現了基於 XOR 流式加密的自定義可逆加密解密,代碼結構清晰、支援中文二進位安全、含完整性校驗。但必須重申:**此為教學/低敏感度用途,非密碼學安全標準**。任何涉及法規遵循 (GDPR、HIPAA、PCI-DSS)、高價值資產、用戶隱私的場景,**強制要求使用 OpenSSL AES-GCM 或 Sodium libsodium 等經標準化驗證的加密庫**,並配合完善的金鑰管理體系。安全無捷徑,選對工具才是正道。