在前後端分離架構中,有時需在 PHP 端加密敏感資料 (如 Token、用戶 ID、業務參數),前端 JavaScript 解密使用。本文實作一套「Base64 編碼 + XOR 流加密 + MD5 金鑰衍生」的對稱加密方案,並提供完整的 PHP 加密類與對應的 JavaScript 解密代碼 (含純 JS 實現的 MD5 與 Base64)。
md5('secret_salt') 產生 32 字元十六進位金鑰 (128 bit)。解密為逆向操作:Base64 解碼 → XOR 還原 → Base64 解碼 → 明文。
<?php
class CryptoBridge {
private string $secretSalt;
public function __construct(string $secretSalt = 'your_strong_random_salt_here') {
$this->secretSalt = $secretSalt;
}
/** 獲取 32 字元十六進位金鑰 */
private function getKey(): string {
return md5($this->secretSalt);
}
/** 加密:回傳 Base64 字串 */
public function encrypt(string $plaintext): string {
$b64 = base64_encode($plaintext);
$key = $this->getKey();
$keyLen = strlen($key);
$cipher = '';
for ($i = 0; $i < strlen($b64); $i++) {
$k = $i % $keyLen;
$cipher .= $b64[$i] ^ $key[$k];
}
return base64_encode($cipher);
}
/** 解密:回傳明文 */
public function decrypt(string $ciphertext): string {
$raw = base64_decode($ciphertext, true);
if ($raw === false) throw new Exception('Invalid Base64 ciphertext');
$key = $this->getKey();
$keyLen = strlen($key);
$plain = '';
for ($i = 0; $i < strlen($raw); $i++) {
$k = $i % $keyLen;
$plain .= $raw[$i] ^ $key[$k];
}
$decoded = base64_decode($plain, true);
if ($decoded === false) throw new Exception('Decryption failed: invalid padding or key');
return $decoded;
}
}
// 使用範例
$crypto = new CryptoBridge('my_app_secret_2024');
$encrypted = $crypto->encrypt('user_id=12345&role=admin&expire=1735689600');
echo "Encrypted: $encrypted\n";
// 解密驗證
echo "Decrypted: " . $crypto->decrypt($encrypted) . "\n";
?>
需引入 md5.js 與 base64.js (見下方完整代碼)。
<script src="md5.js"></script>
<script src="base64.js"></script>
<script>
const CRYPTO_SALT = 'my_app_secret_2024'; // 必須與 PHP 端一致
function decrypt(ciphertext) {
const key = md5(CRYPTO_SALT); // 32 字元 hex 字串
const raw = Base64.decode(ciphertext); // 解碼為二進制字串
let plain = '';
for (let i = 0; i < raw.length; i++) {
const k = i % key.length;
plain += String.fromCharCode(raw.charCodeAt(i) ^ key.charCodeAt(k));
}
// 二次 Base64 解碼得到明文
return Base64.decode(plain);
}
// 測試
const encryptedFromPhp = 'U1s1TFN3IQ0reTZbBgJlCA===='; // PHP 加密輸出範例
console.log('Decrypted:', decrypt(encryptedFromPhp)); // 輸出明文
</script>
標準 RFC 1321 實現,無外部依賴,支援 UTF-8 輸入。代碼較長,建議直接下載成熟庫 (如 blueimp-md5),此處僅展示核心結構:
// md5.js - 精簡版結構 (完整代碼請參考 blueimp-md5 或 crypto-js)
var MD5 = (function() {
// 核心常數與循環左移
function rotateLeft(n, s) { return (n << s) | (n >>> (32 - s)); }
// 四輪非線性函數 F, G, H, I
// ... (省略完整 64 步運算)
// 輸出格式控制
var hexcase = 0; // 0=小寫, 1=大寫
var b64pad = "";
return {
hex: function(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); },
b64: function(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); },
// ... 其他輔助方法
};
})();
// 使用方式
const hash = MD5.hex('hello world'); // 5eb63bbbe01eeed093cb22bb8f5acdc3
💡 生產環境強烈建議:使用 npm install blueimp-md5 或 crypto-js,或直接用瀏覽器原生 crypto.subtle.digest('SHA-256', ...) (現代瀏覽器支援),避免手寫加密代碼的安全風險。
支援 UTF-8、URL-safe 變體、Node.js Buffer 相容。同樣建議使用成熟庫 (如 base64-js、js-base64):
// base64.js - 結構示意
const Base64 = {
btoa: (str) => btoa(unescape(encodeURIComponent(str))), // 現代瀏覽器原生支援
atob: (str) => decodeURIComponent(escape(atob(str))),
encode: (str, urlSafe = false) => { /* ... */ },
decode: (str, urlSafe = false) => { /* ... */ }
};
// 現代瀏覽器可直接用
const encoded = btoa('中文測試'); // 5Lit5paH5a2X5rWL6K+V
const decoded = atob(encoded); // 中文測試
| 層面 | 現狀 | 風險 | 改進 |
|---|---|---|---|
| 加密強度 | XOR 流密碼 + 固定金鑰 | 已知明文攻擊可還原金鑰;重放攻擊 | 改用 AES-GCM / ChaCha20-Poly1305 (需引入 Web Crypto API 或 sodium.js) |
| 金鑰管理 | 硬編碼鹽值 | 原始碼洩露即金鑰洩露 | 後端動態下發一次性金鑰 (KMS)、或用 ECDH 協商會話金鑰 |
| 完整性驗證 | 無 | 密文竄改無法檢測 | 加入 HMAC-SHA256 或使用認證加密 (AEAD) |
| 重放防護 | 無 | 舊密文可重複使用 | 明文加入 timestamp/nonce,解密後驗證時效 |
| 前端安全 | 金鑰暴露於前端代碼 | 任何人可解密 | 此方案僅適用「防誠實用戶竄改」或「混淆非核心參數」,不可用於真正機密資料 |
// PHP (firebase/php-jwt)
use Firebase\JWT\JWT;
$token = JWT::encode(['uid' => 123, 'exp' => time()+3600], $secretKey, 'HS256');
// JS (jsrsasign 或直接驗證簽名)
// 前端生成金鑰對、加密/解密、簽名/驗證
const keyPair = await crypto.subtle.generateKey({name: 'RSA-OAEP', modulusLength: 2048}, true, ['encrypt', 'decrypt']);
const encrypted = await crypto.subtle.encrypt({name: 'RSA-OAEP'}, keyPair.publicKey, plaintextBuffer);
// PHP
$key = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = sodium_crypto_secretbox($message, $nonce, $key);
// 發送 nonce + cipher (Base64)
// JS 端用 sodium.js 或 tweetnacl.js 解密
本文提供的 XOR+Base64 方案適合「快速原型、非核心參數混淆、學習加密原理」。任何涉及真實用戶隱私、支付憑證、權限 Token 的場景,**請務必使用經過專業審計的標準加密庫與協議 (JWT, JWE, libsodium, Web Crypto)**,切勿自行拼裝加密邏輯上線。