ASP.NET Core中的加密配置

问题描述 投票:24回答:5

随着web.config消失,在使用ASP.NET Core构建的Web应用程序的配置中存储敏感信息(密码,令牌)的首选方法是什么?

有没有办法在appsetttings.json中自动获取加密配置部分?

asp.net-mvc asp.net-core asp.net-core-mvc .net-core
5个回答
17
投票

用户机密看起来是一个很好的解决方案,用于存储密码,通常是应用程序机密,至少在开发过程中。

检查这个articlethis。您也可以查看this其他SO问题。

这只是一种在开发过程中“隐藏”您的秘密并避免将它们泄露到源树中的方法; Secret Manager工具不会对存储的机密进行加密,也不应将其视为可信存储。

如果您想将加密的appsettings.json带到生产环境,那么没有限制。您可以构建自定义配置提供程序。检查this

例如:

    public class CustomConfigProvider : ConfigurationProvider, IConfigurationSource
    {
        public CustomConfigProvider()
        {

        }

        public override void Load()
        {
            Data = UnencryptMyConfiguration();
        }

        private IDictionary<string, string> UnencryptMyConfiguration()
        {
            // do whatever you need to do here, for example load the file and unencrypt key by key
            //Like:
           var configValues = new Dictionary<string, string>
           {
                {"key1", "unencryptedValue1"},
                {"key2", "unencryptedValue2"}
           };
           return configValues;
        }

        private IDictionary<string, string> CreateAndSaveDefaultValues(IDictionary<string, string> defaultDictionary)
        {
            var configValues = new Dictionary<string, string>
            {
                {"key1", "encryptedValue1"},
                {"key2", "encryptedValue2"}
            };
            return configValues;                
        }
        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
           return new CustomConfigProvider();
        }
    }

为扩展方法定义静态类:

public static class CustomConfigProviderExtensions
{              
        public static IConfigurationBuilder AddEncryptedProvider(this IConfigurationBuilder builder)
        {
            return builder.Add(new CustomConfigProvider());
        }
}

然后你可以激活它:

 // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddEncryptedProvider()
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

11
投票

我不想写一个自定义提供者 - 太多的工作。我只是想进入JsonConfigurationProvider,所以我找到了一种适合我的方式,希望它可以帮到某些人。

public class JsonConfigurationProvider2 : JsonConfigurationProvider
{
    public JsonConfigurationProvider2(JsonConfigurationSource2 source) : base(source)
    {
    }

    public override void Load(Stream stream)
    {
        // Let the base class do the heavy lifting.
        base.Load(stream);

        // Do decryption here, you can tap into the Data property like so:

         Data["abc:password"] = MyEncryptionLibrary.Decrypt(Data["abc:password"]);

        // But you have to make your own MyEncryptionLibrary, not included here
    }
}

public class JsonConfigurationSource2 : JsonConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);
        return new JsonConfigurationProvider2(this);
    }
}

public static class JsonConfigurationExtensions2
{
    public static IConfigurationBuilder AddJsonFile2(this IConfigurationBuilder builder, string path, bool optional,
        bool reloadOnChange)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
        if (string.IsNullOrEmpty(path))
        {
            throw new ArgumentException("File path must be a non-empty string.");
        }

        var source = new JsonConfigurationSource2
        {
            FileProvider = null,
            Path = path,
            Optional = optional,
            ReloadOnChange = reloadOnChange
        };

        source.ResolveFileProvider();
        builder.Add(source);
        return builder;
    }
}

10
投票

我同意@CoderSteve的观点,写一个全新的提供者是太多的工作。它也不基于现有的标准JSON架构。这是一个解决方案,我提出了基于标准JSON架构的构建,使用首选的.Net Core加密库,并且非常友好。

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddProtectedConfiguration(this IServiceCollection services)
    {
        services
            .AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(@"c:\keys"))
            .ProtectKeysWithDpapi();

        return services;
    }

    public static IServiceCollection ConfigureProtected<TOptions>(this IServiceCollection services, IConfigurationSection section) where TOptions: class, new()
    {
        return services.AddSingleton(provider =>
        {
            var dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>();
            section = new ProtectedConfigurationSection(dataProtectionProvider, section);

            var options = section.Get<TOptions>();
            return Options.Create(options);
        });
    }

    private class ProtectedConfigurationSection : IConfigurationSection
    {
        private readonly IDataProtectionProvider _dataProtectionProvider;
        private readonly IConfigurationSection _section;
        private readonly Lazy<IDataProtector> _protector;

        public ProtectedConfigurationSection(
            IDataProtectionProvider dataProtectionProvider,
            IConfigurationSection section)
        {
            _dataProtectionProvider = dataProtectionProvider;
            _section = section;

            _protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector(section.Path));
        }

        public IConfigurationSection GetSection(string key)
        {
            return new ProtectedConfigurationSection(_dataProtectionProvider, _section.GetSection(key));
        }

        public IEnumerable<IConfigurationSection> GetChildren()
        {
            return _section.GetChildren()
                .Select(x => new ProtectedConfigurationSection(_dataProtectionProvider, x));
        }

        public IChangeToken GetReloadToken()
        {
            return _section.GetReloadToken();
        }

        public string this[string key]
        {
            get => GetProtectedValue(_section[key]);
            set => _section[key] = _protector.Value.Protect(value);
        }

        public string Key => _section.Key;
        public string Path => _section.Path;

        public string Value
        {
            get => GetProtectedValue(_section.Value);
            set => _section.Value = _protector.Value.Protect(value);
        }

        private string GetProtectedValue(string value)
        {
            if (value == null)
                return null;

            return _protector.Value.Unprotect(value);
        }
    }
}

连接受保护的配置部分,如下所示:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    // Configure normal config settings
    services.Configure<MySettings>(Configuration.GetSection("MySettings"));

    // Configure protected config settings
    services.AddProtectedConfiguration();
    services.ConfigureProtected<MyProtectedSettings>(Configuration.GetSection("MyProtectedSettings"));
}

您可以使用如下控制器轻松地为配置文件创建加密值:

[Route("encrypt"), HttpGet, HttpPost]
public string Encrypt(string section, string value)
{
    var protector = _dataProtectionProvider.CreateProtector(section);
    return protector.Protect(value);
}

用法:http://localhost/cryptography/encrypt?section=SectionName:KeyName&value=PlainTextValue


0
投票
public static IServiceCollection ConfigureProtected<TOptions>(this IServiceCollection services, IConfigurationSection section) where TOptions: class, new()
{
    return services.AddSingleton(provider =>
    {
        var dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>();
        var protectedSection = new ProtectedConfigurationSection(dataProtectionProvider, section);

        var options = protectedSection.Get<TOptions>();
        return Options.Create(options);
    });
}

这种方法是正确的


0
投票

只是一些澄清,以帮助避免问题。当您加密某个值时,它会将该部分用作“目的”(https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/purpose-strings?view=aspnetcore-2.2)当您收到“Payload无效”或类似内容时,您用于加密它的目的可能与解密它的目的不同。所以,假设我的appsettings.json中有一个名为“SecureSettings”的第一级部分,其中包含一个连接字符串:

{
"SecureSettings": 
  {
    "ConnectionString":"MyClearTextConnectionString"
  }
}

为了加密这个值,我打电话给:http://localhost/cryptography/encrypt?section=SecureSettings:ConnectionString&value=MyClearTextConnectionString

您可能不想在应用程序本身中保留加密控制器顺便说一下。

© www.soinside.com 2019 - 2024. All rights reserved.