MCRYPT_RIJNDAEL_256 ≠ AES-256:Rijndael 支援區塊大小 128/192/256 bits,AES 固定 128 bits。**不相容**。
function ZxingCrypt($data, $mode = 'encode') {
$key = md5('zxing'); // 弱金鑰派生:無鹽、單次 MD5
if ($mode === 'decode') $data = base64_decode($data);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); // 32 bytes
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); // ECB 模式忽略 IV!
if ($mode === 'encode') {
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv);
return base64_encode($crypt);
} else {
return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv);
}
}
| 項目 | 舊 (mcrypt) | 新 (OpenSSL/Sodium) |
|---|---|---|
| 演算法 | Rijndael-256 ECB | AES-256-GCM (OpenSSL) / XChaCha20-Poly1305 (Sodium) |
| 模式 | ECB (不安全) | GCM (AEAD,機密性+完整性) |
| 金鑰派生 | MD5 無鹽 | PBKDF2 (100k+ 迭代) / Argon2id |
| IV/Nonce | 忽略 | 隨機 12 bytes (GCM) / 24 bytes (XChaCha20) |
| 認證標籤 | 無 | 16 bytes (GCM) / 內建 (Sodium) |
function encryptOpenSSL(string $data, string $password): string {
// 1. 金鑰派生
$salt = random_bytes(16);
$key = hash_pbkdf2('sha256', $password, $salt, 100000, 32, true); // 256-bit key
// 2. 隨機 IV
$iv = random_bytes(12); // GCM 建議 12 bytes
// 3. 加密 + 認證標籤
$cipher = openssl_encrypt($data, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '', 16);
// 4. 封裝:salt + iv + tag + ciphertext (Base64 便於儲存/傳輸)
return base64_encode($salt . $iv . $tag . $cipher);
}
function decryptOpenSSL(string $payload, string $password): string|false {
$raw = base64_decode($payload, true);
if ($raw === false) return false;
// 解析封裝
$salt = substr($raw, 0, 16);
$iv = substr($raw, 16, 12);
$tag = substr($raw, 28, 16);
$ciphertext = substr($raw, 44);
$key = hash_pbkdf2('sha256', $password, $salt, 100000, 32, true);
return openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '', 16);
}
function encryptSodium(string $data, string $password): string {
// 金鑰派生 (Argon2id)
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
$key = sodium_crypto_pwhash(
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
$password,
$salt,
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE,
SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13
);
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes
$cipher = sodium_crypto_secretbox($data, $nonce, $key);
return base64_encode($salt . $nonce . $cipher);
}
function decryptSodium(string $payload, string $password): string|false {
$raw = base64_decode($payload, true);
if ($raw === false) return false;
$salt = substr($raw, 0, SODIUM_CRYPTO_PWHASH_SALTBYTES);
$nonce = substr($raw, SODIUM_CRYPTO_PWHASH_SALTBYTES, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = substr($raw, SODIUM_CRYPTO_PWHASH_SALTBYTES + SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$key = sodium_crypto_pwhash(
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
$password,
$salt,
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE,
SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13
);
return sodium_crypto_secretbox_open($cipher, $nonce, $key);
}
function encrypt(string $data, string $password): string {
return encryptSodium($data, $password); // 新資料用新版
}
function decrypt(string $payload, string $password): string|false {
// 偵測版本:Sodium 產出長度固定 (salt 16 + nonce 24 + cipher),mcrypt 為純 Base64 密文
// 簡單判斷:解碼後長度 > 40 且符合 Sodium 結構 → 新版,否則嘗試舊版
$raw = base64_decode($payload, true);
if ($raw !== false && strlen($raw) >= 56) { // 16+24+16(min cipher)
$result = decryptSodium($payload, $password);
if ($result !== false) return $result;
}
// 回退舊版 (僅解密,不再加密)
return decryptLegacyMcrypt($payload, $password);
}
**立即停用 mcrypt**。遷移至 **Sodium (首選)** 或 **OpenSSL AES-256-GCM**,採用 **PBKDF2/Argon2id 金鑰派生**、**隨機 Nonce**、**AEAD 認證加密**。實施雙軌解密相容舊資料,新資料僅用新版。定期輪換主密鑰 (Key Rotation) 並審計加密資料存儲。