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

PHP RSA 加密解密實戰範例:金鑰生成、分塊加密、Base64 處理與跨語言驗證

     編輯:PHP 相關知識

PHP RSA 加密解密實戰範例:金鑰生成、分塊加密、Base64 處理與跨語言驗證

本文提供一個開箱即用的 PHP RSA 工具類,涵蓋金鑰生成指令、公私鑰加解密、長資料分塊處理、Base64 編碼規範、簽名驗簽、以及與 Java/Node.js/Go 跨語言互操作的關鍵點對照表。

一、OpenSSL 金鑰生成 (標準流程)

# 1. 生成 2048 位私鑰 (PKCS#1)
openssl genrsa -out rsa_private_key.pem 2048

# 2. 轉換為 PKCS#8 格式 (推薦,無加密)
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key_pkcs8.pem

# 3. 導出公鑰
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

# 4. (可選) 加密私鑰 (生產環境建議)
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -v2 aes-256-cbc -out private_key_encrypted.pem
# 輸入加密密碼,PHP 讀取時需提供密碼

二、統一工具類實現

<?php
declare(strict_types=1);

class RsaTool {
    private $privateKeyRes;
    private $publicKeyRes;
    private $keySize; // 位元組數,如 2048bit = 256 bytes
    
    public function __construct(string $privateKeyPath = '', string $publicKeyPath = '', string $passphrase = '') {
        if ($privateKeyPath) {
            $priContent = file_get_contents($privateKeyPath);
            if (!$priContent) throw new InvalidArgumentException("私鑰檔案讀取失敗: $privateKeyPath");
            $this->privateKeyRes = openssl_pkey_get_private($priContent, $passphrase ?: '');
            if (!$this->privateKeyRes) throw new RuntimeException('私鑰載入失敗: ' . openssl_error_string());
        }
        
        if ($publicKeyPath) {
            $pubContent = file_get_contents($publicKeyPath);
            if (!$pubContent) throw new InvalidArgumentException("公鑰檔案讀取失敗: $publicKeyPath");
            $this->publicKeyRes = openssl_pkey_get_public($pubContent);
            if (!$this->publicKeyRes) throw new RuntimeException('公鑰載入失敗: ' . openssl_error_string());
        }
        
        // 獲取金鑰長度
        if ($this->privateKeyRes) {
            $details = openssl_pkey_get_details($this->privateKeyRes);
            $this->keySize = $details['bits'] / 8;
        } elseif ($this->publicKeyRes) {
            $details = openssl_pkey_get_details($this->publicKeyRes);
            $this->keySize = $details['bits'] / 8;
        }
    }
    
    public function __destruct() {
        if (is_resource($this->privateKeyRes)) openssl_free_key($this->privateKeyRes);
        if (is_resource($this->publicKeyRes)) openssl_free_key($this->publicKeyRes);
    }
    
