ASP.NET Framework 4.8远程存储访问Key、IV的受保护配置实现问询
.NET Framework 4.8 迁移Protected Configuration密钥到远程API(含Hashicorp Vault示例)
核心思路
你当前使用自定义ProtectedConfigurationProvider从本地文件读取加密密钥(Key)和初始化向量(IV),要迁移到远程API,只需改造自定义Provider的密钥获取逻辑,替换原本地文件读取为远程API调用,其余加密解密逻辑保持和原有实现一致即可。
步骤1:修改配置文件
移除原keyFilePath参数,新增远程API的连接、认证相关配置(以通用远程API和Hashicorp Vault为例)。
通用远程API配置
<configProtectedData> <providers> <add name="MyRemoteApiProvider" useMachineContainer="false" keyContainerName="MyKeyStore" description="自定义Provider:从远程API获取密钥" type="PCP.RemoteApiAlgo, MyAlgo, Version=0.0.0.0, CultureInfo=neutral, PublicKeyToken=88031de5f175f38, processorArchitecture=MSIL" apiUrl="http://你的远程API地址/keys" apiToken="API认证令牌" /> </providers> </configProtectedData> <connectionStrings configProtectionProvider="MyRemoteApiProvider"> <EncryptedData>...</EncryptedData> </connectionStrings>
Hashicorp Vault配置
假设你使用Vault的KVv2密钥引擎,路径为secret/data/myapp,存储了Base64编码的key和iv:
<configProtectedData> <providers> <add name="MyVaultProvider" useMachineContainer="false" keyContainerName="MyKeyStore" description="自定义Provider:从Hashicorp Vault获取密钥" type="PCP.VaultAlgo, MyAlgo, Version=0.0.0.0, CultureInfo=neutral, PublicKeyToken=88031de5f175f38, processorArchitecture=MSIL" vaultUrl="http://你的Vault地址:8200" vaultToken="Vault访问令牌" secretPath="secret/data/myapp" /> </providers> </configProtectedData> <connectionStrings configProtectionProvider="MyVaultProvider"> <EncryptedData>...</EncryptedData> </connectionStrings>
步骤2:实现改造后的自定义Provider
通用远程API版本Provider
继承ProtectedConfigurationProvider,重写初始化、加密、解密方法,替换密钥获取逻辑为API调用:
using System; using System.Configuration; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Xml; namespace PCP { public class RemoteApiAlgo : ProtectedConfigurationProvider { private string _apiUrl; private string _apiToken; private byte[] _encryptionKey; private byte[] _iv; public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { base.Initialize(name, config); _apiUrl = config["apiUrl"] ?? throw new ConfigurationErrorsException("必须配置apiUrl参数"); _apiToken = config["apiToken"] ?? throw new ConfigurationErrorsException("必须配置apiToken参数"); LoadKeysFromRemoteApi(); } private void LoadKeysFromRemoteApi() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiToken); var response = client.GetAsync(_apiUrl).Result; response.EnsureSuccessStatusCode(); var keyData = response.Content.ReadAsAsync<KeyResponse>().Result; // 假设API返回Base64编码的Key和IV _encryptionKey = Convert.FromBase64String(keyData.Key); _iv = Convert.FromBase64String(keyData.IV); } } private class KeyResponse { public string Key { get; set; } public string IV { get; set; } } public override XmlNode Encrypt(XmlNode node) { string plainText = node.OuterXml; byte[] encryptedBytes; using (var aes = Aes.Create()) { aes.Key = _encryptionKey; aes.IV = _iv; var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); } var doc = new XmlDocument(); var encryptedNode = doc.CreateElement("EncryptedData"); encryptedNode.InnerText = Convert.ToBase64String(encryptedBytes); return encryptedNode; } public override XmlNode Decrypt(XmlNode encryptedNode) { string encryptedText = encryptedNode.InnerText; byte[] encryptedBytes = Convert.FromBase64String(encryptedText); string plainText; using (var aes = Aes.Create()) { aes.Key = _encryptionKey; aes.IV = _iv; var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); byte[] plainBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length); plainText = Encoding.UTF8.GetString(plainBytes); } var doc = new XmlDocument(); doc.LoadXml(plainText); return doc.DocumentElement; } } }
Hashicorp Vault版本Provider
调用Vault的KVv2 API获取密钥,需要安装Newtonsoft.Json NuGet包处理JSON响应:
using System; using System.Configuration; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Xml; using Newtonsoft.Json; namespace PCP { public class VaultAlgo : ProtectedConfigurationProvider { private string _vaultUrl; private string _vaultToken; private string _secretPath; private byte[] _encryptionKey; private byte[] _iv; public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { base.Initialize(name, config); _vaultUrl = config["vaultUrl"] ?? throw new ConfigurationErrorsException("必须配置vaultUrl参数"); _vaultToken = config["vaultToken"] ?? throw new ConfigurationErrorsException("必须配置vaultToken参数"); _secretPath = config["secretPath"] ?? throw new ConfigurationErrorsException("必须配置secretPath参数"); LoadKeysFromVault(); } private void LoadKeysFromVault() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Vault-Token", _vaultToken); var response = client.GetAsync($"{_vaultUrl}/v1/{_secretPath}").Result; response.EnsureSuccessStatusCode(); var vaultResponse = response.Content.ReadAsStringAsync().Result; var secretData = JsonConvert.DeserializeObject<VaultSecretResponse>(vaultResponse); _encryptionKey = Convert.FromBase64String(secretData.Data.Data["key"]); _iv = Convert.FromBase64String(secretData.Data.Data["iv"]); } } // 匹配Vault KVv2 API的响应结构 private class VaultSecretResponse { public VaultData Data { get; set; } } private class VaultData { public System.Collections.Generic.Dictionary<string, string> Data { get; set; } } // 加密解密逻辑和通用版本一致,直接复用 public override XmlNode Encrypt(XmlNode node) { string plainText = node.OuterXml; byte[] encryptedBytes; using (var aes = Aes.Create()) { aes.Key = _encryptionKey; aes.IV = _iv; var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); } var doc = new XmlDocument(); var encryptedNode = doc.CreateElement("EncryptedData"); encryptedNode.InnerText = Convert.ToBase64String(encryptedBytes); return encryptedNode; } public override XmlNode Decrypt(XmlNode encryptedNode) { string encryptedText = encryptedNode.InnerText; byte[] encryptedBytes = Convert.FromBase64String(encryptedText); string plainText; using (var aes = Aes.Create()) { aes.Key = _encryptionKey; aes.IV = _iv; var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); byte[] plainBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length); plainText = Encoding.UTF8.GetString(plainBytes); } var doc = new XmlDocument(); doc.LoadXml(plainText); return doc.DocumentElement; } } }
关键注意事项
- 异常处理:实际生产环境需添加API调用失败的重试、降级逻辑,避免因远程服务不可用导致应用启动失败。
- 密钥缓存:可使用
MemoryCache缓存密钥,减少远程调用次数,降低依赖风险。 - 认证安全:API令牌/Vault令牌不要硬编码,可存储在环境变量或本地受限权限的配置文件中(权限仅授予应用运行账号)。
- 权限控制:确保应用运行账号具备远程API/Vault的访问权限,遵循最小权限原则。
内容的提问来源于stack exchange,提问作者addcolor
相关产品推荐
相关产品推荐

