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

Java 數位簽章實戰:DSA 算法實現完整指南

     編輯:Java 程式設計入門

前言:數位簽章與非對稱加密

數位簽章是資訊安全的核心技術,實現身份認證、完整性驗證與不可否認性。本文基於 Java Cryptography Architecture (JCA),完整演示 DSA (Digital Signature Algorithm) 簽章的密鑰生成、簽名、驗證全流程,並解析關鍵 API 與最佳實踐。

一、核心類別與流程概覽

類別/介面角色關鍵方法
KeyPairGenerator非對稱密鑰對生成器getInstance("DSA"), initialize(keysize), genKeyPair()
KeyPair密鑰對容器getPublic(), getPrivate()
PrivateKey / PublicKey私鑰/公鑰介面實作 Serializable,可序列化儲存
Signature簽章引擎getInstance("DSA"), initSign(privateKey), update(data), sign(), initVerify(publicKey), verify(signature)
ObjectOutputStream/InputStream密鑰/簽名資料持久化writeObject(), readObject()

二、完整程式碼實現


import java.security.*;
import java.security.spec.*;
import java.io.*;

public class DSADigitalSignatureDemo {

    private static final String PRIVATE_KEY_FILE = "private_key.der";
    private static final String PUBLIC_KEY_FILE  = "public_key.der";
    private static final String SIGNED_DATA_FILE = "signed_data.dat";
    private static final String ALGORITHM = "DSA";
    private static final int KEY_SIZE = 2048; // 現代建議 2048 或 3072 位

    public static void main(String[] args) throws Exception {
        DSADigitalSignatureDemo demo = new DSADigitalSignatureDemo();
        
        // 1. 生成或載入密鑰對
        KeyPair keyPair = demo.loadOrGenerateKeyPair();
        
        // 2. 簽名階段 (發送方)
        String message = "這是一份重要的合約內容,需要數位簽章保護。";
        byte[] signature = demo.signMessage(message, keyPair.getPrivate());
        System.out.println("簽名完成,長度: " + signature.length + " bytes");
        System.out.println("簽名值 (Hex): " + bytesToHex(signature));
        
        // 3. 持久化簽名資料 (模擬傳輸)
        demo.saveSignedData(message, signature);
        
        // 4. 驗證階段 (接收方)
        demo.verifySignature(keyPair.getPublic());
    }

    /** 生成或載入 DSA 密鑰對 */
    KeyPair loadOrGenerateKeyPair() throws Exception {
        File priFile = new File(PRIVATE_KEY_FILE);
        File pubFile = new File(PUBLIC_KEY_FILE);
        
        if (priFile.exists() && pubFile.exists()) {
            System.out.println("載入現有密鑰對...");
            PrivateKey priKey = readKey(priFile, PrivateKey.class);
            PublicKey pubKey = readKey(pubFile, PublicKey.class);
            return new KeyPair(pubKey, priKey);
        }
        
        System.out.println("生成新的 " + KEY_SIZE + " 位 DSA 密鑰對...");
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
        keyGen.initialize(KEY_SIZE); // 可傳入 SecureRandom 增強隨機性
        KeyPair keyPair = keyGen.generateKeyPair();
        
        // 儲存為 PKCS#8 (私鑰) 和 X.509 (公鑰) 標準格式
        saveKey(keyPair.getPrivate(), priFile);
        saveKey(keyPair.getPublic(), pubFile);
        System.out.println("密鑰對已儲存至 " + PRIVATE_KEY_FILE + " / " + PUBLIC_KEY_FILE);
        return keyPair;
    }

    /** 簽名核心邏輯 */
    byte[] signMessage(String message, PrivateKey privateKey) throws Exception {
        Signature signer = Signature.getInstance(ALGORITHM);
        signer.initSign(privateKey);
        signer.update(message.getBytes(StandardCharsets.UTF_8)); // 明確指定編碼
        return signer.sign();
    }

