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

PHP OpenSSL 數位簽章實戰:PKCS#12 私鑰簽名、PEM 公鑰驗證、多演算法支援

     編輯:PHP 綜合知識

金流、API 對接、電子合約常需數位簽章確保資料完整性、不可否認性。PHP openssl_* 函數提供完整 PKI 操作。本文實戰演示:載入 PKCS#12 (.p12/.pfx) 私鑰簽名、載入 PEM 公鑰驗證、支援 SHA1/SHA256/SM3 等演算法。

核心流程

  1. 簽名端:讀取 .p12 憑證 → 提取私鑰 → openssl_sign() → 輸出二進位簽名。
  2. 驗證端:讀取 .pem 公鑰證書 → openssl_verify() → 回傳 1=成功/0=失敗/-1=錯誤。

完整工具類

<?php
class DigitalSigner {
    private $p12Path;
    private $p12Pass;
    private $pemPath;

    public function __construct(string $p12Path, string $p12Pass, string $pemPath = '') {
        $this->p12Path = $p12Path;
        $this->p12Pass = $p12Pass;
        $this->pemPath = $pemPath;
    }

    // 簽名(預設 SHA1,可指定 SHA256 等)
    public function sign(string $data, int $algo = OPENSSL_ALGO_SHA1): string {
        $pkcs12 = file_get_contents($this->p12Path);
        if (!openssl_pkcs12_read($pkcs12, $certs, $this->p12Pass)) {
            throw new RuntimeException('PKCS#12 讀取失敗,請檢查密碼');
        }
        $privateKey = $certs['pkey'] ?? throw new RuntimeException('私鑰不存在');
        
        $signature = '';
        if (!openssl_sign($data, $signature, $privateKey, $algo)) {
            throw new RuntimeException('簽名失敗: ' . openssl_error_string());
        }
        return $signature; // 二進位原始簽名
    }

    // 驗證(用同一 .p12 的公鑰自驗)
    public function verifySelf(string $data, string $signature, int $algo = OPENSSL_ALGO_SHA1): bool {
        $pkcs12 = file_get_contents($this->p12Path);
        if (!openssl_pkcs12_read($pkcs12, $certs, $this->p12Pass)) return false;
        $publicKey = $certs['cert'] ?? throw new RuntimeException('公鑰不存在');
        $ok = openssl_verify($data, $signature, $publicKey, $algo);
        return $ok === 1;
    }

    // 驗證(用外部 PEM 公鑰,如對方給的驗證憑證)
    public function verifyWithPem(string $data, string $signature, int $algo = OPENSSL_ALGO_SHA1): bool {
        if (!$this->pemPath || !file_exists($this->pemPath)) return false;
        $pem = file_get_contents($this->pemPath);
        $pubKeyId = openssl_get_publickey($pem);
        if (!$pubKeyId) return false;
        $ok = openssl_verify($data, $signature, $pubKeyId, $algo);
        openssl_free_key($pubKeyId);
        return $ok === 1;
    }
}
?>

使用範例

<?php
$signer = new DigitalSigner('merchant.p12', 'p12_password', 'bank_public.pem');

$data = 'order_id=12345&amount=100.00×tamp=1699999999';

// 簽名(建議用 SHA256 以上)
$signature = $signer->sign($data, OPENSSL_ALGO_SHA256);
// 傳輸時通常 Base64 編碼
$sigB64 = base64_encode($signature);

// 對方驗證
if ($signer->verifyWithPem($data, $signature, OPENSSL_ALGO_SHA256)) {
    echo '驗證通過';
}
?>

支援的簽名演算法常數($algo 參數)

常數演算法備註
OPENSSL_ALGO_SHA1SHA-1預設,已不建議新系統使用
OPENSSL_ALGO_SHA224SHA-224
OPENSSL_ALGO_SHA256SHA-256✅ 推薦最低標準
OPENSSL_ALGO_SHA384SHA-384
OPENSSL_ALGO_SHA512SHA-512
OPENSSL_ALGO_RMD160RIPEMD-160
OPENSSL_ALGO_MD5MD5❌ 不安全,禁用
OPENSSL_ALGO_DSS1DSA-SHA1DSA 金鑰專用

💡 國密 SM2/SM3 需 PHP 編譯啟用 --enable-openssl 且 OpenSSL 库支援 GM/T 标准,調用方式相同,常數可能為 OPENSSL_ALGO_SM3(視版本)。

關鍵避坑

  1. .p12 密碼錯誤 → openssl_pkcs12_read 返回 false,無錯誤訊息,需自行檢查。
  2. 私鑰/公鑰陣列鍵名:$certs['pkey']、$certs['cert'],不同 OpenSSL 版本可能不同,建議 print_r($certs) 確認。
  3. 簽名為二進位:傳輸/儲存請 base64_encode,驗證前 base64_decode。
  4. 資料排序:簽名前必須規範化資料(如鍵值對按 ASCII 排序、URL 編碼),雙方一致才能驗證通過。
  5. 憑證鏈驗證:生產環境應用 openssl_x509_verify() 驗證憑證鏈信任鍊,防止偽造憑證。

結語

PHP OpenSSL 簽名驗證核心就三個函數:openssl_pkcs12_read、openssl_sign、openssl_verify。封裝好工具類、統一演算法(SHA256+)、規範化資料格式,即可滿足金流、開放平台 99% 的簽名需求。

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