代碼簽名是確保 JAR 檔、Applet、RMI 物件完整性與來源可信的基石。本文提供三個獨立工具類別,示範如何使用 java.security 套件實作 DSA 金鑰對產生、SHA1withDSA 簽章與驗證,適合 Jini、RMI、Applet 部署場景。
import java.security.*;
import java.io.*;
public class KeyPairGen {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java KeyPairGen ");
System.exit(1);
}
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(512); // 建議生產環境用 2048 bits
KeyPair kp = kpg.generateKeyPair();
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(args[0]))) {
oos.writeObject(kp);
}
System.out.println("Key pair saved to " + args[0]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行:java KeyPairGen mykeys.dat
import java.security.*;
import java.io.*;
public class SignGen {
public static void main(String[] args) {
if (args.length != 3) {
System.out.println("Usage: java SignGen ");
System.exit(1);
}
try {
KeyPair kp;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(args[0]))) {
kp = (KeyPair) ois.readObject();
}
Signature sig = Signature.getInstance("SHA1withDSA");
sig.initSign(kp.getPrivate());
sig.update(args[1].getBytes("UTF-8"));
byte[] signature = sig.sign();
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(args[2]))) {
oos.writeObject(signature);
}
System.out.println("Signature saved to " + args[2]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行:java SignGen mykeys.dat "Hello World" mysig.dat
注意:實務上應對檔案內容計算 SHA-256 摘要再簽章,而非直接簽字串。
import java.security.*;
import java.io.*;
public class SignVerify {
public static void main(String[] args) {
if (args.length != 3) {
System.out.println("Usage: java SignVerify ");
System.exit(1);
}
try {
KeyPair kp;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(args[0]))) {
kp = (KeyPair) ois.readObject();
}
byte[] signature;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(args[2]))) {
signature = (byte[]) ois.readObject();
}
Signature sig = Signature.getInstance("SHA1withDSA");
sig.initVerify(kp.getPublic());
sig.update(args[1].getBytes("UTF-8"));
if (sig.verify(signature)) {
System.out.println("Verify OK!");
} else {
System.out.println("Verify Error!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行:java SignVerify mykeys.dat "Hello World" mysig.dat
| 場景 | 做法 |
|---|---|
| JAR 檔簽名 | 使用 jarsigner 工具搭配 Keystore (JKS/PKCS12),而非手動序列化 KeyPair |
| Applet/RMI | 簽署所有 JAR,並在 JNLP/HTML 中宣告 all-permissions |
| 金鑰長度 | DSA 512 bits 已不安全,生產環境請用 RSA 2048+ 或 ECDSA P-256 |
| 憑證鏈 | 向 CA 購買代碼簽署憑證,避免使用者看到「未受信任」警告 |
| 時間戳 | jarsigner -tsa https://timestamp.digicert.com 確保憑證過期後簽名仍有效 |
InvalidKeyException:金鑰格式不符 (DSA 金鑰用 RSA 算法或反之)SignatureException: Signature length not correct:簽章資料損毀或非對應算法產生ClassNotFoundException:反序列化 KeyPair 時 classpath 缺少提供者類別