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

PHP RSA 加密類完整實現:簽名、驗簽、非對稱加解密

     編輯:PHP 相關知識

PHP RSA 加密類完整實現:簽名、驗簽、非對稱加解密

基於 OpenSSL 擴展,封裝一個功能完整的 RSA 工具類,支援:簽名/驗簽(私鑰簽名、公鑰驗證)、非對稱加密/解密(公鑰加密、私鑰解密)、多種編碼格式(Base64、Hex、二進制)、PKCS#1 填充模式。需配合 X.509 格式證書文件(.crt/.pem)使用。

核心類別 RSA

class RSA {
    private $pubKey = null;  // OpenSSL 公鑰資源
    private $priKey = null;  // OpenSSL 私鑰資源

    /**
     * 建構子:載入公鑰/私鑰文件
     * @param string $public_key_file  公鑰文件路徑(.crt/.pem),用於驗簽、加密
     * @param string $private_key_file 私鑰文件路徑(.pem),用於簽名、解密
     */
    public function __construct(string $public_key_file = '', string $private_key_file = '') {
        if ($public_key_file)  $this->_loadPublicKey($public_key_file);
        if ($private_key_file) $this->_loadPrivateKey($private_key_file);
    }

    // ==================== 公開介面 ====================

    /** 簽名
     * @param string $data    待簽名資料
     * @param string $code    輸出編碼:base64 | hex | bin
     * @return string|false 簽名值
     */
    public function sign(string $data, string $code = 'base64') {
        $ret = false;
        if (openssl_sign($data, $ret, $this->priKey)) {
            $ret = $this->_encode($ret, $code);
        }
        return $ret;
    }

    /** 驗簽
     * @param string $data    原始資料
     * @param string $sign    簽名值
     * @param string $code    簽名編碼
     * @return bool
     */
    public function verify(string $data, string $sign, string $code = 'base64'): bool {
        $sign = $this->_decode($sign, $code);
        if ($sign === false) return false;
        $result = openssl_verify($data, $sign, $this->pubKey);
        return $result === 1; // 1=成功, 0=失敗, -1=錯誤
    }

    /** 公鑰加密
     * @param string $data    明文(長度限制:key_size/8 - 11 bytes,如 1024bit key 最大 117 bytes)
     * @param string $code    輸出編碼
     * @param int $padding    填充模式(僅支援 OPENSSL_PKCS1_PADDING)
     * @return string|false 密文
     */
    public function encrypt(string $data, string $code = 'base64', int $padding = OPENSSL_PKCS1_PADDING) {
        if (!$this->_checkPadding($padding, 'en')) $this->_error('Unsupported padding for encrypt');
        $ret = false;
        if (openssl_public_encrypt($data, $result, $this->pubKey, $padding)) {
            $ret = $this->_encode($result, $code);
        }
        return $ret;
    }

    /** 私鑰解密
     * @param string $data    密文
     * @param string $code    密文編碼
     * @param int $padding    填充模式(OPENSSL_PKCS1_PADDING | OPENSSL_NO_PADDING)
     * @param bool $rev       是否反轉位元組序(Microsoft CryptoAPI 相容)
     * @return string|false 明文
     */
    public function decrypt(string $data, string $code = 'base64', int $padding = OPENSSL_PKCS1_PADDING, bool $rev = false) {
        if (!$this->_checkPadding($padding, 'de')) $this->_error('Unsupported padding for decrypt');
        $data = $this->_decode($data, $code);
        if ($data === false) return false;
        $ret = false;
        if (openssl_private_decrypt($data, $result, $this->priKey, $padding)) {
            $ret = $rev ? rtrim(strrev($result), "\0") : $result;
        }
        return $ret;
    }

    // ==================== 私有輔助 ====================

    private function _checkPadding(int $padding, string $type): bool {
        if ($type === 'en') {
            return $padding === OPENSSL_PKCS1_PADDING; // PHP OpenSSL 加密僅支援 PKCS1
        }
        return in_array($padding, [OPENSSL_PKCS1_PADDING, OPENSSL_NO_PADDING], true);
    }

    private function _encode(string $data, string $code): string {
        return match (strtolower($code)) {
            'base64' => base64_encode($data),
            'hex'    => bin2hex($data),
            'bin'    => $data,
            default  => $data,
        };
    }

    private function _decode(string $data, string $code): string|false {
        return match (strtolower($code)) {
            'base64' => base64_decode($data, true), // strict mode
            'hex'    => $this->_hex2bin($data),
            'bin'    => $data,
            default  => $data,
        };
    }

