| 類/介面 | 角色 | 關鍵方法 |
KeyPairGenerator | 金鑰對生成器 | getInstance("RSA"), initialize(2048), generateKeyPair() |
KeyPair | 公私鑰容器 | getPublic(), getPrivate() |
Signature | 簽名/驗證引擎 | getInstance("SHA256withRSA"), initSign(privateKey), initVerify(publicKey), update(data), sign(), verify(signature) |
KeyFactory | 金鑰規範轉換 | generatePrivate(PKCS8EncodedKeySpec), generatePublic(X509EncodedKeySpec) |
PKCS8EncodedKeySpec | 私鑰編碼規範 (PKCS#8) | 構造器接收 byte[] |
X509EncodedKeySpec | 公鑰編碼規範 (X.509) | 構造器接收 byte[] |
完整代碼實現:RSA-SHA256 簽名驗證
package com.example.crypto;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
public class RsaSignatureUtil {
private static final String ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final int KEY_SIZE = 2048; // 至少 2048 位,1024 已不安全
/** 生成金鑰對,返回 Base64 編碼的公私鑰 */
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);
kpg.initialize(KEY_SIZE, new SecureRandom());
return kpg.generateKeyPair();
}
/** 私鑰簽名,返回 Base64 編碼簽名 */
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] rawSig = signature.sign();
return Base64.getEncoder().encodeToString(rawSig);
}
/** 公鑰驗證 */
public static boolean verify(String data, String base64Signature, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] rawSig = Base64.getDecoder().decode(base64Signature);
return signature.verify(rawSig);
}
/** 從 Base64 字串還原 PrivateKey (PKCS#8) */
public static PrivateKey restorePrivateKey(String base64PrivateKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(base64PrivateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance(ALGORITHM);
return kf.generatePrivate(spec);
}
/** 從 Base64 字串還原 PublicKey (X.509) */
public static PublicKey restorePublicKey(String base64PublicKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(base64PublicKey);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance(ALGORITHM);
return kf.generatePublic(spec);
}
// 測試主程式
public static void main(String[] args) throws Exception {
// 1. 生成金鑰對
KeyPair kp = generateKeyPair();
String pubBase64 = Base64.getEncoder().encodeToString(kp.getPublic().getEncoded());
String priBase64 = Base64.getEncoder().encodeToString(kp.getPrivate().getEncoded());
System.out.println("PublicKey (Base64): " + pubBase64);
System.out.println("PrivateKey (Base64): " + priBase64);
// 2. 簽名
String data = "重要業務資料:訂單金額 10000 元";
String signature = sign(data, kp.getPrivate());
System.out.println("Signature (Base64): " + signature);
// 3. 驗證(模擬接收方只有公鑰)
PublicKey restoredPub = restorePublicKey(pubBase64);
boolean valid = verify(data, signature, restoredPub);
System.out.println("驗證結果: " + valid); // true
// 4. 篡改資料驗證失敗
boolean tampered = verify(data + " (竄改)", signature, restoredPub);
System.out.println("篡改後驗證: " + tampered); // false
}
}
關鍵安全最佳實踐
- 金鑰長度 ≥ 2048 位:1024 位 RSA 已可被暴力破解,NIST 強制要求 2048+,長期機密建議 3072/4096。
- 雜湊算法選 SHA-256 或以上:
MD5withRSA、SHA1withRSA 已被棄用(碰撞攻擊)。優先使用 SHA256withRSA、SHA512withRSA。
- 使用 SecureRandom:金鑰生成、簽名隨機填充(PSS 模式)需加密級隨機數源。
- 私鑰全生命週期保護:
- 生成即加密儲存(PKCS#12 / JCEKS / HSM / KMS)
- 記憶體中使用後立即清零(
char[] 代替 String)
- 不出現在日誌、堆轉儲、配置檔、Git 歷史
- 採用 RSA-PSS 填充模式(JDK 11+):
Signature.getInstance("SHA256withRSAandMGF1") 或 RSASSA-PSS,比 PKCS#1 v1.5 更抗選擇明文攻擊。
- 時間戳與 Nonce 防重放:簽名資料包含
timestamp、nonce,驗證端檢查時效性與唯一性。
- 證書鏈驗證:生產環境公鑰應透過 X.509 證書鏈(CA 根證書信任錨)驗證身份,而非裸公鑰。
常見異常與排查
| 異常 | 原因 | 解決 |
InvalidKeyException | 金鑰格式錯誤、長度不符、演算法不匹配 | 檢查 KeySpec 類型(PKCS8/X509)、KeyFactory 演算法、金鑰編碼是否 Base64 正確解碼 |
SignatureException | 簽名/驗證過程失敗 | 資料編碼不一致(UTF-8 vs GBK)、簽名資料順序錯誤、金鑰不匹配 |
NoSuchAlgorithmException | JCE 提供者不支援算法 | 確認 JDK 版本、安裝無限制策略檔(舊版需)、使用 BouncyCastle 提供者 |
擴展:金鑰持久化與跨語言互操作
- PKCS#8 / X.509 DER 編碼:Java
getEncoded() 產出標準格式,可直接被 OpenSSL、Python cryptography、Go crypto/rsa、Node.js crypto 讀取。
- PEM 格式轉換:Base64 包裝 +
-----BEGIN PRIVATE KEY----- 標頭,通用性最強。
- JWT 整合:
RS256 算法即 SHA256withRSA,java-jwt、nimbus-jose-jwt 庫可直接載入 PrivateKey/PublicKey 簽發/驗證 Token。
總結
RSA 數位簽名是現代分散式系統信任基石。掌握 私鑰簽/公鑰驗 核心語義、SHA256withRSA 算法選型、2048+ 位金鑰、私鑰全鏈路防護、PSS 填充模式、跨語言 PKCS#8/X.509 互操作,即可構建安全可靠的簽名體系。代碼層面核心僅三步:Signature.getInstance → initSign/initVerify → update → sign/verify,但安全細節決定成敗,容不得半點馬虎。
- 上一篇文章: Java 類型判斷三大核心:instanceof、isInstance、isAssignableFrom 深度解析
- 下一篇文章: Quartz 與 Spring 整合實戰:定時任務的企業級最佳實踐
Copyright ©
程式碼之家 All Rights Reserved