需「加密儲存、後續解密還原」的資料:API Token、設定檔敏感欄位、跨系統傳遞參數、暫時性 Token。不適用於密碼儲存(密碼請用 password_hash()/password_verify() 單向雜湊)。
原理:將金鑰經 MD5 擴展為等長密鑰流,與明文逐字元 XOR,再 Base64 編碼輸出。解密為逆向操作。
<?php
declare(strict_types=1);
/**
* 可逆對稱加密
* @param string $data 明文
* @param string $key 密鑰(建議 32+ 字元高熵值字串)
* @return string Base64 編碼密文
* @throws ValueError 金鑰為空
*/
function encrypt(string $data, string $key): string {
if ($key === '') throw new ValueError('Encryption key cannot be empty');
$keyBytes = hash('sha256', $key, true); // 32 bytes 密鑰流種子
$keyStream = '';
$dataLen = strlen($data);
// 擴展密鑰流至資料長度
for ($i = 0; $i < $dataLen; $i += 32) {
$keyStream .= hash('sha256', $keyBytes . pack('N', $i / 32), true);
}
$keyStream = substr($keyStream, 0, $dataLen);
// XOR 加密
$cipher = $data ^ $keyStream;
return base64_encode($cipher);
}
/**
* 可逆對稱解密
* @param string $cipherBase64 Base64 密文
* @param string $key 密鑰(需與加密時相同)
* @return string 明文
* @throws ValueError 密文格式錯誤或金鑰錯誤
*/
function decrypt(string $cipherBase64, string $key): string {
if ($key === '') throw new ValueError('Decryption key cannot be empty');
$cipher = base64_decode($cipherBase64, true);
if ($cipher === false) throw new ValueError('Invalid Base64 ciphertext');
$keyBytes = hash('sha256', $key, true);
$keyStream = '';
$cipherLen = strlen($cipher);
for ($i = 0; $i < $cipherLen; $i += 32) {
$keyStream .= hash('sha256', $keyBytes . pack('N', $i / 32), true);
}
$keyStream = substr($keyStream, 0, $cipherLen);
return $cipher ^ $keyStream;
}
// 使用範例
$data = 'PHP 可逆加密解密演算法示範';
$key = 'my-super-secret-key-32-chars!!'; // 實際應用請從環境變數/金鑰管理系統讀取
$encrypted = encrypt($data, $key);
$decrypted = decrypt($encrypted, $key);
echo "Original: $data\n";
echo "Encrypted: $encrypted\n";
echo "Decrypted: $decrypted\n";
echo "Match: " . ($data === $decrypted ? 'YES' : 'NO') . "\n";
上述 XOR 方案屬「自訂流式加密」,缺乏完整性驗證,易受竄改攻擊。**強烈建議改用 PHP 內建 sodium_crypto_secretbox (XChaCha20-Poly1305) 或 openssl_encrypt (AES-256-GCM)**。
// PHP 7.2+ sodium 擴展(預設內建)
function aeadEncrypt(string $data, string $key): string {
$key = hash('sha256', $key, true); // 確保 32 bytes
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes
$cipher = sodium_crypto_secretbox($data, $nonce, $key);
return base64_encode($nonce . $cipher);
}
function aeadDecrypt(string $payload, string $key): string|false {
$key = hash('sha256', $key, true);
$raw = base64_decode($payload, true);
if ($raw === false) return false;
$nonce = substr($raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = substr($raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
return sodium_crypto_secretbox_open($cipher, $nonce, $key);
}
getenv('APP_ENCRYPTION_KEY')、AWS KMS、HashiCorp Vault、Azure Key Vault 讀取。| 誤區 | 正確做法 |
|---|---|
| 用 MD5 作為金鑰衍生 | 用 SHA-256/HKDF/PBKDF2,MD5 已脆弱 |
| 同一金鑰加密所有資料 | 用途分離、HKDF 衍生 |
| Base64 當作加密 | Base64 僅編碼,零安全性 |
| 自行設計加密演算法 | 使用經審計標準庫 |
| 密碼用可逆加密儲存 | 用 password_hash() 單向雜湊 |
可逆加密在特定場景必要,但實作極易踩坑。現代 PHP 專案請**直接使用 sodium_crypto_secretbox (libsodium) 或 openssl_encrypt (AES-GCM)**,兩者皆為經嚴密審計的 AEAD 模式,同時提供機密性與完整性。搭配妥善的金鑰管理(環境變數、KMS、輪換、用途分離),才能構建真正安全的加密體系。