You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
        }
    }
}

关键注意事项

  1. 异常处理:实际生产环境需添加API调用失败的重试、降级逻辑,避免因远程服务不可用导致应用启动失败。
  2. 密钥缓存:可使用MemoryCache缓存密钥,减少远程调用次数,降低依赖风险。
  3. 认证安全:API令牌/Vault令牌不要硬编码,可存储在环境变量或本地受限权限的配置文件中(权限仅授予应用运行账号)。
  4. 权限控制:确保应用运行账号具备远程API/Vault的访问权限,遵循最小权限原则。

内容的提问来源于stack exchange,提问作者addcolor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 02:18:13