    private function _loadPublicKey(string $file): void {
        $content = $this->_readFile($file);
        $this->pubKey = openssl_get_publickey($content);
        if (!$this->pubKey) $this->_error("Failed to load public key: $file");
    }

    private function _loadPrivateKey(string $file): void {
        $content = $this->_readFile($file);
        $this->priKey = openssl_get_privatekey($content);
        if (!$this->priKey) $this->_error("Failed to load private key: $file");
    }

    private function _readFile(string $file): string {
        if (!file_exists($file)) $this->_error("Key file not found: $file");
        return file_get_contents($file);
    }

    private function _hex2bin(string $hex): string|false {
        return (preg_match('/^[0-9a-fA-F]+$/', $hex)) ? pack('H*', $hex) : false;
    }

    private function _error(string $msg): never {
        throw new RuntimeException('RSA Error: ' . $msg);
    }
}

使用範例

// 測試腳本
header('Content-Type: text/html; charset=utf-8');
require_once 'RSA.php';

$data = '測試資料:中文、English、123、符號!@#';

// 憑證路徑(需自行生成或獲取)
$pubFile = __DIR__ . '/certs/public.crt';  // 公鑰證書
$priFile = __DIR__ . '/certs/private.pem'; // 私鑰(PKCS#8 或 PKCS#1 格式)

try {
    $rsa = new RSA($pubFile, $priFile);

    // 1. 簽名與驗簽
    $sign = $rsa->sign($data, 'base64');
    echo "簽名: $sign\n";
    $verified = $rsa->verify($data, $sign, 'base64');
    echo "驗簽: " . ($verified ? '通過 ✅' : '失敗 ❌') . "\n";

    // 2. 加密與解密
    $encrypted = $rsa->encrypt($data, 'base64');
    echo "加密: $encrypted\n";
    $decrypted = $rsa->decrypt($encrypted, 'base64');
    echo "解密: $decrypted\n";

    // 3. 異常測試:篡改資料驗證失敗
    $tampered = $data . '被篡改';
    $verified2 = $rsa->verify($tampered, $sign, 'base64');
    echo "篡改後驗簽: " . ($verified2 ? '通過' : '失敗 ❌') . "\n";

} catch (Throwable $e) {
    echo "錯誤: " . $e->getMessage() . "\n";
}

關鍵技術細節

項目說明
金鑰長度限制1024bit key:加密明文 ≤ 117 bytes (128-11);2048bit key:≤ 245 bytes。大數據需採用混合加密(AES 加密資料 + RSA 加密 AES Key)。
填充模式加密僅支援 OPENSSL_PKCS1_PADDING;解密支援 PKCS1 與 NO_PADDING(對接 Java/C# 等需注意)。
私鑰格式支援 PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) 與 PKCS#8 (-----BEGIN PRIVATE KEY-----),OpenSSL 自動識別。
公鑰提取可從 .crt 證書提取:openssl x509 -in cert.crt -pubkey -noout > public.pem。
編碼選擇Base64 最通用(JSON/URL 安全需 base64url);Hex 適合日誌/資料庫存儲;Bin 適合二進制協議。
錯誤處理建議將 _error() 改為拋出異常,上層統一捕獲記錄,避免 die() 導致難以追蹤。

生產環境最佳實踐

  1. 使用成熟庫:phpseclib/phpseclib、spomky-labs/otphp、lcobucci/jwt(JWT 封裝),而非手寫類。
  2. 金鑰管理:私鑰存儲於 HSM / Vault / KMS,程式碼中僅引用識別符,絕不硬編碼路徑。
  3. 金鑰輪換:定期輪換金鑰對,保留舊公鑰驗證舊簽名,平滑過渡。
  4. 混合加密:大數據加密採用 openssl_encrypt($data, 'aes-256-gcm', $aesKey) + RSA->encrypt($aesKey)。
  5. 簽名演算法升級:優先使用 RSA-PSS (PHP 7.0+ OPENSSL_ALGO_SHA256 + PSS padding) 或 ECDSA (secp256r1/Ed25519),比 PKCS#1 v1.5 更安全。

OpenSSL 指令快速生成測試金鑰

# 生成 2048bit 私鑰
openssl genrsa -out private.pem 2048

# 提取公鑰
openssl rsa -in private.pem -pubout -out public.pem

# 自簽證書 (可選)
openssl req -new -x509 -key private.pem -out cert.crt -days 365 -subj "/CN=Test RSA"

RSA 非對稱加密是現代資安基石。理解其原理、長度限制、填充模式、編碼細節,並善用成熟庫與金鑰管理基礎設施,才能構建真正安全的系統。

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