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

PHP mcrypt 可逆加密遷移指南:MCRYPT_RIJNDAEL_256 ECB 模式分析與 OpenSSL/Sodium 現代替代方案

     編輯:PHP 綜合知識

mcrypt 現狀:已棄用、已移除

  • PHP 7.1 棄用,PHP 7.2 移至 PECL,PHP 8.0 核心移除。
  • **ECB 模式不安全**:相同明文塊產生相同密文塊,洩露模式資訊 (參考 ECB 模式企鵝圖)。
  • 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);
    }
}

遷移目標:AES-256-GCM (認證加密) + PBKDF2 金鑰派生

項目舊 (mcrypt)新 (OpenSSL/Sodium)
演算法Rijndael-256 ECBAES-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)

方案一:OpenSSL (PHP 7.1+ 內建、廣泛相容)


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);
}

方案二:Sodium (PHP 7.2+ 內建、現代、抗側信道、API 簡潔)


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) 並審計加密資料存儲。

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