# API 使用動態金鑰加密資料 ![api-dynamic-key-encryption](https://hackmd.io/_uploads/SkxipGtpwll.png) > 在 API 通訊加密中,使用 **固定金鑰 (Static Key Wrapping)** 雖然實作簡單,但一旦金鑰外洩,後果嚴重。若能在每次登入時動態產生金鑰 (**Dynamic Key Wrapping**),可有效縮短攻擊面並降低風險。本文將介紹固定金鑰與動態金鑰的差異,並示範如何在 `Bee.NET` 框架中設定與實作動態金鑰。 📦 範例程式:[jsonrpc-sample](https://github.com/jeff377/jsonrpc-sample) --- ## 1️⃣ 固定金鑰 vs 動態金鑰差異 ### 固定金鑰 (Static Key Wrapping) - **概念**:所有用戶端共用同一把 API 加密金鑰,並儲存在後端設定檔。 - **優點** - 架構簡單,不需維護 Session 狀態。 - 適合內網或低風險系統。 - **缺點** - 金鑰一旦外洩,歷史封包可被回溯解密。 - 必須人工輪換金鑰,維運成本高。 --- ### 動態金鑰 (Dynamic Key Wrapping) - **概念**:每次用戶登入時,系統隨機產生新的 API 加密金鑰,並與 Session 綁定。 - **優點** - 金鑰外洩僅影響當前 Session,歷史封包不易回溯解密。 - 金鑰自動輪換,安全性高。 - **缺點** - 必須維護 Session 狀態,實作稍複雜。 - 登入流程多一次金鑰產生與加解密。 --- ## 2️⃣ `Bee.NET` 動態金鑰設定方式 透過 [BeeSettingsEditor](https://github.com/jeff377/bee-library/releases/download/BeeSettingsEditor-v1.0.3/BeeSettingsEditor-v1.0.3.zip) 工具編輯 **SystemSettings.xml**,只需設定 `<ApiEncryptionKeyProvider>`: ```xml <SystemSettings> <BackendConfiguration> <!-- ApiEncryptionKeyProvider: - Bee.Business.StaticApiEncryptionKeyProvider, Bee.Business (靜態金鑰) - Bee.Business.DynamicApiEncryptionKeyProvider, Bee.Business (動態金鑰) --> <ApiEncryptionKeyProvider> Bee.Business.DynamicApiEncryptionKeyProvider, Bee.Business </ApiEncryptionKeyProvider> </BackendConfiguration> </SystemSettings> ``` 設定 `DynamicApiEncryptionKeyProvider` 後,系統會在登入時自動產生 **Session 專屬金鑰**,並於 API 加解密時自動套用。 --- ## 3️⃣ Login 方法支援靜態與動態金鑰 `Bee.NET` 的 `Login` 方法在登入時都會產生金鑰並存入 `SessionInfo`,差別在於後續是否使用: - **靜態金鑰模式**:使用設定檔共用金鑰,`SessionInfo` 的金鑰不會被使用。 - **動態金鑰模式**:登入時產生隨機金鑰並寫入 `SessionInfo`,API 透過 AccessToken 取出對應金鑰。 ### Login 方法範例 ```csharp public virtual LoginResult Login(LoginArgs args) { // 1. 驗證帳密 if (!AuthenticateUser(args)) throw new UnauthorizedAccessException("Invalid username or password."); // 2. 依設定產生靜態或動態金鑰 byte[] encryptionKey = BackendInfo.ApiEncryptionKeyProvider.GenerateKeyForLogin(); // 3. 建立 SessionInfo 並存入快取 var sessionInfo = new SessionInfo { AccessToken = Guid.NewGuid(), UserID = args.UserId, ExpiredAt = DateTime.UtcNow.AddHours(1), ApiEncryptionKey = encryptionKey }; CacheFunc.SetSessionInfo(sessionInfo); // 4. 用 RSA 公鑰加密金鑰回傳 string encryptedKey = RsaCryptor.EncryptWithPublicKey( Convert.ToBase64String(encryptionKey), args.ClientPublicKey ); return new LoginResult { AccessToken = sessionInfo.AccessToken, ExpiredAt = sessionInfo.ExpiredAt, ApiEncryptionKey = encryptedKey }; } ``` --- ## 4️⃣ 金鑰提供者實作比較 ### 動態金鑰提供者 DynamicApiEncryptionKeyProvider ```csharp public class DynamicApiEncryptionKeyProvider : IApiEncryptionKeyProvider { public byte[] GetKey(Guid accessToken) { if (BaseFunc.IsEmpty(accessToken)) throw new UnauthorizedAccessException("Access token is required."); var sessionInfo = CacheFunc.GetSessionInfo(accessToken); return sessionInfo?.ApiEncryptionKey ?? throw new UnauthorizedAccessException("Session key not found or expired."); } public byte[] GenerateKeyForLogin() { return AesCbcHmacKeyGenerator.GenerateCombinedKey(); } } ``` --- ### 靜態金鑰提供者 StaticApiEncryptionKeyProvider ```csharp public class StaticApiEncryptionKeyProvider : IApiEncryptionKeyProvider { public byte[] GetKey(Guid accessToken) { return BackendInfo.ApiEncryptionKey ?? throw new InvalidOperationException("BackendInfo.ApiEncryptionKey is not initialized."); } public byte[] GenerateKeyForLogin() { return GetKey(Guid.Empty); } } ``` --- ## 5️⃣ JSON-RPC 請求參數範例 以下為 **未加密** 與 **加密** 格式的範例,示範如何傳遞 `Employee.Hello` 方法的參數: ### (1) 未加密格式 ```json { "jsonrpc": "2.0", "method": "Employee.Hello", "params": { "value": { "$type": "Custom.Define.HelloArgs, Custom.Define", "userName": "Jeff" } }, "id": "85b2c2c3-9854-4eb9-b6dc-6a82a9165fc3" } ``` ### (2) 加密格式 (Encrypted) ```json { "jsonrpc": "2.0", "method": "Employee.Hello", "params": { "format": "Encrypted", "value": { "$type": "System.Byte[], System.Private.CoreLib", "$value": "EAAAAFw44iP8acRR8V6gK6A4g8UwAAAAU6rvBwZkdXaboglEilgv8rSX2gaxK3phfZUW1tJCiNsPoCNt9hPJ1VLO8hnJe9eqJ8NanxPrstyOssDJV8GQjderfsBtBfGer1WcdgnGYy0=" }, "type": "Custom.Define.HelloArgs, Custom.Define" }, "id": "85b2c2c3-9854-4eb9-b6dc-6a82a9165fc3" } ``` > 當 `format` 為 `Encrypted` 時,`value` 會變成經 AES + HMAC 加密後的 Base64 字串,並透過動態金鑰進行解密。 --- ## 6️⃣ 動態金鑰完整流程圖 ```mermaid sequenceDiagram participant Client participant Server participant Cache Note over Client,Server: 使用者登入流程 Client->>Server: 1. 提交帳號密碼 + 公鑰 (Login) Server->>Server: 2. 驗證帳密 Server->>Server: 3. 產生隨機 Session 專屬金鑰 Server->>Cache: 4. 建立 SessionInfo (包含金鑰) Server-->>Client: 5. 回傳 AccessToken + 金鑰 (RSA 公鑰加密) Note over Client,Server: 後續 API 請求 Client->>Server: 6. 提交 AccessToken + 加密資料 Server->>Cache: 7. 依 AccessToken 取得 SessionInfo Server->>Server: 8. 使用 SessionInfo.ApiEncryptionKey 解密 Server-->>Client: 9. 回傳 API 執行結果 ``` --- ## ✨ 總結 - `Login` 方法透過 `IApiEncryptionKeyProvider` 介面即可同時支援 **靜態**與**動態金鑰**,由設定檔決定模式。 - **動態金鑰模式**:登入時自動產生 Session 專屬金鑰,降低回溯解密風險。 - **靜態金鑰模式**:實作簡單,但外洩影響全面,建議僅用於低風險或內網系統。 - 搭配 JSON-RPC 加密傳輸,可有效保護 API 傳遞的參數資料。 --- **📢 歡迎轉載,請註明出處** **📬 歡迎追蹤我的技術筆記與實戰經驗分享** [Facebook](https://www.facebook.com/profile.php?id=61574839666569) | [HackMD](https://hackmd.io/@jeff377) | [GitHub](https://github.com/jeff377) | [NuGet](https://www.nuget.org/profiles/jeff377)