2

.NET Secret Manager 安全強化 - 無腦加密版

 1 month ago
source link: https://blog.darkthread.net/blog/encrypt-dotnet-secret-manager/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

.NET Secret Manager 安全強化

昨天提到 .NET Secret Manager 機制,可取代 appsettings.json 或環境變數作為本機開發測試時的 ApiKey 或密碼保存容器,但美中不足是它用明碼儲存,檔案一旦外流便無險可守。我想應用 Secret Manager 的情境除了開發測試,也會用於在本機跑一些自用的 RAG 或 ChatGPT 整合應用,既然是 Windows 平台,用 DPAPI/ProtectedData 無腦加密感覺是好主意。
(註:DPAPI 的優點是加密不需想金鑰,由 OS 層次鎖定特定主機及登入帳號,即便被改掉密碼強行登入也無法解密)

構想很簡單,我打算設計 ApiKey 或密碼時用 PowerShell SecureString 加密後再存入,設定部分沿用 dotnet user-secrets set CLI;讀取時也沿用 ConfigurationBuilder().AddUserSecrets<Program>()IConfigration["key-name"] 讀取再用 ProtectedData.Unprotect() 解密,就這麼簡單。

最近在看微軟範例時,看到許多場合開始用 C# Notebook 示範測試程式片段。這個概念源自 Python 好用的 Jupyter 筆記本,可在文件穿插 Markdown 說明及 Pythong 程式,很適用來寫作及分享學習筆記;而在 VSCode 裡也有個對等的 Polyglot Notebook,一樣可以穿插程式區塊及 Markdown 區塊,但支援的語言擴及 C#、F#、HTML、JavaScript、KQL、PowerShell、SQL,而在 VSCode 的好處是全程有 Github Copilot 黑魔法加持,用左手就能把程式寫完(誤)。(延伸閱讀:用 Jupyter Notebook 寫 C# / PowerShell / JavaScript 筆記 - Ploygot Notebooks)

Fig1_638465383295508551.png

我先開了一個 Ploygots .ipynb 文件,在其中寫分別用 PowerShell 讀入字串轉成 SecureString,ConvertFrom-SecureString 轉成 16 進位數字字串解析為 byte[],最後編碼成 Base64 字串寫入檔案 D:\Temp\secret.txt。之後用 C# 引用 System.Security.Cryptography.ProtectedData,讀取 D:\Temp\secret.txt 解碼 Base64 後用 ProtectedData.Unprotect 解密還原得到原文。同一個 .ipynb 中同時使用 PowerShell 及 C#,還自帶文字說明,感覺很酷。

Fig2_638465383298691659.png

實驗成功後,下一步我用一小段程式以 PowerShell 讀取使用者輸入內容轉 SecureString,將 16 進位字串用 dotnet.exe user-secrets set "ApiKey" "dpapi:$encApiKey" --project ".\user-secret.csproj" 在 Secret Manager 存入 ApiKey 設定 (前方加上 enc: 前綴,方便 C# 端判斷是否需解密),我還另外設定一筆未加密的ApiUrl設定作為對照;之後用 PowerShell 從 .csproj XML 得到 UserSecretsId 算出 secrets.json 完整路徑,讀取內容觀察寫入值:

$encApiKey = Read-Host "ApiKey" -AsSecureString | ConvertFrom-SecureString
dotnet.exe user-secrets set "ApiKey" "enc:$encApiKey" --project ".\user-secret.csproj"
dotnet.exe user-secrets set "ApiUrl" "https://api.example.com" --project ".\user-secret.csproj"

Fig3_638465383300392482.png

設定完成後,來看 C# Program.cs 端如何讀取。還蠻簡單的,new ConfigurationBuilder().AddUserSecrets<Program>().Build() 引用 Secret Manager 建立 IConfiguration,用 config["ApiKey"] 讀到 "enc:01000000d08c9ddf0115d111..." 後,偵測到 "enc:" 字首時用將 16 進位字串還原 byte[],用 ProtectedData.Unprotect() 解密;若無 "enc:" 前綴則為明碼,直接取值使用。

using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Configuration;

IConfiguration config = new ConfigurationBuilder()
        .AddUserSecrets<Program>().Build();
Console.WriteLine("API Key: " + ReadValueFromSecretManager("ApiKey"));
Console.WriteLine("API Url: " + ReadValueFromSecretManager("ApiUrl"));

string ReadValueFromSecretManager(string key) {
    var val = config[key];
    if (string.IsNullOrEmpty(val) || !val.StartsWith("enc:")) return val!;
    var hex = val.Substring(4);
    var bytes = new byte[hex.Length / 2];
    for (int i = 0; i < bytes.Length; i++) {
        bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
    }
    return Encoding.Unicode.GetString(ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser));
}

測試結果,加密的 ApiKey 與未加密碼的 ApiUrl 都成功讀取,輕鬆搞定:

Fig4_638465383302101502.png

就醬,透過幾行程式為 Secret Manager 加上加密功能,用起來就更安心囉~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK