基於 OpenSSL 擴展,封裝一個功能完整的 RSA 工具類,支援:簽名/驗簽(私鑰簽名、公鑰驗證)、非對稱加密/解密(公鑰加密、私鑰解密)、多種編碼格式(Base64、Hex、二進制)、PKCS#1 填充模式。需配合 X.509 格式證書文件(.crt/.pem)使用。
RSAclass 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() 導致難以追蹤。 |
phpseclib/phpseclib、spomky-labs/otphp、lcobucci/jwt(JWT 封裝),而非手寫類。openssl_encrypt($data, 'aes-256-gcm', $aesKey) + RSA->encrypt($aesKey)。OPENSSL_ALGO_SHA256 + PSS padding) 或 ECDSA (secp256r1/Ed25519),比 PKCS#1 v1.5 更安全。# 生成 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 非對稱加密是現代資安基石。理解其原理、長度限制、填充模式、編碼細節,並善用成熟庫與金鑰管理基礎設施,才能構建真正安全的系統。