首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 使用 TripleDES (3DES) 實現加密解密:完整實作、坑位避雷與現代替代方案

     編輯:PHP 相關知識

TripleDES (3DES) 作為 DES 的增強版,曾廣泛用於支付、金融、老系統對接。本文提供可直接落地的 PHP 實作,深度剖析 ECB 模式缺陷、金鑰派生、IV 處理、填充機制等關鍵細節,並給出遷移至 AES-GCM/ChaCha20-Poly1305 的現代化路徑。

一、核心實作:相容 mcrypt 風格的 OpenSSL 版本 (PHP 7.2+)

注意:mcrypt 擴充套件已於 PHP 7.2 棄用、7.3 移除。現代專案**必須使用 openssl** 或 sodium。

<?php
/**
 * TripleDES 加密解密工具類 (OpenSSL 實作,相容 PKCS#7 填充)
 */
class TripleDesCipher
{
    private string $cipher = 'des-ede3';     // OpenSSL 名稱:3DES-EDE (三金鑰)
    private int $keyLength = 24;             // 3DES 需 24 位元組 (192 bits)
    private int $ivLength = 8;               // DES 區塊大小 64 bits = 8 bytes
    private string $paddingMethod = 'pkcs7'; // PKCS#7 標準填充

    /**
     * 從任意長度密碼派生 24 位元組金鑰 (相容舊系統常見 MD5 截取)
     * 生產環境建議使用 PBKDF2/Argon2/Scrypt + 隨機鹽值
     */
    private function deriveKey(string $password): string
    {
        // 舊系統常見:md5($password) 取前 24 字元 (16 字元 hex = 8 bytes,不足 24)
        // 修正:使用 hash('sha256', $password, true) 取前 24 bytes
        return substr(hash('sha256', $password, true), 0, $this->keyLength);
    }

    /**
     * 加密
     * @param string $plaintext 明文
     * @param string $password 密碼/金鑰
     * @param bool $base64 輸出是否 Base64 編碼
     * @return string 密文 (Base64 或原始二進制)
     * @throws Exception
     */
    public function encrypt(string $plaintext, string $password, bool $base64 = true): string
    {
        $key = $this->deriveKey($password);
        
        // **關鍵:每次加密生成隨機 IV**,ECB 模式不需 IV 但極度不安全,這裡強制使用 CBC
        $iv = random_bytes($this->ivLength);
        
        // OpenSSL 自動處理 PKCS#7 填充 (OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING 需手動)
        $ciphertext = openssl_encrypt(
            $plaintext,
            $this->cipher,
            $key,
            OPENSSL_RAW_DATA,     // 輸出原始二進制
            $iv                   // CBC 模式必須提供 IV
        );
        
        if ($ciphertext === false) {
            throw new Exception('Encryption failed: ' . openssl_error_string());
        }
        
        // 組合:IV (8 bytes) + 密文,解密時需分離
        $combined = $iv . $ciphertext;
        
        return $base64 ? base64_encode($combined) : $combined;
    }

    /**
     * 解密
     * @param string $ciphertextBase64 Base64 編碼的密文 (含 IV 前綴)
     * @param string $password 密碼/金鑰
     * @return string 明文
     * @throws Exception
     */
    public function decrypt(string $ciphertextBase64, string $password): string
    {
        $combined = base64_decode($ciphertextBase64, true);
        if ($combined === false) {
            throw new Exception('Invalid Base64 input');
        }
        
        if (strlen($combined) < $this->ivLength) {
            throw new Exception('Ciphertext too short to contain IV');
        }
        
        // 分離 IV 與密文
        $iv = substr($combined, 0, $this->ivLength);
        $ciphertext = substr($combined, $this->ivLength);
        
        $key = $this->deriveKey($password);
        
        $plaintext = openssl_decrypt(
            $ciphertext,
            $this->cipher,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );
        
        if ($plaintext === false) {
            throw new Exception('Decryption failed: ' . openssl_error_string());
        }
        
        return $plaintext;
    }
}

// === 使用範例 ===
$cipher = new TripleDesCipher();
$password = 'MySuperSecretKey2024!';
$data = '敏感資料:身分證 A123456789、信用卡 4242-4242-4242-4242';

