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

PHP 對稱加密算法實戰:從 mcrypt 到 OpenSSL 的遷移指南

     編輯:PHP 相關知識

前言

在 PHP 開發中,對稱加密是保護敏感數據(如 API 金鑰、用戶 Token、配置文件密碼)的常用手段。早期開發者多依賴 mcrypt 擴展,但自 PHP 7.1 起已棄用,PHP 7.2 完全移除。本文將梳理經典 mcrypt 代碼遷移至現代 openssl 的最佳實踐,並提供一個開箱即用的加密工具類。

核心概念速覽

  • 算法:AES-256-CBC 是目前推薦標準,取代舊式 RIJNDAEL-256-ECB。
  • 模式:CBC 模式需初始化向量(IV),ECB 模式不需 IV 但安全性較弱。
  • 填充:OpenSSL 預設 PKCS#7,mcrypt 常用 PKCS#5(實際相容)。
  • 金鑰派生:建議使用 hash('sha256', $key, true) 產生 32 字節金鑰,而非直接使用任意長度字串。

遷移對照表

mcrypt 函數OpenSSL 替代方案
mcrypt_encrypt()openssl_encrypt()
mcrypt_decrypt()openssl_decrypt()
mcrypt_create_iv()openssl_random_pseudo_bytes() 或 random_bytes()
mcrypt_get_iv_size()openssl_cipher_iv_length()

通用加密工具類(支援 AES-256-CBC)

<?php
namespace App\Security;

class SymmetricCrypt
{
    private const CIPHER = 'AES-256-CBC';
    private const KEY_LENGTH = 32; // 256 bits
    private const IV_LENGTH = 16;  // 128 bits

    /** @var string 原始金鑰(建議從環境變數讀取) */
    private string $rawKey;

    public function __construct(string $rawKey)
    {
        $this->rawKey = $rawKey;
    }

    /** 取得派生後的 32 字節金鑰 */
    private function getKey(): string
    {
        return hash('sha256', $this->rawKey, true);
    }

    /** 加密並回傳 Base64(含 IV 前綴) */
    public function encrypt(string $plaintext): string
    {
        $iv = random_bytes(self::IV_LENGTH);
        $ciphertext = openssl_encrypt(
            $plaintext,
            self::CIPHER,
            $this->getKey(),
            OPENSSL_RAW_DATA,
            $iv
        );
        if ($ciphertext === false) {
            throw new \RuntimeException('加密失敗:' . openssl_error_string());
        }
        // IV + 密文 → Base64,解密時先切割 IV
        return base64_encode($iv . $ciphertext);
    }

    /** 解密 Base64 字串 */
    public function decrypt(string $payload): string
    {
        $data = base64_decode($payload, true);
        if ($data === false) {
            throw new \InvalidArgumentException('非法的 Base64 載荷');
        }
        if (strlen($data) < self::IV_LENGTH) {
            throw new \InvalidArgumentException('載荷長度不足');
        }
        $iv = substr($data, 0, self::IV_LENGTH);
        $ciphertext = substr($data, self::IV_LENGTH);
        $plaintext = openssl_decrypt(
            $ciphertext,
            self::CIPHER,
            $this->getKey(),
            OPENSSL_RAW_DATA,
            $iv
        );
        if ($plaintext === false) {
            throw new \RuntimeException('解密失敗:資料可能已竄改或金鑰錯誤');
        }
        return $plaintext;
    }
}

使用範例

<?php
require_once 'vendor/autoload.php';

$key = getenv('APP_ENCRYPTION_KEY') ?: 'your-32-char-secret-key-here!!';
$crypt = new App\Security\SymmetricCrypt($key);

$original = '敏感資料:API_SECRET=sk_live_xxx...';
$encrypted = $crypt->encrypt($original);
$decrypted = $crypt->decrypt($encrypted);

echo "原文:$original\n";
echo "密文:$encrypted\n";
echo "解密:$decrypted\n";
assert($original === $decrypted);

常見坑點與對策

  1. 金鑰長度不足:務必用 SHA-256 派生 32 字節,避免直接傳入短字串導致安全性下降。
  2. IV 重複使用:每次加密必須產生新的隨機 IV,並隨密文儲存(如上例前綴法)。
  3. 編碼問題:OpenSSL OPENSSL_RAW_DATA 模式輸出二進位,務必自行 Base64 編碼;預設模式會雙重 Base64 導致解密失敗。
  4. 錯誤處理:openssl_encrypt/decrypt 失敗回傳 false,需搭配 openssl_error_string() 診斷。

進階:支援多種算法的 Xcrypt 類遷移版

若專案需相容舊版 DES / AES-128 / AES-256 並支援 CBC/ECB/OFB/CFB 模式,可參考下列重構版(已移除 mcrypt 依賴):

<?php
namespace App\Security;

class Xcrypt
{
    private string $cipher;
    private string $key;
    private string $iv;

    /** 支援 key 長度:16(DES/AES-128), 24(AES-192), 32(AES-256) */
    public function __construct(string $key, string $mode = 'cbc', string $iv = 'auto')
    {
        $klen = strlen($key);
        $this->key = hash('sha256', $key, true); // 統一派生 32 字節
        $this->cipher = match ($klen) {
            8  => 'DES-CBC',     // 僅相容舊資料,新專案勿用
            16 => 'AES-128-CBC',
            24 => 'AES-192-CBC',
            32 => 'AES-256-CBC',
            default => throw new \InvalidArgumentException('金鑰長度需為 8/16/24/32 字節'),
        };
        // 模式映射(OpenSSL 命名慣例)
        $this->cipher = str_replace('CBC', strtoupper($mode), $this->cipher);

        // IV 處理
        $ivLen = openssl_cipher_iv_length($this->cipher);
        if ($iv === 'off') {
            $this->iv = '';
        } elseif ($iv === 'auto') {
            $this->iv = random_bytes($ivLen);
        } else {
            if (strlen($iv) !== $ivLen) {
                throw new \InvalidArgumentException("IV 長度需為 $ivLen 字節");
            }
            $this->iv = $iv;
        }
    }

    public function getIV(string $encoding = 'base64'): string
    {
        return match ($encoding) {
            'base64' => base64_encode($this->iv),
            'hex'    => bin2hex($this->iv),
            'bin'    => $this->iv,
            default  => throw new \InvalidArgumentException('不支援的編碼'),
        };
    }

    public function encrypt(string $data, string $encoding = 'base64'): string
    {
        $ct = openssl_encrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA, $this->iv);
        if ($ct === false) throw new \RuntimeException('加密失敗');
        return match ($encoding) {
            'base64' => base64_encode($ct),
            'hex'    => bin2hex($ct),
            'bin'    => $ct,
        };
    }

    public function decrypt(string $data, string $encoding = 'base64'): string
    {
        $bin = match ($encoding) {
            'base64' => base64_decode($data, true),
            'hex'    => hex2bin($data),
            'bin'    => $data,
        };
        if ($bin === false) throw new \InvalidArgumentException('編碼格式錯誤');
        $pt = openssl_decrypt($bin, $this->cipher, $this->key, OPENSSL_RAW_DATA, $this->iv);
        if ($pt === false) throw new \RuntimeException('解密失敗');
        return $pt;
    }
}

總結

淘汰 mcrypt、擁抱 OpenSSL 是 PHP 專案現代化的必經之路。掌握金鑰派生、IV 管理、編碼規範三大核心,即可構建安全、可維護的對稱加密體系。建議將金鑰存放於環境變數或密碼管理系統(如 Vault),切勿硬編碼於代碼庫中。

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