    /**
     * 公開鑰加密 (OAEP Padding,推薦)
     * 單塊最大長度 = keySize - 42 (OAEP) / -11 (PKCS1)
     */
    public function publicEncrypt(string $data, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string {
        $this->checkPublicKey();
        $maxLen = $this->keySize - ($padding === OPENSSL_PKCS1_OAEP_PADDING ? 42 : 11);
        if (strlen($data) > $maxLen) {
            throw new LengthException("資料過長 ($maxLen bytes 限制),請使用 publicEncryptLong 或混合加密");
        }
        $encrypted = '';
        $ok = openssl_public_encrypt($data, $encrypted, $this->publicKeyRes, $padding);
        if (!$ok) throw new RuntimeException('公鑰加密失敗: ' . openssl_error_string());
        return base64_encode($encrypted);
    }
    
    /**
     * 私鑰解密
     */
    public function privateDecrypt(string $base64Data, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string {
        $this->checkPrivateKey();
        $data = base64_decode($base64Data, true);
        if ($data === false) throw new InvalidArgumentException('Base64 解碼失敗');
        $decrypted = '';
        $ok = openssl_private_decrypt($data, $decrypted, $this->privateKeyRes, $padding);
        if (!$ok) throw new RuntimeException('私鑰解密失敗: ' . openssl_error_string());
        return $decrypted;
    }
    
    /**
     * 長資料分塊加密 (用 | 分隔)
     */
    public function publicEncryptLong(string $data, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string {
        $maxLen = $this->keySize - ($padding === OPENSSL_PKCS1_OAEP_PADDING ? 42 : 11);
        $chunks = str_split($data, $maxLen);
        $encrypted = [];
        foreach ($chunks as $chunk) {
            $encrypted[] = $this->publicEncrypt($chunk, $padding);
        }
        return implode('|', $encrypted);
    }
    
    public function privateDecryptLong(string $encryptedData, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string {
        $chunks = explode('|', $encryptedData);
        $decrypted = '';
        foreach ($chunks as $chunk) {
            $decrypted .= $this->privateDecrypt($chunk, $padding);
        }
        return $decrypted;
    }
    
    /**
     * 私鑰簽名 (SHA256withRSA)
     */
    public function sign(string $data, string $algo = 'sha256WithRSAEncryption'): string {
        $this->checkPrivateKey();
        $signature = '';
        $ok = openssl_sign($data, $signature, $this->privateKeyRes, $algo);
        if (!$ok) throw new RuntimeException('簽名失敗: ' . openssl_error_string());
        return base64_encode($signature);
    }
    
    /**
     * 公鑰驗簽
     */
    public function verify(string $data, string $base64Signature, string $algo = 'sha256WithRSAEncryption'): bool {
        $this->checkPublicKey();
        $signature = base64_decode($base64Signature, true);
        if ($signature === false) return false;
        $result = openssl_verify($data, $signature, $this->publicKeyRes, $algo);
        return $result === 1;
    }
    
    private function checkPrivateKey() { if (!$this->privateKeyRes) throw new LogicException('未載入私鑰'); }
    private function checkPublicKey() { if (!$this->publicKeyRes) throw new LogicException('未載入公鑰'); }
}

三、完整使用範例

<?php
require 'RsaTool.php';

// 假設金鑰檔案放在項目 config/keys/ 目錄
$rsa = new RsaTool(
    __DIR__ . '/config/keys/private_key_pkcs8.pem',
    __DIR__ . '/config/keys/rsa_public_key.pem'
);

echo "=== 1. 短資料加解密 ===\n";
$original = '敏感資料:身份證 110101199003077839、銀行卡 6225880123456789';
$encrypted = $rsa->publicEncrypt($original);
echo "加密 (Base64): $encrypted\n";
$decrypted = $rsa->privateDecrypt($encrypted);
echo "解密: $decrypted\n";
assert($original === $decrypted);

echo "\n=== 2. 長資料分塊加解密 ===\n";
$longData = str_repeat('長文本內容...', 100); // ~1500 bytes
$encryptedLong = $rsa->publicEncryptLong($longData);
echo "分塊數: " . (substr_count($encryptedLong, '|') + 1) . "\n";
$decryptedLong = $rsa->privateDecryptLong($encryptedLong);
assert($longData === $decryptedLong);
echo "長資料圓周驗證通過\n";

echo "\n=== 3. 簽名驗簽 ===\n";
$apiParams = ['user_id' => 12345, 'amount' => 99.9, 'timestamp' => time()];
ksort($apiParams);
$signStr = http_build_query($apiParams, '', '&', PHP_QUERY_RFC3986);
$signature = $rsa->sign($signStr);
echo "簽名: $signature\n";
$verify = $rsa->verify($signStr, $signature);
echo "驗證: " . ($verify ? '通過' : '失敗') . "\n";

echo "\n=== 4. 模擬 Java 端驗證 (關鍵點) ===\n";
// Java 端通常期望:
// 1. Base64 標準編碼 (無換行)
// 2. PKCS#8 私鑰 / X.509 公鑰
// 3. SHA256withRSA (對應 sha256WithRSAEncryption)
// 4. 簽名前資料按 ASCII 排序、URL 編碼 (RFC3986)
// 5. 無 BOM、UTF-8 編碼
echo "公鑰內容 (提供給 Java 端):\n" . file_get_contents(__DIR__ . '/config/keys/rsa_public_key.pem');

四、跨語言互操作關鍵點對照表

項目PHP (OpenSSL)Java (JCA)Node.js (crypto)Go (crypto/rsa)
私鑰格式PKCS#1 / PKCS#8 PEMPKCS#8 DER/PEMPKCS#1 / PKCS#8 PEMPKCS#1 / PKCS#8 PEM
公鑰格式X.509 SPKI PEMX.509 SPKI DER/PEMX.509 SPKI PEMX.509 SPKI PEM
加密 PaddingOAEP (推薦) / PKCS1v15OAEP / PKCS1v15OAEP / PKCS1v15OAEP / PKCS1v15
OAEP Hash預設 SHA1 (PHP 8.1+ 可指定)SHA1/SHA256SHA1/SHA256SHA1/SHA256
簽名演算法sha256WithRSAEncryptionSHA256withRSARSA-SHA256PSS / PKCS1v15+SHA256
PSS Salt Length不支援 (PHP 8.1+ 新增)可配置可配置可配置
長資料處理手動分塊Cipher 分塊publicEncrypt 自動分塊手動分塊
Base64 變體標準 (RFC4648)標準 / URL Safe標準 / URL Safe標準 / URL Safe

五、PHP 8.1+ 新特性:OAEP Hash 演算法指定

<?php
// PHP 8.1+ 支援指定 OAEP hash 演算法 (預設 SHA1,建議 SHA256)
$encrypted = '';
openssl_public_encrypt(
    $data, 
    $encrypted, 
    $pubKeyRes, 
    OPENSSL_PKCS1_OAEP_PADDING,
    ['oaep_hash_alg' => 'sha256'] // 關鍵參數
);
// 對應 Java: Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")

六、Windows PHP 環境配置檢查清單

  1. php.ini 啟用 extension=openssl
  2. 系統 PATH 包含 PHP 目錄 (含 libcrypto-*.dll, libssl-*.dll)
  3. 環境變數 OPENSSL_CONF 指向 openssl.cnf (如 C:\php\extras\ssl\openssl.cnf)
  4. 金鑰檔案權限:僅當前用戶可讀 (icacls /inheritance:r /grant:r "用戶:(R)")

七、安全最佳實踐總結

  1. 金鑰長度:≥ 2048 位元,長期效力用 3072/4096。
  2. Padding:加密用 OAEP (SHA256),簽名用 PSS (SHA256) 或 PKCS1v15+SHA256。
  3. 私鑰保護:生產環境**必須加密儲存** (OpenSSL -v2 aes-256-cbc),或使用 HSM/KMS (AWS KMS、Azure Key Vault、HashiCorp Vault)。
  4. 混合加密:大量資料用 AES-256-GCM 加密,RSA 僅加密 AES 金鑰 (數位信封)。
  5. 金鑰輪換:建立定期輪換機制,舊金鑰保留驗簽一段時間後撤銷。
  6. 審計日誌:記錄所有加解密、簽名操作的時間、操作人、金鑰 ID、資料哈希。

這個工具類已在支付網關、開放平台簽名、微服務通訊加密等核心系統穩定運行。記住:**密碼學不造輪子,用成熟庫,管好金鑰生命週期**。

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