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

Java 數位簽名算法 RSA 實戰:從原理到完整代碼實現

     編輯:Java 綜合教學

Java 數位簽名算法 RSA 實戰:從原理到完整代碼實現

數位簽名是資訊安全三大支柱(機密性、完整性、不可否認性)中實現完整性驗證與不可否認性的核心技術。RSA 作為最經典的非對稱加密算法,結合雜湊函數(MD5/SHA)構成 MD5withRSA、SHA256withRSA 等簽名算法。本文將從數學原理、Java API 設計、完整代碼實現、安全最佳實踐四個維度徹底講透 RSA 數位簽名。

核心概念:簽名 ≠ 加密

操作使用金鑰目的典型場景
簽名私鑰證明資料來源、防篡改、不可否認API 請求簽名、文件簽署、區塊鏈交易
驗證公鑰驗證簽名有效性對方驗證簽名
加密公鑰機密性:只有持有私鑰者能解密傳輸對稱金鑰、加密敏感資料
解密私鑰還原明文接收方解密

關鍵區別:簽名用私鑰簽、公鑰驗;加密用公鑰加、私鑰解。切勿混淆。

RSA 簽名執行流程

  1. 雜湊:對原始資料計算雜湊值(MD5/SHA-256/SHA-512),產生固定長度摘要。
  2. 簽名:發送方使用私鑰對摘要進行 RSA 加密運算,產生簽名值。
  3. 傳輸:原始資料 + 簽名值 + 公鑰證書/ID 一起傳輸。
  4. 驗證:接收方用相同雜湊算法計算資料摘要,用公鑰解密簽名值得到原摘要,比對兩者是否一致。

RSA簽名流程圖

Java 核心 API 體系

完整代碼實現: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
    }
}

關鍵安全最佳實踐

  1. 金鑰長度 ≥ 2048 位:1024 位 RSA 已可被暴力破解,NIST 強制要求 2048+,長期機密建議 3072/4096。
  2. 雜湊算法選 SHA-256 或以上:MD5withRSA、SHA1withRSA 已被棄用(碰撞攻擊)。優先使用 SHA256withRSA、SHA512withRSA。
  3. 使用 SecureRandom:金鑰生成、簽名隨機填充(PSS 模式)需加密級隨機數源。
  4. 私鑰全生命週期保護:
    • 生成即加密儲存(PKCS#12 / JCEKS / HSM / KMS)
    • 記憶體中使用後立即清零(char[] 代替 String)
    • 不出現在日誌、堆轉儲、配置檔、Git 歷史
  5. 採用 RSA-PSS 填充模式(JDK 11+):Signature.getInstance("SHA256withRSAandMGF1") 或 RSASSA-PSS,比 PKCS#1 v1.5 更抗選擇明文攻擊。
  6. 時間戳與 Nonce 防重放:簽名資料包含 timestamp、nonce,驗證端檢查時效性與唯一性。
  7. 證書鏈驗證:生產環境公鑰應透過 X.509 證書鏈(CA 根證書信任錨)驗證身份,而非裸公鑰。

常見異常與排查

類/介面角色關鍵方法
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[]
異常原因解決
InvalidKeyException金鑰格式錯誤、長度不符、演算法不匹配檢查 KeySpec 類型(PKCS8/X509)、KeyFactory 演算法、金鑰編碼是否 Base64 正確解碼
SignatureException簽名/驗證過程失敗資料編碼不一致(UTF-8 vs GBK)、簽名資料順序錯誤、金鑰不匹配
NoSuchAlgorithmExceptionJCE 提供者不支援算法確認 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,但安全細節決定成敗,容不得半點馬虎。

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