    /** 儲存簽名資料 (訊息 + 簽名) */
    void saveSignedData(String message, byte[] signature) throws IOException {
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream(SIGNED_DATA_FILE))) {
            out.writeObject(message);
            out.writeObject(signature);
        }
    }

    /** 驗證簽名 */
    void verifySignature(PublicKey publicKey) throws Exception {
        // 讀取傳輸的資料
        String receivedMsg;
        byte[] receivedSig;
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream(SIGNED_DATA_FILE))) {
            receivedMsg = (String) in.readObject();
            receivedSig = (byte[]) in.readObject();
        }
        
        Signature verifier = Signature.getInstance(ALGORITHM);
        verifier.initVerify(publicKey);
        verifier.update(receivedMsg.getBytes(StandardCharsets.UTF_8));
        
        boolean valid = verifier.verify(receivedSig);
        System.out.println("驗證結果: " + (valid ? "✅ 簽名有效,資料完整且來源可信" : "❌ 簽名無效,資料可能被篡改或非持有私鑰者發送"));
        if (valid) System.out.println("原始訊息: " + receivedMsg);
    }

    /** 序列化儲存金鑰 (標準格式) */
    void saveKey(Key key, File file) throws IOException {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
            out.writeObject(key);
        }
    }

    /** 反序列化載入金鑰 */
     T readKey(File file, Class keyClass) throws Exception {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
            return keyClass.cast(in.readObject());
        }
    }

    /** 位元組陣列轉十六進位字串 */
    static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) sb.append(String.format("%02x", b));
        return sb.toString();
    }
}

三、關鍵技術點深度解析

3.1 DSA 參數與金鑰長度

  • DSA 基於離散對數難題,需生成素數 p, q, g 參數集。
  • Java KeyPairGenerator.initialize(keysize) 會自動生成參數,過程較慢 (秒級)。
  • 現代標準:L=2048, N=256 (FIPS 186-4),對應 initialize(2048)。
  • 生產環境建議預先生成參數檔,載入時使用 AlgorithmParameterSpec 避免啟動延遲。

3.2 金鑰儲存格式

  • 私鑰:PKCS#8 編碼 (PrivateKey.getEncoded() → "PKCS#8")。
  • 公鑰:X.509 編碼 (PublicKey.getEncoded() → "X.509")。
  • 示例使用 Java 序列化 (ObjectOutputStream) 簡化演示,**生產環境應使用標準編碼格式**搭配 PEM/DER 檔案,便於跨語言/平台互操作。

3.3 簽名流程細節

  1. initSign(privateKey):初始化簽名模式,綁定私鑰。
  2. update(data):可多次呼叫,流式處理大數據,**最終簽名的是所有 update 資料的哈希值** (DSA 內部用 SHA-256/384/512 視金鑰長度)。
  3. sign():生成簽名位元組陣列 (ASN.1 DER 編碼的 r, s 整數對)。

3.4 安全性最佳實踐

  1. 私鑰保護:生產環境**絕不可**以明文檔案儲存。必須使用 KeyStore (JKS/PKCS12)、HSM、或密碼學保護 (PBE 加密)。
  2. 演算法選擇:新專案優先考慮 ECDSA (EC 橢圓曲線) 或 RSA-PSS,效能更優、金鑰更短。DSA 維護相容性用。
  3. 隨機數安全:DSA 簽名極度依賴 k 值隨機性,**必須使用 SecureRandom**,重複/可預測的 k 會直接洩露私鑰。
  4. 時間戳與憑證鏈:長期有效簽名需引入 RFC 3161 時間戳服務與 X.509 憑證鏈驗證。

四、常見異常與排查

異常原因對策
NoSuchAlgorithmExceptionJCE 提供者不支援 DSA/指定長度確認 java.security 配置,使用 BouncyCastle 提供者
InvalidKeyException金鑰格式錯誤、長度不符、演算法不匹配檢查金鑰編碼格式 (PKCS#8/X.509)、長度是否符合參數集
SignatureException簽名/驗證過程失敗檢查 update 資料一致性、編碼是否統一 (UTF-8)
ClassCastException反序列化金鑰類型不符使用 KeyFactory.generatePrivate(KeySpec) 從標準編碼重建

結語

Java JCA 提供了完整、標準化的數位簽章 API。掌握 KeyPairGenerator、Signature 核心流程、理解金鑰編碼格式、重視私鑰保護與隨機數安全,是構建可信數位簽章系統的基石。實際專案中,建議封裝為高層服務元件,整合 KeyStore 管理、憑證驗證、時間戳服務,構建企業級可信基礎設施。

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