一、前言
愛德華曲線數位簽章演算法(EdDSA)是一種現代化的數位簽章方案,相較於傳統的 RSA 或 DSA 演算法,它在安全性、效能和易用性方面都具有優勢。EdDSA 基於扭曲愛德華曲線 (Twisted Edwards Curves) 進行運算,提供了更強的安全性保證,並且在實作上能有效避免時間側通道攻擊。EdDSA 在區塊鏈、程式碼簽章、安全通訊協定等領域都有廣泛的應用。Java 作為一個廣泛使用的程式語言,對 EdDSA 的支援也隨著版本迭代而不斷演進。本文旨在整理 Java 7 至 Java 17 中關於 EdDSA 的新特性,並分析其在不同版本的應用、架構、工具與範例。
目錄
- 一、前言
- 二、Java 7 與 EdDSA 的缺失
- 三、Java 8 至 Java 14:無顯著變更
- 四、Java 15:引入 EdDSA 原生支援
- 五、Java 16 & 17:持續改進與效能優化
- 六、總結
二、Java 7 與 EdDSA 的缺失
Java 7 並 沒有 直接支援 EdDSA 演算法。在 Java 7 環境中,開發者若需要使用 EdDSA 進行數位簽章,必須透過第三方函式庫,例如:
- Bouncy Castle: Bouncy Castle 是一個廣泛使用的 Java 加密函式庫,在 Java 7 的時代,它提供了 EdDSA 的實作,開發者需要引入 Bouncy Castle 的 JAR 包才能使用。
架構簡述:
xxxxxxxxxx
Java 7 應用程式
|
v
Bouncy Castle 函式庫
|
v
EdDSA 演算法實作 (基於 Bouncy Castle 提供的 API)
缺點:
- 需要引入外部依賴,增加了專案的複雜度。
- 不同第三方函式庫的版本相容性問題。
- 並非官方標準 API,可能存在潛在的維護風險。
下載 Bouncy Castle 第三方套件: Bouncy Castle 官方網站
範例(使用 Bouncy Castle):
xxxxxxxxxx
import org.bouncycastle.jce.provider.BouncyCastleProvider; // 引入 Bouncy Castle 安全性提供者
import java.security.*; // 引入 Java 安全性 API 相關的類別
import java.util.Arrays; // 引入 Arrays 類別用於顯示陣列
import org.bouncycastle.jcajce.spec.EdDSAParameterSpec; // 引入 Bouncy Castle 提供的 EdDSAParameterSpec
public class MyEdDSASignatureBefore15 { // 宣告 EdDSASignatureBefore15 類別,用於演示在 Java 15 之前如何使用 EdDSA
public static void main(String[] args) throws Exception { // 主方法,程式執行起點
Security.addProvider(new BouncyCastleProvider()); // 加入 Bouncy Castle 作為安全性提供者
// 1. 產生金鑰對
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EdDSA", "BC"); // 取得 EdDSA 金鑰對產生器,指定使用 Bouncy Castle (BC) 提供者
EdDSAParameterSpec edDSAParameterSpec = new EdDSAParameterSpec("Ed25519"); // 建立 EdDSAParameterSpec 物件,指定使用 Ed25519 曲線
keyPairGen.initialize(edDSAParameterSpec); // 初始化金鑰對產生器,使用 EdDSAParameterSpec 指定 Ed25519 曲線
KeyPair keyPair = keyPairGen.generateKeyPair(); // 產生金鑰對
PrivateKey privateKey = keyPair.getPrivate(); // 取得私鑰
PublicKey publicKey = keyPair.getPublic(); // 取得公鑰
// 2. 待簽名的數據
byte[] data = "Hello EdDSA!".getBytes("UTF-8"); // 將字串 "Hello EdDSA!" 轉換為 UTF-8 編碼的位元組陣列,作為待簽署的資料
// 3. 簽名
Signature signature = Signature.getInstance("EdDSA", "BC"); // 取得 EdDSA 簽章物件,指定使用 Bouncy Castle 提供者
signature.initSign(privateKey); // 使用私鑰初始化簽章物件
signature.update(data); // 設定要簽名的資料
byte[] sigBytes = signature.sign(); // 產生簽名
// 4. 驗證簽名
Signature verifier = Signature.getInstance("EdDSA", "BC"); // 取得 EdDSA 簽章驗證物件,指定使用 Bouncy Castle 提供者
verifier.initVerify(publicKey); // 使用公鑰初始化驗證物件
verifier.update(data); // 設定要驗證的資料
boolean verified = verifier.verify(sigBytes); // 驗證簽名
System.out.println("Data: " + new String(data, "UTF-8")); // 印出原始資料 (使用 UTF-8 編碼)
System.out.println("Signature: " + Arrays.toString(sigBytes)); // 印出簽名 (以位元組陣列的形式)
System.out.println("Signature Verified: " + verified); // 印出簽章是否驗證成功
}
}
執行結果:
xxxxxxxxxx
$ /d/jdk/zulu8.46.0.19-ca-jdk8.0.252-win_x64/bin/javac -cp ".;../lib/*;" -encoding utf-8 MyEdDSASignatureBefore15.java
$ /d/jdk/zulu8.46.0.19-ca-jdk8.0.252-win_x64/bin/java -cp ".;../lib/*;" -Dfile.encoding=utf-8 MyEdDSASignatureBefore15
Data: Hello EdDSA!
Signature: [-93, -32, -86, 101, 121, 31, -127, 3, 82, 13, -64, 28, -25, 43, 21, -101, 86, 68, 53, -32, 17, 54, -14, 114, -116, -114, 69, 30, -18, 113, 16, 99, -82, 1, 95, -66, 110, -23, -105, 123, 99, -82, 103, -120, 74, -28, -105, -14, 67, -6, -58, -78, -77, 47, -69, -41, 104, 32, 76, -17, -65, -71, -101, 4]
Signature Verified: true
三、Java 8 至 Java 14:無顯著變更
在 Java 8 至 Java 14 的版本中,官方 JDK 仍然 沒有 提供原生 EdDSA 的支援。因此,開發者在這段時間內仍然需要依賴 Bouncy Castle 等第三方函式庫來使用 EdDSA。架構和實作方式與 Java 7 時期相似。
四、Java 15:引入 EdDSA 原生支援
Java 15 是 EdDSA 支援的一個轉捩點。JDK 15 正式引入了 原生 EdDSA 演算法 的支援,成為 JDK 加密 API 的一部分。這使得開發者無需再依賴第三方函式庫,降低了程式碼的複雜度,並且能享受 JDK 原生效能優化帶來的益處。
特性:
-
新增了
java.security.Signature
類的EdDSA
演算法名稱。 -
新增了
java.security.spec.EdDSAParameterSpec
等相關類別。 -
可以透過
KeyPairGenerator
和Signature
等 JDK 核心類別直接使用 EdDSA。
架構簡述:
xxxxxxxxxx
Java 15+ 應用程式
|
v
Java Security API (JDK 內建)
|
v
EdDSA 演算法實作 (JDK 原生)
工具:
-
JDK 內建的
keytool
工具也支援生成 EdDSA 金鑰對。
範例 (Java 15+):
xxxxxxxxxx
import java.security.*;
import java.security.spec.NamedParameterSpec;
import java.security.spec.EdDSAParameterSpec;
import java.util.Arrays;
public class MyEdDSASignature {
public static void main(String[] args) throws Exception {
// 2. 待簽名的數據
byte[] data = "Hello EdDSA!".getBytes(); // 將字串轉換為位元組陣列,這是要簽署的資料
byte[] context = "My Context".getBytes(); // 將字串轉換為位元組陣列,作為簽章的 context
// 簽名方(發送方):
// 資料: 發送方需要傳輸的資料。
// Context: 發送方選擇的 context 值。
// 簽名: 發送方使用私鑰和 資料 + Context 生成簽名。
// 傳輸: 發送方將 資料 + 簽名 + Context 發送給接收方。
// Context 的產生: context 可以是任意的位元組陣列,可以使用隨機數產生器生成,也可以是時間戳記、訊息序號等。
// 2.1. 使用 EdDSAParameterSpec with context
EdDSAParameterSpec edDSAParameterSpec = new EdDSAParameterSpec(false, context); // 建立 EdDSAParameterSpec 物件,設定不使用 prehash,並且使用指定的 context
// 1. 產生金鑰對 (使用 NamedParameterSpec)
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EdDSA"); // 取得 EdDSA 金鑰對產生器
keyPairGen.initialize(new NamedParameterSpec("Ed25519")); // 初始化金鑰對產生器,使用 Ed25519 曲線
KeyPair keyPair = keyPairGen.generateKeyPair(); // 產生金鑰對
PrivateKey privateKey = keyPair.getPrivate(); // 取得私鑰
PublicKey publicKey = keyPair.getPublic(); // 取得公鑰
// 3. 簽名
Signature signature = Signature.getInstance("EdDSA"); // 取得 EdDSA 簽章物件
signature.setParameter(edDSAParameterSpec); // 設定簽章物件的參數,使用我們建立的 EdDSAParameterSpec
signature.initSign(privateKey); // 使用私鑰初始化簽章物件
signature.update(data); // 設定要簽名的資料
byte[] sigBytes = signature.sign(); // 產生簽名
// 接收方(驗證方):
// 接收: 接收方接收到 資料 + 簽名 + Context。
// 驗證: 接收方使用發送方的公鑰、接收到的 資料、接收到的 Context 來驗證簽名。
// Context 的使用: 接收方必須使用與簽名方相同的 context 值,才能成功驗證簽名。
// 4. 驗證簽名 (使用不同的 Signature 物件)
String receivedContext = "My Context"; // 假設接收到的 context
Signature verifier = Signature.getInstance("EdDSA"); // 取得 EdDSA 簽章驗證物件
verifier.initVerify(publicKey); // 使用公鑰初始化驗證物件, **必須先初始化再設定參數**
verifier.setParameter(new EdDSAParameterSpec(false, receivedContext.getBytes())); // 設定驗證物件的參數,使用接收到的 context
verifier.update(data); // 設定要驗證的資料
boolean verified = verifier.verify(sigBytes); // 驗證簽名
System.out.println("Data: " + new String(data)); // 印出原始資料
System.out.println("Context: " + receivedContext); // 印出接收到的 context
System.out.println("Signature: " + Arrays.toString(sigBytes)); // 印出簽名
System.out.println("Signature Verified: " + verified); // 印出簽章是否驗證成功
// 5. 取得 AlgorithmParameters 並取出 EdDSAParameterSpec
AlgorithmParameters params = verifier.getParameters(); // 取得 verifier 物件所使用的參數 (實際上此方法會回傳 null)
System.out.println(params == null ? "null" : params.getAlgorithm()); // 印出參數,如果是 null 則印出 "null",否則印出參數的名稱 (實際上總是會印出 "null")
}
}
執行結果:
xxxxxxxxxx
$ java -cp ".;../lib/*;" -Dfile.encoding=utf-8 MyEdDSASignature.java
Data: Hello EdDSA!
Context: My Context
Signature: [61, -117, -1, 64, -41, 54, -113, -67, 95, -105, -46, -102, 28, 78, -21, -16, -69, -16, 127, 28, -3, 107, 24, 22, 35, -4, 126, -67, -98, 92, -11, -103, 76, 98, -114, 110, -117, -102, 68, 59, -56, 85, 115, 87, 45, -3, 71, 113, -88, 127, 102, -10, -123, -100, -76, 98, 86, -116, 36, 58, 22, 27, 118, 14]
Signature Verified: true
null
五、Java 16 & 17:持續改進與效能優化
Java 16 和 Java 17 在 EdDSA 的支援方面,主要側重於效能的提升和細節的優化,並沒有引入新的核心 API 或演算法。開發者可以繼續使用 Java 15 中引入的相關 API 和功能。
六、總結
Java 對於 EdDSA 演算法的支援經歷了一個逐步演進的過程。Java 7 至 Java 14 時代,開發者必須仰賴 Bouncy Castle 等第三方函式庫才能使用 EdDSA。Java 15 是一個重要的分水嶺,它引入了原生的 EdDSA 支援,極大地簡化了開發流程,提升了效能。Java 16 和 17 則在既有的基礎上進行了持續的效能優化,但主要著重於底層實作,並沒有引入新的 API 或功能。
對於現代應用而言,使用 Java 15 或以上版本開發 EdDSA 數位簽章相關功能,是更有效且安全的方式。透過 JDK 原生 API,我們可以享受更高的效能、更簡潔的程式碼、以及更低的維護成本。我們也必須理解 JDK 原生的 EdDSA 實作的限制,並且根據情況選擇合適的解決方案。儘管如此,EdDSA 仍然是現代數位簽章的首選,它為數位安全提供了更強大的基礎。
重要里程碑:
- Java 7 - 14:第三方函式庫時代: 在這個階段,EdDSA 的使用高度依賴 Bouncy Castle 等第三方函式庫。這增加了開發的複雜性,需要額外的依賴管理,並且存在潛在的相容性和維護風險。然而,這也是當時在 Java 環境中使用 EdDSA 的唯一方法。
-
Java 15:原生 EdDSA 支援:
Java 15 正式引入了原生的 EdDSA 支援,標誌著 EdDSA 進入了 Java 核心 API。這帶來了許多好處,包括:
- 簡化開發: 開發者無需再引入第三方函式庫,減少了專案的依賴和複雜度。
- 效能提升: JDK 原生實作通常能享有更佳的效能優化。
- 標準化: 透過 JDK API 使用 EdDSA,符合官方標準,降低了潛在的維護風險。
-
NamedParameterSpec
與EdDSAParameterSpec
: Java 15 正式引入java.security.spec.NamedParameterSpec
用於指定曲線(例如Ed25519
、Ed448
),並使用java.security.spec.EdDSAParameterSpec
設定簽章的相關參數 (例如:context
、prehash
等)。
- Java 16 & 17:持續優化: Java 16 和 17 延續了 Java 15 的 EdDSA 支援,主要集中在效能的微調和細節的優化,並沒有引入新的 EdDSA 相關的核心 API。
注意事項:
-
Signature.getParameters()
的限制: 由於 JDK 原生 EdDSA 實作的限制,Signature.getParameters()
方法在 Java 15 ~ Java 17 之間會返回null
,這與Signature
API 的規範不完全一致,也表示我們不能直接從Signature
物件獲取EdDSAParameterSpec
,而必須額外紀錄。 - 實作的差異: 在 Java 15 之前,我們依賴第三方函式庫,例如 Bouncy Castle,不同的實作方式會導致簽章結果不同,但只要使用相同的公鑰,不同的簽章仍然可以被驗證。
- 安全實作: 無論使用哪個 Java 版本和實作,都應該注意遵循安全實作的最佳實踐,避免使用不安全的隨機數產生器、金鑰管理不當或程式碼漏洞,以確保數位簽章系統的安全性。
沒有留言:
張貼留言