用戶密碼儲存應使用單向雜湊(如 bcrypt/Argon2),但某些業務場景(API Token、第三方憑證、配置文件敏感值)需可逆加密。本文提供一個基於 OpenSSL AES-256-ECB 的加解密類,含 URL 安全 Base64 編碼、金鑰派生、異常處理。
<?php
class Crypt {
private string $key; // 32 位元組二進制金鑰
public function __construct(string $secret) {
// 使用 SHA-256 將任意長度密鑰派生為 32 字節
$this->key = hash('sha256', $secret, true);
}
/** URL 安全 Base64 編碼(移除 + / =) */
private function safeB64Encode(string $data): string {
$b64 = base64_encode($data);
return str_replace(['+', '/', '='], ['-', '_', ''], $b64);
}
/** URL 安全 Base64 解碼(自動補齊 =) */
private function safeB64Decode(string $data): string {
$b64 = str_replace(['-', '_'], ['+', '/'], $data);
$mod4 = strlen($b64) % 4;
if ($mod4) {
$b64 .= substr('====', $mod4);
}
return base64_decode($b64);
}
/** 加密:返回 URL 安全字串 */
public function encrypt(string $value): ?string {
if ($value === '') return null;
// AES-256-ECB 不需 IV,但 OpenSSL 要求提供,傳空字串
$ciphertext = openssl_encrypt($value, 'AES-256-ECB', $this->key, OPENSSL_RAW_DATA);
if ($ciphertext === false) {
throw new RuntimeException('加密失敗: ' . openssl_error_string());
}
return $this->safeB64Encode($ciphertext);
}
/** 解密:返回原始明文 */
public function decrypt(string $value): ?string {
if ($value === '') return null;
$ciphertext = $this->safeB64Decode($value);
$plaintext = openssl_decrypt($ciphertext, 'AES-256-ECB', $this->key, OPENSSL_RAW_DATA);
if ($plaintext === false) {
throw new RuntimeException('解密失敗: ' . openssl_error_string());
}
return $plaintext;
}
}
$crypt = new Crypt('my-super-secret-key-2024'); // 實際應從環境變數讀取
$original = 'sk_live_abcdef1234567890'; // 敏感 API Key
$encrypted = $crypt->encrypt($original);
echo "加密: $encrypted\n";
$decrypted = $crypt->decrypt($encrypted);
echo "解密: $decrypted\n";
// 驗證
var_dump($original === $decrypted); // true
| 項目 | 現狀 | 建議 |
|---|---|---|
| 模式 | ECB (Electronic Codebook) | 生產環境強烈建議改用 CBC 或 GCM 模式,ECB 模式相同明文塊產生相同密文塊,洩漏模式特徵 |
| IV (初始化向量) | ECB 不使用 | CBC/GCM 需為每次加密生成隨機 IV (16 字節),並與密文一起儲存(如 IV + 密文) |
| 金鑰管理 | 硬編碼/配置文件 | 使用環境變數、HashiCorp Vault、AWS KMS 等密鑰管理系統 |
| 完整性驗證 | 無 | GCM 模式內建認證標籤;CBC 需額外 HMAC (Encrypt-then-MAC) |
| 密鑰輪換 | 不支援 | 在密文前加版本號,支援多版本金鑰並行解密、平滑遷移 |
public function encryptGCM(string $value): string {
$iv = random_bytes(16); // GCM 建議 12 字節,16 也可
$tag = '';
$ciphertext = openssl_encrypt($value, 'AES-256-GCM', $this->key, OPENSSL_RAW_DATA, $iv, $tag);
// 儲存格式: IV (16) + TAG (16) + CIPHERTEXT
return $this->safeB64Encode($iv . $tag . $ciphertext);
}
public function decryptGCM(string $value): string {
$data = $this->safeB64Decode($value);
$iv = substr($data, 0, 16);
$tag = substr($data, 16, 16);
$ciphertext = substr($data, 32);
$plaintext = openssl_decrypt($ciphertext, 'AES-256-GCM', $this->key, OPENSSL_RAW_DATA, $iv, $tag);
if ($plaintext === false) throw new RuntimeException('GCM 解密失敗/資料被竄改');
return $plaintext;
}
結論:本類適合快速原型、內部工具、非極高安全場景。處理支付憑證、個人身份信息等敏感數據時,請務必使用 GCM 模式並妥善管理金鑰。