| 模式 | 加密鑰 | 解密鑰 | 用途 |
|---|---|---|---|
| 公鑰加密 | 公鑰 | 私鑰 | 機密傳輸(僅持有私鑰者可解密) |
| 私鑰加密 | 私鑰 | 公鑰 | 數位簽章(驗證來源、防篡改) |
; php.ini
extension=openssl
; Windows: extension=php_openssl.dll
; Linux: 通常內建或安裝 php-openssl
驗證:php -m | grep openssl 或 phpinfo() 搜尋 OpenSSL
# 生成 RSA 2048 位元私鑰
openssl genrsa -out private.pem 2048
# 從私鑰導出公鑰
openssl rsa -in private.pem -pubout -out public.pem
# 查看金鑰資訊
openssl rsa -in private.pem -text -noout
openssl rsa -in public.pem -pubin -text -noout
<?php
/**
* PKI 加密/簽章工具類
*/
class PkiCrypto {
private $privateKey;
private $publicKey;
private $privateKeyResource;
private $publicKeyResource;
public function __construct(string $privateKeyPath = '', string $publicKeyPath = '') {
if ($privateKeyPath) $this->loadPrivateKey($privateKeyPath);
if ($publicKeyPath) $this->loadPublicKey($publicKeyPath);
}
public function loadPrivateKey(string $path, string $passphrase = ''): bool {
$content = file_get_contents($path);
if ($content === false) return false;
$this->privateKey = $content;
$this->privateKeyResource = openssl_pkey_get_private($content, $passphrase);
return $this->privateKeyResource !== false;
}
public function loadPublicKey(string $path): bool {
$content = file_get_contents($path);
if ($content === false) return false;
$this->publicKey = $content;
$this->publicKeyResource = openssl_pkey_get_public($content);
return $this->publicKeyResource !== false;
}
public function loadPrivateKeyFromString(string $pem, string $passphrase = ''): bool {
$this->privateKey = $pem;
$this->privateKeyResource = openssl_pkey_get_private($pem, $passphrase);
return $this->privateKeyResource !== false;
}
public function loadPublicKeyFromString(string $pem): bool {
$this->publicKey = $pem;
$this->publicKeyResource = openssl_pkey_get_public($pem);
return $this->publicKeyResource !== false;
}
// --- 公鑰加密 / 私鑰解密(機密傳輸) ---
public function publicEncrypt(string $data): ?string {
if (!$this->publicKeyResource) return null;
$success = openssl_public_encrypt($data, $encrypted, $this->publicKeyResource, OPENSSL_PKCS1_PADDING);
return $success ? $encrypted : null;
}
public function privateDecrypt(string $encrypted): ?string {
if (!$this->privateKeyResource) return null;
$success = openssl_private_decrypt($encrypted, $decrypted, $this->privateKeyResource, OPENSSL_PKCS1_PADDING);
return $success ? $decrypted : null;
}
// --- 私鑰加密 / 公鑰解密(簽章驗證) ---
public function privateEncrypt(string $data): ?string {
if (!$this->privateKeyResource) return null;
$success = openssl_private_encrypt($data, $encrypted, $this->privateKeyResource, OPENSSL_PKCS1_PADDING);
return $success ? $encrypted : null;
}
public function publicDecrypt(string $encrypted): ?string {
if (!$this->publicKeyResource) return null;
$success = openssl_public_decrypt($encrypted, $decrypted, $this->publicKeyResource, OPENSSL_PKCS1_PADDING);
return $success ? $decrypted : null;
}
// --- 數位簽章(推薦:SHA256 + 私鑰簽名,公鑰驗證) ---
public function sign(string $data, string $algo = 'sha256'): ?string {
if (!$this->privateKeyResource) return null;
$success = openssl_sign($data, $signature, $this->privateKeyResource, $algo);
return $success ? $signature : null;
}
public function verify(string $data, string $signature, string $algo = 'sha256'): int {
if (!$this->publicKeyResource) return -1;
return openssl_verify($data, $signature, $this->publicKeyResource, $algo);
// 返回 1=成功, 0=失敗, -1=錯誤
}
// --- 方便方法:Base64 編碼輸出 ---
public function publicEncryptBase64(string $data): ?string {
$enc = $this->publicEncrypt($data);
return $enc ? base64_encode($enc) : null;
}
public function privateDecryptBase64(string $b64): ?string {
return $this->privateDecrypt(base64_decode($b64));
}
public function signBase64(string $data, string $algo = 'sha256'): ?string {
$sig = $this->sign($data, $algo);
return $sig ? base64_encode($sig) : null;
}
public function verifyBase64(string $data, string $b64Sig, string $algo = 'sha256'): int {
return $this->verify($data, base64_decode($b64Sig), $algo);
}
public function __destruct() {
if ($this->privateKeyResource) openssl_free_key($this->privateKeyResource);
if ($this->publicKeyResource) openssl_free_key($this->publicKeyResource);
}
}
<?php
require 'PkiCrypto.php';
// 初始化(假設已有 private.pem、public.pem)
$crypto = new PkiCrypto('private.pem', 'public.pem');
$originalData = '敏感資料:訂單金額 10000 元,用戶 ID 12345';
// 1. 公鑰加密 → 私鑰解密(機密傳輸)
$encryptedB64 = $crypto->publicEncryptBase64($originalData);
echo "加密: $encryptedB64\n";
$decrypted = $crypto->privateDecryptBase64($encryptedB64);
echo "解密: $decrypted\n";
assert($decrypted === $originalData);
// 2. 私鑰簽名 → 公鑰驗證(完整性/來源驗證)
$signatureB64 = $crypto->signBase64($originalData, 'sha256');
echo "簽名: $signatureB64\n";
$verifyResult = $crypto->verifyBase64($originalData, $signatureB64, 'sha256');
echo "驗證: " . ($verifyResult === 1 ? '通過' : '失敗') . "\n";
// 3. 篡改測試
$tampered = $originalData . ' (被竄改)';
$verifyResult2 = $crypto->verifyBase64($tampered, $signatureB64, 'sha256');
echo "篡改驗證: " . ($verifyResult2 === 1 ? '通過' : '失敗(預期)') . "\n";
RSA 直接加密資料長度受限(2048-bit 金鑰最大加密 245 字節)。實務上採用 混合加密:
RSA加密的AES金鑰 + IV + AES加密資料<?php
function hybridEncrypt(string $data, $publicKeyResource): array {
// 產生隨機 AES-256 金鑰 (32 bytes) + IV (16 bytes)
$aesKey = random_bytes(32);
$iv = random_bytes(16);
// AES-256-CBC 加密資料
$encryptedData = openssl_encrypt($data, 'aes-256-cbc', $aesKey, OPENSSL_RAW_DATA, $iv);
// RSA 公鑰加密 AES 金鑰
openssl_public_encrypt($aesKey, $encryptedKey, $publicKeyResource, OPENSSL_PKCS1_OAEP_PADDING);
return [
'key' => base64_encode($encryptedKey),
'iv' => base64_encode($iv),
'data' => base64_encode($encryptedData),
];
}
function hybridDecrypt(array $package, $privateKeyResource): ?string {
$encryptedKey = base64_decode($package['key']);
$iv = base64_decode($package['iv']);
$encryptedData = base64_decode($package['data']);
// RSA 私鑰解密 AES 金鑰
openssl_private_decrypt($encryptedKey, $aesKey, $privateKeyResource, OPENSSL_PKCS1_OAEP_PADDING);
// AES 解密資料
return openssl_decrypt($encryptedData, 'aes-256-cbc', $aesKey, OPENSSL_RAW_DATA, $iv);
}
| 常數 | 填充標準 | 建議用途 |
|---|---|---|
OPENSSL_PKCS1_PADDING | PKCS#1 v1.5 | 相容舊系統、簽章 |
OPENSSL_PKCS1_OAEP_PADDING | OAEP (SHA-1) | 加密推薦、較安全 |
OPENSSL_SSLV23_PADDING | SSL v23 | 特定相容需求 |
OPENSSL_NO_PADDING | 無填充 | 特殊用途(手動填充) |
openssl_pkey_get_private() 回傳 false → 檢查 PEM 格式、密碼、檔案權限openssl_public_encrypt() 失敗 → 資料過長(改用混合加密)、金鑰不匹配openssl_verify() 回傳 0 → 簽名不符、演算法不一致、公鑰非對應私鑰openssl_error_string() 可取得詳細錯誤訊息