在 API 通訊加密中,使用 固定金鑰 (Static Key Wrapping) 雖然實作簡單,但一旦金鑰外洩,後果嚴重。若能在每次登入時動態產生金鑰 (Dynamic Key Wrapping),可有效縮短攻擊面並降低風險。本文將介紹固定金鑰與動態金鑰的差異,並示範如何在
Bee.NET
框架中設定與實作動態金鑰。
? 範例程式:jsonrpc-sample
Bee.NET
動態金鑰設定方式透過 BeeSettingsEditor 工具編輯 SystemSettings.xml,只需設定 <ApiEncryptionKeyProvider>
:
<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 加解密時自動套用。
Bee.NET
的 Login
方法在登入時都會產生金鑰並存入 SessionInfo
,差別在於後續是否使用:
SessionInfo
的金鑰不會被使用。SessionInfo
,API 透過 AccessToken 取出對應金鑰。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
};
}
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();
}
}
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);
}
}
以下為 未加密 與 加密 格式的範例,示範如何傳遞 Employee.Hello
方法的參數:
{
"jsonrpc": "2.0",
"method": "Employee.Hello",
"params": {
"value": {
"$type": "Custom.Define.HelloArgs, Custom.Define",
"userName": "Jeff"
}
},
"id": "85b2c2c3-9854-4eb9-b6dc-6a82a9165fc3"
}
{
"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 字串,並透過動態金鑰進行解密。
Login
方法透過 IApiEncryptionKeyProvider
介面即可同時支援 靜態與動態金鑰,由設定檔決定模式。? HackMD 原文筆記:
? https://hackmd.io/@jeff377/api-dynamic-key-encryption
? 歡迎轉載,請註明出處
? 歡迎追蹤我的技術筆記與實戰經驗分享
Facebook | HackMD | GitHub | NuGet