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

OpenSSL 使用 DSA 算法生成與驗證數位簽名完整實戰指南

     編輯:PHP 相關知識

DSA 簽名原理速覽

DSA (Digital Signature Algorithm) 是基於離散對數難題的非對稱簽名算法,**僅用於簽名/驗證,不可用於加密**。關鍵特性:

  • 私鑰簽名,公鑰驗證
  • 簽名過程需隨機數 k,**k 必須唯一且保密**,重複或洩露 k 會導致私鑰被推導(如 Sony PS3 簽名災難)
  • 簽名長度固定(視金鑰長度:1024/2048/3072 位)
  • **必須搭配 SHA-1(DSS1)雜湊**,OpenSSL 中以 -dss1 指定

金鑰生成流程

# 1. 生成 DSA 參數檔(僅需一次,可共用)
openssl dsaparam -out dsaparam.pem 2048

# 2. 生成私鑰(加密保護)
openssl gendsa -out dsa_private.pem -aes256 dsaparam.pem
# 輸入密碼保護私鑰

# 3. 導出公鑰
openssl dsa -in dsa_private.pem -pubout -out dsa_public.pem

# 4. (可選) 移除私鑰密碼(僅測試環境)
openssl dsa -in dsa_private.pem -out dsa_private_nopass.pem

命令列簽名與驗證

簽名

# 對明文檔案 s.txt 生成簽名 signature.bin
openssl dgst -dss1 -sign dsa_private.pem -out signature.bin s.txt

# 若私鑰有密碼,會提示輸入;或使用 -passin pass:yourpass

驗證

# 使用公鑰驗證簽名
openssl dgst -dss1 -verify dsa_public.pem -signature signature.bin s.txt
# 輸出: Verified OK / Verification Failure

PHP 程式碼實現簽名與驗證

簽名端(sign.php)

<?php
/**
 * DSA 簽名(PHP 7.0+ / OpenSSL 1.0.2+)
 * @param string $data      待簽明文
 * @param string $privKey   私鑰內容(PEM 格式,含 -----BEGIN DSA PRIVATE KEY-----)
 * @param string $passphrase 私鑰密碼(無則 null)
 * @return string|false     二進位簽名,失敗回傳 false
 */
function dsa_sign(string $data, string $privKey, ?string $passphrase = null) {
    $keyRes = openssl_get_privatekey($privKey, $passphrase);
    if (!$keyRes) {
        error_log('私鑰載入失敗: ' . openssl_error_string());
        return false;
    }
    
    $signature = '';
    $ok = openssl_sign($data, $signature, $keyRes, OPENSSL_ALGO_DSS1); // 關鍵:指定 DSS1 (SHA-1)
    openssl_free_key($keyRes);
    
    return $ok ? $signature : false;
}

// 使用範例
$data = '重要業務數據:訂單號 20240101001,金額 999.00 元';
$privKey = file_get_contents(__DIR__ . '/keys/dsa_private.pem');
$passphrase = 'your_strong_passphrase'; // 生產環境從 Vault/環境變數讀取

$sig = dsa_sign($data, $privKey, $passphrase);
if ($sig) {
    // 簽名通常以 Base64 或 Hex 儲存/傳輸
    file_put_contents(__DIR__ . '/signature.bin', $sig);
    echo '簽名成功,Base64: ' . base64_encode($sig) . "\n";
} else {
    echo '簽名失敗';
}

驗證端(verify.php)

<?php
/**
 * DSA 驗證
 * @param string $data      原始明文
 * @param string $signature 二進位簽名
 * @param string $pubKey    公鑰內容(PEM 格式)
 * @return bool
 */
function dsa_verify(string $data, string $signature, string $pubKey): bool {
    $keyRes = openssl_get_publickey($pubKey);
    if (!$keyRes) {
        error_log('公鑰載入失敗: ' . openssl_error_string());
        return false;
    }
    
    // openssl_verify 回傳: 1=成功, 0=失敗, -1=錯誤
    $result = openssl_verify($data, $signature, $keyRes, OPENSSL_ALGO_DSS1);
    openssl_free_key($keyRes);
    
    return $result === 1;
}

// 使用範例
$data = file_get_contents(__DIR__ . '/s.txt');
$sig = file_get_contents(__DIR__ . '/signature.bin');
$pubKey = file_get_contents(__DIR__ . '/keys/dsa_public.pem');

if (dsa_verify($data, $sig, $pubKey)) {
    echo "✅ 簽名驗證通過,資料完整且來源可信。\n";
} else {
    echo "❌ 簽名驗證失敗,資料可能被篡改或簽名無效。\n";
    // 詳細錯誤
    while ($err = openssl_error_string()) {
        error_log("OpenSSL Error: $err");
    }
}

PHP 直接查看簽名內容(除錯用)

<?php
// 將二進位簽名轉為 Hex 顯示
$sig = file_get_contents('signature.bin');
echo 'Signature (Hex): ' . bin2hex($sig) . "\n";
// 亦可轉 Base64
echo 'Signature (Base64): ' . base64_encode($sig) . "\n";

關鍵注意事項與最佳實踐

項目建議
雜湊算法DSA **強制使用 SHA-1 (DSS1)**。雖 SHA-1 已被碰撞攻擊破解,但在 DSA 簽名結構下仍被廣泛接受。新系統優先考慮 ECDSA (P-256/P-384) + SHA-256/384 或 Ed25519。
金鑰長度最小 2048 位。NIST 建議 2030 年後使用 3072 位。
隨機數 kOpenSSL 內部使用 RFC 6979 確定性 k(基於私鑰+訊息雜湊),避免隨機數重複風險。確保 OpenSSL ≥ 1.0.2。
私鑰保護**必須加密儲存**(AES-256),生產環境使用 HSM/KMS/Vault,絕不硬編碼、不提交版控。
簽名格式OpenSSL 輸出為 ASN.1 DER 編碼的 SEQUENCE { r, s }。跨語言驗證時需注意編碼一致性(Java Signature、Python cryptography、Go crypto/dsa 均支援 DER)。
時間戳長期有效簽名需加入可信時間戳(RFC 3161 TSA),防止私鑰洩露後歷史簽名被否認。

常見錯誤排查

  • openssl_sign(): supplied key param cannot be coerced into a private key:私鑰格式錯誤、密碼錯誤、非 DSA 金鑰(如誤用 RSA 私鑰)。
  • openssl_verify(): wrong signature length:簽名資料損壞、編碼不一致(Hex/Base64 未正確解碼)、公私鑰不匹配。
  • Verification Failure 但確信資料正確:檢查明文編碼(UTF-8 vs 二進位)、行尾符(CRLF/LF)、BOM、空白字元差異。**簽名必須對完全相同的位元序列進行**。
現代架構建議:新專案優先採用 Ed25519(openssl_sign(..., OPENSSL_ALGO_ED25519) PHP 8.0+)、ECDSA P-256 或 RSA-PSS。DSA 維護現有相容系統即可,不建議新引入。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved