Java 中的金鑰封裝機制 API
1. 引言
JEP 452將標準化的金鑰封裝機制 (KEM) API 引入 Java 平台(JDK 21),作為javax.crypto套件的一部分。
這項新增功能填補了 Java 加密架構 (JCA) 中長期存在的空白——提供了一種標準的、與提供者無關的方式,可以使用公鑰加密安全地建立共享對稱金鑰。
在 Java 21 之前,開發人員依賴Cipher 、 KeyAgreement或KeyGenerator物件的自訂組合來模擬此過程。這些方法通常容易出錯,並且在不同的密鑰提供者之間也不一致。
借助 KEM,整個金鑰交換步驟變成了一個單一的、定義明確的 API。
在本教程中,我們將探索 Java 21 中的 KEM API。
2. 什麼是密鑰封裝機制?
KEM 是一種公鑰密碼學原語,用於在雙方之間安全地建立共享對稱金鑰。
它由三個功能組成:
- 產生金鑰對,輸出公鑰和私鑰。
- 金鑰封裝,其中發送方使用接收方的公鑰產生共享的秘密金鑰(
K)和金鑰封裝訊息(密文)。 - 金鑰解封裝,其中接收者使用其私鑰和封裝訊息來恢復相同的秘密金鑰(
K)。
與直接使用Cipher不同,KEM 與格式無關,避免了填充問題,並且可以無縫地用於經典演算法和後量子演算法。
它也是現代協定(如傳輸層安全協定 (TLS)、混合公鑰加密協定 (HPKE, RFC 9180 ) 和新興的後量子密碼 (PQC) 方案)的基本組成部分。
3. 支援的演算法
JEP 452 的實現支持幾種標準化的關鍵事件管理 (KEM) :
- RSA-KEM:一種基於 RSA 金鑰對的 KEM。
- 橢圓曲線整合加密方案(ECIES):基於橢圓曲線的方案。
- DHKEM:Diffie-Hellman KEM 在RFC 9180中定義。
提供者可以添加新的 KEM 實現,例如 Kyber 等後量子 KEM,而無需更改我們的應用程式邏輯。
4. 封裝共享密鑰
在傳送方,我們需要使用接收方的公鑰封裝一個新的共用金鑰。
讓我們來看看如何使用KemUtils類別進行封裝:
public class KemUtils {
public record KemResult(SecretKey sharedSecret, byte[] encapsulation) {}
public static KemResult encapsulate(String algorithm, PublicKey publicKey) throws Exception {
KEM kem = KEM.getInstance(algorithm);
KEM.Encapsulator encapsulator = kem.newEncapsulator(publicKey);
KEM.Encapsulated result = encapsulator.encapsulate();
return new KemResult(result.key(), result.encapsulation());
}
}
` encapsulate()方法執行整個 KEM 封裝過程。它首先取得指定演算法(例如「DHKEM」)的 KEM 實例,並使用接收者的公鑰建立一個Encapsulator 。此封裝器透過encapsulate()來產生共享金鑰和相關的封裝數據,從而履行發送方的職責。然後,該方法將這兩個輸出打包到一個KemResult記錄中,並將其傳回給呼叫方。
為了讓 KEM 操作更容易使用,我們定義了一個名為KemResult的簡單記錄:
public record KemResult(SecretKey sharedSecret, byte[] encapsulation) {}
KemResult記錄是 KEM 操作的兩個輸出的簡單、不可變的容器-共用對稱金鑰( SecretKey )和對應的封裝 blob( byte[] )。
然後,發送方將封裝位元組陣列傳送給接收方,同時對sharedSecret保密,用於加密或金鑰派生。
5. 解封裝共享密鑰
在接收方,我們需要使用封裝 blob 和接收方的私鑰來恢復相同的共用金鑰。
接收者呼叫我們工具類別中的decapsulate()方法,並傳遞演算法名稱(“DHKEM”)、接收方的私鑰以及從發送方收到的封裝 blob:
public static KemResult decapsulate(String algorithm, PrivateKey privateKey, byte[] encapsulation)
throws Exception {
KEM kem = KEM.getInstance(algorithm);
KEM.Decapsulator decapsulator = kem.newDecapsulator(privateKey);
SecretKey recoveredSecret = decapsulator.decapsulate(encapsulation);
return new KemResult(recoveredSecret, encapsulation);
}
該方法首先為指定的演算法建立一個 KEM 實例,並使用接收方的私鑰初始化一個Decapsulator 。然後,該方法呼叫decapsulate(encapsulation)函數,從發送方收到的封裝資料塊中重構共用對稱金鑰( SecretKey )。最後,它會傳回一個新的KemResult其中包含恢復的金鑰和封裝資料。
6. 寫作測試
為了確保我們的 KEM 封裝和解封裝邏輯正常運作,我們可以編寫一個單元測試來驗證雙方。此測試模擬完整的 KEM 金鑰交換過程,從金鑰產生到秘密驗證。
讓我們來建立測試方法:
@Test
void givenKem_whenSenderEncapsulatesAndReceiverDecapsulates_thenSecretsMatch() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519");
KeyPair keyPair = kpg.generateKeyPair();
KemUtils.KemResult senderResult = KemUtils.encapsulate("DHKEM", keyPair.getPublic());
assertNotNull(senderResult.sharedSecret());
assertNotNull(senderResult.encapsulation());
KemUtils.KemResult receiverResult = KemUtils.decapsulate("DHKEM", keyPair.getPrivate(),
senderResult.encapsulation());
SecretKey senderSecret = senderResult.sharedSecret();
SecretKey receiverSecret = receiverResult.sharedSecret();
assertArrayEquals(senderSecret.getEncoded(), receiverSecret.getEncoded(),
"Shared secrets from sender and receiver must match");
}
測試首先產生「X25519」金鑰對,代表接收者的公鑰和私鑰。主要測試使用「DHKEM」演算法模擬完整的發送方-接收方金鑰交換:發送方使用接收方的公鑰封裝共用金鑰,接收方使用其私鑰解封裝該金鑰。
此測試斷言產生的共用金鑰和封裝區塊均不為空,然後驗證發送方和接收方共用金鑰的編碼位元組是否相同。
現在,讓我們建立一個測試案例,驗證當使用錯誤的私鑰時,KEM 解封裝過程是否會失敗:
@Test
void givenDifferentReceiverKey_whenDecapsulate_thenFails() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPair wrongKeyPair = kpg.generateKeyPair();
KemUtils.KemResult senderResult = KemUtils.encapsulate("DHKEM", keyPair.getPublic());
assertThrows(Exception.class, () ->
KemUtils.decapsulate("DHKEM", wrongKeyPair.getPrivate(), senderResult.encapsulation()));
}
它首先產生一個不匹配的金鑰對(本例中使用“RSA”),以模擬未經授權的接收者。然後,測試使用合法接收者的公鑰執行封裝,產生封裝區塊和共用金鑰。
當嘗試使用錯誤的私鑰解封裝此資料塊時,操作預期會失敗並觸發異常。 `assertThrows` assertThrows證實了這一行為,驗證了 KEM 強制執行封裝資料與正確金鑰對之間的嚴格加密綁定。
7. 結論
在本教程中,我們探討如何利用 Java 21 的 KEM API。我們示範如何使用接收方的公鑰封裝共用金鑰。然後,我們傳輸了封裝後的密鑰塊。最後,在接收方,我們對其進行解封裝,從而重構出相同的金鑰。
透過單元測試,我們驗證了 KEM 工作流程能夠正確地為預期的接收者產生相同的金鑰,同時防止使用錯誤的私鑰進行未經授權的解封裝。
和往常一樣,原始碼可以在 GitHub 上找到。