數位簽章是資訊安全的核心技術,實現身份認證、完整性驗證與不可否認性。本文基於 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();
}
}
p, q, g 參數集。KeyPairGenerator.initialize(keysize) 會自動生成參數,過程較慢 (秒級)。initialize(2048)。AlgorithmParameterSpec 避免啟動延遲。PrivateKey.getEncoded() → "PKCS#8")。PublicKey.getEncoded() → "X.509")。ObjectOutputStream) 簡化演示,**生產環境應使用標準編碼格式**搭配 PEM/DER 檔案,便於跨語言/平台互操作。initSign(privateKey):初始化簽名模式,綁定私鑰。update(data):可多次呼叫,流式處理大數據,**最終簽名的是所有 update 資料的哈希值** (DSA 內部用 SHA-256/384/512 視金鑰長度)。sign():生成簽名位元組陣列 (ASN.1 DER 編碼的 r, s 整數對)。KeyStore (JKS/PKCS12)、HSM、或密碼學保護 (PBE 加密)。k 值隨機性,**必須使用 SecureRandom**,重複/可預測的 k 會直接洩露私鑰。| 異常 | 原因 | 對策 |
|---|---|---|
NoSuchAlgorithmException | JCE 提供者不支援 DSA/指定長度 | 確認 java.security 配置,使用 BouncyCastle 提供者 |
InvalidKeyException | 金鑰格式錯誤、長度不符、演算法不匹配 | 檢查金鑰編碼格式 (PKCS#8/X.509)、長度是否符合參數集 |
SignatureException | 簽名/驗證過程失敗 | 檢查 update 資料一致性、編碼是否統一 (UTF-8) |
ClassCastException | 反序列化金鑰類型不符 | 使用 KeyFactory.generatePrivate(KeySpec) 從標準編碼重建 |
Java JCA 提供了完整、標準化的數位簽章 API。掌握 KeyPairGenerator、Signature 核心流程、理解金鑰編碼格式、重視私鑰保護與隨機數安全,是構建可信數位簽章系統的基石。實際專案中,建議封裝為高層服務元件,整合 KeyStore 管理、憑證驗證、時間戳服務,構建企業級可信基礎設施。