<?php
/**
* 加密:明文 + 隨機密鑰流 XOR → Base64
* @param string $str 明文
* @param string $key 主密鑰
* @return string 密文
*/
function passport_encrypt(string $str, string $key): string {
// 1. 生成一次性隨機密鑰流(32 字符 MD5)
srand((double)microtime() * 1000000); // 老版本播種,PHP 4.2+ 自動播種可省略
$encrypt_key = md5(rand(0, 32000)); // 32 字符十六進制字符串
// 2. 明文與密鑰流逐字符 XOR,並插入密鑰流字符作為標記
$ctr = 0;
$tmp = '';
for ($i = 0; $i < strlen($str); $i++) {
$ctr = $ctr === strlen($encrypt_key) ? 0 : $ctr; // 循環使用密鑰流
$tmp .= $encrypt_key[$ctr] . ($str[$i] ^ $encrypt_key[$ctr++]); // 插入密鑰字符 + XOR結果
}
// $tmp 長度 = 2 * strlen($str),格式:k1⊕c1 k2⊕c2 ...
// 3. 二次 XOR:用主密鑰 MD5 加密上述中間文本
return base64_encode(passport_key($tmp, $key));
}
/**
* 解密:Base64 解碼 → 主密鑰 XOR 還原中間文本 → 提取密鑰流字符 → XOR 還原明文
*/
function passport_decrypt(string $str, string $key): string {
$str = passport_key(base64_decode($str), $key); // 還原中間文本
$tmp = '';
for ($i = 0; $i < strlen($str); $i++) {
$md5 = $str[$i]; // 密鑰流字符
$tmp .= $str[++$i] ^ $md5; // 取下一字符 XOR 還原
}
return $tmp;
}
/**
* 核心 XOR 函數:數據與密鑰流循環 XOR
*/
function passport_key(string $str, string $encrypt_key): string {
$encrypt_key = md5($encrypt_key); // 主密鑰衍生 32 字符流
$ctr = 0;
$tmp = '';
for ($i = 0; $i < strlen($str); $i++) {
$ctr = $ctr === strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $str[$i] ^ $encrypt_key[$ctr++];
}
return $tmp;
}
// 測試
$str = '作者:WWW.JB51.NET';
$key = 'jb51net';
$encrypt = passport_encrypt($str, $key);
$decrypt = passport_decrypt($encrypt, $key);
echo "原文:$str\n密文:$encrypt\n譯文:$decrypt\n";
加密流程:
明文 "ABC" (3字節)
│
├─ 生成隨機密鑰流 K = md5(rand()) = "k1k2k3...k32" (32字節)
│
├─ 第一輪 XOR + 插入密鑰字符:
│ i=0: tmp += K[0] . (A ^ K[0]) → "k1⊕A"
│ i=1: tmp += K[1] . (B ^ K[1]) → "k2⊕B"
│ i=2: tmp += K[2] . (C ^ K[2]) → "k3⊕C"
│ 中間文本 M = "k1⊕A k2⊕B k3⊕C" (6字節)
│
├─ 第二輪 XOR:主密鑰 key → md5(key) = "m1m2...m32"
│ M 與 "m1m2..." 循環 XOR → C'
│
└─ Base64 編碼 → 最終密文
解密流程:
密文 → Base64 解碼 → 與 md5(key) XOR 還原 M → 逐對讀取 (K[i], M[i]) → 明文 = K[i] ^ M[i]
| 缺陷 | 影響 | 攻擊示例 |
|---|---|---|
| 隨機數源弱 | rand() 非加密安全,種子空間小(microtime()*1e6 可預測),md5(rand()) 熵極低 | 重放攻擊、密鑰流預測、已知明文攻擊恢復密鑰流 |
| 自定義 XOR 模式 | 插入密鑰字符導致密文長度翻倍,洩露明文長度;密鑰流循環使用,違反一次性密碼本原則 | 頻率分析、已知明文攻擊輕松破解 |
| 無完整性驗證 | 任意修改密文解密後產生可控明文變化,無 MAC/認證標籤 | 填充 oracle 攻擊、密文篡改(如修改金額、權限位) |
| 密鑰衍生弱 | 直接 md5(key),無鹽、無迭代、無記憶體硬度,GPU 可秒級暴力破解弱密碼 | 離線字典攻擊、彩虹表 |
| ECB 模式等價 | 塊大小實際為 1 字節,相同明文字節產生相同密文字節(受隨機密鑰流影響部分緩解,但主密鑰 XOR 為固定流) | 模式識別、重放 |
| Base64 非加密 | 僅編碼,無安全屬性 | 直接可見密文結構 |
// 對稱加密:XChaCha20-Poly1305 (AEAD,帶認證加密)
function encrypt(string $plaintext, string $key): string {
// $key 必須為 SODIUM_CRYPTO_SECRETBOX_KEYBYTES (32字節) 隨機生成
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24字節
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
// 存儲:nonce + ciphertext (Base64 或二進制)
return base64_encode($nonce . $ciphertext);
}
function decrypt(string $encoded, string $key): string|false {
$data = base64_decode($encoded);
$nonce = substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
return sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
}
// 密鑰生成與存儲
$key = sodium_crypto_secretbox_keygen(); // 32字節隨機
// 安全存儲:環境變量、密鑰管理系統、硬件安全模塊 (HSM)
// 切勿硬編碼在代碼中!
function openssl_encrypt_aead(string $plaintext, string $key, string $aad = ''): string {
$ivLen = 12; // GCM 建議 96 位 IV
$iv = random_bytes($ivLen);
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
$aad, // 附加認證數據
16 // 標籤長度
);
if ($ciphertext === false) throw new Exception('Encryption failed');
return base64_encode($iv . $tag . $ciphertext);
}
function openssl_decrypt_aead(string $encoded, string $key, string $aad = ''): string|false {
$data = base64_decode($encoded);
$ivLen = 12;
$tagLen = 16;
$iv = substr($data, 0, $ivLen);
$tag = substr($data, $ivLen, $tagLen);
$ciphertext = substr($data, $ivLen + $tagLen);
return openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
$aad
);
}
加密是專業領域,**切勿自行設計加密算法**。PHP 現代版本內建 sodium(基於 libsodium,經過密碼學專家審計、標準化、硬件加速),配合標準化的密鑰管理流程,才是保護數據機密性、完整性、真實性的正確道路。將原文代碼作為教學案例研究其缺陷,而非複製粘貼進項目。