try {
    $encrypted = $cipher->encrypt($data, $password);
    echo "Encrypted (Base64): $encrypted\n";
    
    $decrypted = $cipher->decrypt($encrypted, $password);
    echo "Decrypted: $decrypted\n";
    
    assert($decrypted === $data, 'Round-trip failed');
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

二、關鍵技術細節深度解析

1. 金鑰長度與 EDE 模式

  • 3DES 三金鑰模式 (Keying Option 1):K1, K2, K3 互不相同,共 24 bytes (168 有效位,含 24 校驗位)
  • OpenSSL des-ede3:對應 3-Key TripleDES (Encrypt-Decrypt-Encrypt)
  • 兩金鑰模式 (Option 2):K1=K3,16 bytes,OpenSSL 用 des-ede,**安全性較低,不推薦**
模式IV 需求安全性適用場景
ECB (Electronic Codebook)無**極差**:相同明文塊產生相同密文塊,圖像可見輪廓**嚴禁用於機密資料**
CBC (Cipher Block Chaining)隨機 IV (8 bytes)良好:需隨機 IV、正確填充、防填充神諭攻擊**標準選擇** (配合 HMAC 或改用 GCM)
CTR / OFB / CFBNonce/IV良好:串流模式、可並行、無填充特定場景 (需同步計數器)
GCM / CCMNonce (12 bytes 推薦)**最佳**:認證加密 (AEAD)、完整性+機密性**現代首選** (AES-GCM, ChaCha20-Poly1305)

原文範例使用 ECB 且 IV 固定/隨機但未儲存,**無法正確解密**,且洩露明文模式。

3. 填充機制:PKCS#7 (標準) vs Zero Padding

  • PKCS#7:填充 N 個值為 N 的位元組 (1~8),OpenSSL 預設支援
  • Zero Padding:填充 0x00,**無法區分原始資料結尾的 0x00**,不推薦
  • 手動填充時:OPENSSL_ZERO_PADDING 標誌需自行處理

4. IV 管理鐵律

  1. 每次加密唯一隨機:random_bytes(8)
  2. 不需保密,但**不可預測、不可重複**
  3. 隨密文儲存/傳輸:通常作為密文前綴 (如本實作)
  4. 解密時精確分離:固定長度切割

三、常見相容性坑位對照表

場景舊系統 (mcrypt/Java/.NET/Python) 常見做法PHP OpenSSL 對應調整
金鑰派生MD5(password) 取 16/24 字元 hex / 直接用 passwordderiveKey() 模擬邏輯,或協商改用 PBKDF2
模式ECB (無 IV)**拒絕相容 ECB**,協商升級 CBC+隨機 IV
IV 來源固定全 0、固定字串、密碼派生隨機生成,前綴輸出
填充PKCS#5/7、Zero、ISO 10126、無填充OpenSSL 預設 PKCS#7,其餘需手動
輸出格式Base64、Hex、原始二進制、IV+密文、密文+IV協商格式,建議 Base64(IV||密文)
字元編碼UTF-8、GBK、Latin1明確指定 UTF-8,加密前統一編碼

四、現代化遷移路徑:棄用 3DES,擁抱 AES-GCM / ChaCha20-Poly1305

為什麼棄用 3DES?

  • Sweet32 攻擊 (CVE-2016-2183):64-bit 區塊大小導致生日攻擊可行 (TLS/VPN 長連線)
  • 效能低:3 次 DES 運算,比 AES 慢 10-20 倍 (無 AES-NI 時)
  • NIST SP 800-131A Rev. 2:2023 年後禁用 3DES 加密保護敏感資料
  • PCI DSS 4.0:禁止使用 3DES 作為加密演算法

AES-GCM 現代實作 (PHP 7.1+ OpenSSL, PHP 8.1+ 原生支援)

<?php
/**
 * 現代認證加密:AES-256-GCM (PHP 7.1+)
 * 優點:AEAD、高效能 (AES-NI)、標準化、無填充神諭攻擊
 */
class ModernAeadCipher
{
    private string $cipher = 'aes-256-gcm';
    private int $keyLength = 32;  // 256 bits
    private int $ivLength = 12;   // GCM 建議 96 bits (12 bytes)
    private int $tagLength = 16;  // 認證標籤 128 bits

    /** 從密碼派生金鑰 (生產請用 Argon2ID/PBKDF2 + 儲存鹽值) */
    private function deriveKey(string $password, string $salt = ''): string
    {
        if ($salt === '') $salt = random_bytes(16);
        // PBKDF2: 100,000 迭代,SHA-256
        return hash_pbkdf2('sha256', $password, $salt, 100_000, $this->keyLength, true);
    }

    public function encrypt(string $plaintext, string $password, string $associatedData = ''): string
    {
        $salt = random_bytes(16);
        $key = $this->deriveKey($password, $salt);
        $iv = random_bytes($this->ivLength);
        
        $ciphertext = openssl_encrypt(
            $plaintext,
            $this->cipher,
            $key,
            OPENSSL_RAW_DATA,
            $iv,
            $tag,           // 由引用返回認證標籤
            $associatedData, // AAD: 關聯資料 (如用戶ID、時間戳),不加密但驗證完整性
            $this->tagLength
        );
        
        if ($ciphertext === false) throw new Exception('AES-GCM encryption failed');
        
        // 組合:Salt(16) + IV(12) + Tag(16) + Ciphertext
        $combined = $salt . $iv . $tag . $ciphertext;
        return base64_encode($combined);
    }

    public function decrypt(string $payloadBase64, string $password, string $associatedData = ''): string
    {
        $combined = base64_decode($payloadBase64, true);
        if ($combined === false) throw new Exception('Invalid Base64');
        
        $minLen = 16 + 12 + 16; // Salt + IV + Tag
        if (strlen($combined) < $minLen) throw new Exception('Payload too short');
        
        $salt = substr($combined, 0, 16);
        $iv = substr($combined, 16, 12);
        $tag = substr($combined, 28, 16);
        $ciphertext = substr($combined, 44);
        
        $key = $this->deriveKey($password, $salt);
        
        $plaintext = openssl_decrypt(
            $ciphertext,
            $this->cipher,
            $key,
            OPENSSL_RAW_DATA,
            $iv,
            $tag,
            $associatedData
        );
        
        if ($plaintext === false) throw new Exception('Decryption failed: authentication tag mismatch or key error');
        
        return $plaintext;
    }
}

// Sodium 更簡潔 (PHP 7.2+), 內建 Argon2ID 金鑰派生 + XChaCha20-Poly1305
// sodium_crypto_aead_xchacha20poly1305_ietf_encrypt/decrypt
?>

五、遷移檢查清單

  • [ ] 盤點所有使用 3DES/DES/RC4/MD5/SHA1 的加密場景
  • [ ] 評估相容性需求:是否需解密舊資料 (雙向遷移) 或僅新資料用新演算法
  • [ ] 選定新演算法:AES-256-GCM (硬體加速) 或 XChaCha20-Poly1305 (軟體高速、抗旁路)
  • [ ] 設計金鑰管理:主金鑰輪換、資料加密金鑰 (DEK) 包裹、KMS/HSM 整合
  • [ ] 實作版本化載荷:v1|base64(salt|iv|tag|ct),支援多演算法並存平滑遷移
  • [ ] 完整測試:正向/反向、錯誤金鑰、截斷資料、重放攻擊、效能基準
  • [ ] 合規驗證:PCI DSS、GDPR、ISO 27001、國密標準 (SM4-GCM)

六、總結

  1. **停止使用 mcrypt**,全面遷移至 OpenSSL 或 Sodium
  2. **棄用 ECB 模式**,強制使用 **CBC + 隨機 IV** 或 **GCM/CCM (AEAD)**
  3. **3DES 已淘汰**,新專案直接採用 **AES-256-GCM** 或 **ChaCha20-Poly1305**
  4. 金鑰派生使用 **Argon2ID / PBKDF2 + 高迭代 + 隨機鹽值**,而非簡單雜湊截取
  5. 密文格式**版本化、自描述** (含鹽值、IV、Tag、演算法標識),利於未來升級

加密是系統安全的基石,演算法選型與金鑰管理同樣重要。定期審視密碼學組件,跟進標準演進,是守護資料資產的長期策略。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved