Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-mvc/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-core/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Asp.net mvc ASP.NET内核中的加密配置_Asp.net Mvc_Asp.net Core_Asp.net Core Mvc_.net Core - Fatal编程技术网

Asp.net mvc ASP.NET内核中的加密配置

Asp.net mvc ASP.NET内核中的加密配置,asp.net-mvc,asp.net-core,asp.net-core-mvc,.net-core,Asp.net Mvc,Asp.net Core,Asp.net Core Mvc,.net Core,随着web.config的消失,在使用ASP.NET核心构建的web应用程序的配置中,存储敏感信息(密码、令牌)的首选方式是什么 是否有一种方法可以自动在appsettings.json中获取加密的配置部分?至少在开发过程中,用户机密看起来是存储密码和应用程序机密的一个很好的解决方案 检查一下。您还可以复习其他SO问题 这只是在开发过程中“隐藏”您的秘密的一种方法,避免将它们泄露到源代码树中;机密管理器工具不加密存储的机密,不应将其视为受信任的存储 如果要将加密的appsettings.json

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


是否有一种方法可以自动在
appsettings.json
中获取加密的配置部分?

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

检查一下。您还可以复习其他SO问题

这只是在开发过程中“隐藏”您的秘密的一种方法,避免将它们泄露到源代码树中;机密管理器工具不加密存储的机密,不应将其视为受信任的存储

如果要将加密的
appsettings.json
带到生产环境中,可以通过构建

例如:

公共类CustomConfigProvider:ConfigurationProvider,IConfigurationSource
{
公共CustomConfigProvider()
{
}
公共覆盖无效负载()
{
数据=未加密配置();
}
私有IDictionary未加密配置()
{
//在这里执行您需要执行的任何操作,例如加载文件并逐个密钥取消加密
//比如:
var configValues=新字典
{
{“key1”,“uncryptedvalue1”},
{“key2”,“uncryptedvalue2”}
};
返回配置值;
}
专用IDictionary CreateAndSaveDefaultValues(IDictionary defaultDictionary)
{
var configValues=新字典
{
{“key1”,“encryptedValue1”},
{“key2”,“encryptedValue2”}
};
返回配置值;
}
公共IConfigurationBuilder生成(IConfigurationBuilder)
{
返回新的CustomConfigProvider();
}
}
为扩展方法定义静态类:

公共静态类CustomConfigProviderExtensions
{              
公共静态IConfigurationBuilder AddEncryptedProvider(此IConfigurationBuilder)
{
返回builder.Add(新的CustomConfigProvider());
}
}
然后您可以激活它:

//设置配置源。
var builder=new ConfigurationBuilder()
.AddJsonFile(“appsettings.json”)
.AddEncryptedProvider()
.AddJsonFile($“appsettings.{env.EnvironmentName}.json”,可选:true);

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

检查一下。您还可以复习其他SO问题

这只是在开发过程中“隐藏”您的秘密的一种方法,避免将它们泄露到源代码树中;机密管理器工具不加密存储的机密,不应将其视为受信任的存储

如果要将加密的
appsettings.json
带到生产环境中,可以通过构建

例如:

公共类CustomConfigProvider:ConfigurationProvider,IConfigurationSource
{
公共CustomConfigProvider()
{
}
公共覆盖无效负载()
{
数据=未加密配置();
}
私有IDictionary未加密配置()
{
//在这里执行您需要执行的任何操作,例如加载文件并逐个密钥取消加密
//比如:
var configValues=新字典
{
{“key1”,“uncryptedvalue1”},
{“key2”,“uncryptedvalue2”}
};
返回配置值;
}
专用IDictionary CreateAndSaveDefaultValues(IDictionary defaultDictionary)
{
var configValues=新字典
{
{“key1”,“encryptedValue1”},
{“key2”,“encryptedValue2”}
};
返回配置值;
}
公共IConfigurationBuilder生成(IConfigurationBuilder)
{
返回新的CustomConfigProvider();
}
}
为扩展方法定义静态类:

公共静态类CustomConfigProviderExtensions
{              
公共静态IConfigurationBuilder AddEncryptedProvider(此IConfigurationBuilder)
{
返回builder.Add(新的CustomConfigProvider());
}
}
然后您可以激活它:

//设置配置源。
var builder=new ConfigurationBuilder()
.AddJsonFile(“appsettings.json”)
.AddEncryptedProvider()
.AddJsonFile($“appsettings.{env.EnvironmentName}.json”,可选:true);

我不想编写自定义提供程序–工作量太大了。我只是想利用JSONConfiguration Provider,所以我找到了一种适合我的方法,希望它能帮助别人

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

我不想写一个定制的提供者——工作量太大了。我只是想利用JSONConfiguration Provider,所以我找到了一种适合我的方法,希望它能帮助别人

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

我同意@CoderSteve的观点,编写一个全新的提供者太费事了。它也不基于现有的标准JSON体系结构。这是我在标准JSON体系结构之上提出的一个解决方案,它使用首选的.Net核心加密库,并且对DI非常友好

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);
        }
    }
}
用法:
http://localhost/cryptography/encrypt?section=SectionName:KeyName&value=PlainTextValue

我同意@CoderSteve的观点,即编写一个全新的提供者太费力了。它也不基于现有的标准JSON体系结构。这是我在标准JSON体系结构之上提出的一个解决方案,它使用首选的.Net核心加密库,并且对DI非常友好

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 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);
    });
}
用法:
http://localhost/cryptography/encrypt?section=SectionName:KeyName&value=PlainTextValue

公共静态IServiceCollection配置受保护(此IServiceCollection服务,IConfiguration部分)w
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);
    });
}
{
    "authentication": {
        "credentials": [
            {
                user: "john",
                password: "just a password"
            },
            {
                user: "jane",
                password: "just a password"
            }
        ]
    }
}
{
    "authentication": {
        "credentials": [
            {
                "user": "john",
                "password": "IUVOQyEBAAAA0Iyd3wEV0R=="
            },
            {
                "user": "jane",
                "password": "IUVOQyEBAAAA0Iyd3wEV0R=="
            }
        ]
    }
}
public class AuthenticationConfiguration
{
    [JsonProperty("credentials")]
    public Collection<CredentialConfiguration> Credentials { get; set; }
}

public class CredentialConfiguration
{
    [JsonProperty("user")]
    public string User { get; set; }
    [JsonProperty("password")]
    public string Password { get; set; }
}
//Note that the regular expression will cause the authentication.credentials.password path to be encrypted.
//Also note that the byte[] contains the entropy to increase security
var configurationBuilder = new ConfigurationBuilder()
    .AddProtectedJsonFile("authentication.json", true, new byte[] { 9, 4, 5, 6, 2, 8, 1 },
        new Regex("authentication:credentials:[0-9]*:password"));

var configuration = configurationBuilder.Build();
var authenticationConfiguration = configuration.GetSection("authentication").Get<AuthenticationConfiguration>();

//Get the decrypted password from the encrypted JSON file.
//Note that the ProtectedJsonConfigurationProvider.TryGet() method is called (I didn't expect that :D!)
var password = authenticationConfiguration.Credentials.First().Password
/// <summary>Represents a <see cref="ProtectedJsonConfigurationProvider"/> source</summary>
public class ProtectedJsonConfigurationSource : JsonConfigurationSource
{
    /// <summary>Gets the byte array to increse protection</summary>
    internal byte[] Entropy { get; private set; }

    /// <summary>Represents a <see cref="ProtectedJsonConfigurationProvider"/> source</summary>
    /// <param name="entropy">Byte array to increase protection</param>
    /// <exception cref="ArgumentNullException"/>
    public ProtectedJsonConfigurationSource(byte[] entropy)
    {
        this.Entropy = entropy ?? throw new ArgumentNullException(Localization.EntropyNotSpecifiedError);
    }

    /// <summary>Builds the configuration provider</summary>
    /// <param name="builder">Builder to build in</param>
    /// <returns>Returns the configuration provider</returns>
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);
        return new ProtectedJsonConfigurationProvider(this);
    }

    /// <summary>Gets or sets the protection scope of the configuration provider. Default value is <see cref="DataProtectionScope.CurrentUser"/></summary>
    public DataProtectionScope Scope { get; set; }
    /// <summary>Gets or sets the regular expressions that must match the keys to encrypt</summary>
    public IEnumerable<Regex> EncryptedKeyExpressions { get; set; }
}

/// <summary>Represents a provider that protects a JSON configuration file</summary>
public partial class ProtectedJsonConfigurationProvider : JsonConfigurationProvider
{
    private readonly ProtectedJsonConfigurationSource protectedSource;
    private readonly HashSet<string> encryptedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
    private static readonly byte[] encryptedPrefixBytes = Encoding.UTF8.GetBytes("!ENC!");

    /// <summary>Checks whether the given text is encrypted</summary>
    /// <param name="text">Text to check</param>
    /// <returns>Returns true in case the text is encrypted</returns>
    private bool isEncrypted(string text)
    {
        if (text == null) { return false; }

        //Decode the data in order to verify whether the decoded data starts with the expected prefix
        byte[] decodedBytes;
        try { decodedBytes = Convert.FromBase64String(text); }
        catch (FormatException) { return false; }

        return decodedBytes.Length >= encryptedPrefixBytes.Length
            && decodedBytes.AsSpan(0, encryptedPrefixBytes.Length).SequenceEqual(encryptedPrefixBytes);
    }

    /// <summary>Converts the given key to the JSON token path equivalent</summary>
    /// <param name="key">Key to convert</param>
    /// <returns>Returns the JSON token path equivalent</returns>
    private string convertToTokenPath(string key)
    {
        var jsonStringBuilder = new StringBuilder();

        //Split the key by ':'
        var keyParts = key.Split(':');
        for (var keyPartIndex = 0; keyPartIndex < keyParts.Length; keyPartIndex++)
        {
            var keyPart = keyParts[keyPartIndex];

            if (keyPart.All(char.IsDigit)) { jsonStringBuilder.Append('[').Append(keyPart).Append(']'); }
            else if (keyPartIndex > 0) { jsonStringBuilder.Append('.').Append(keyPart); }
            else { jsonStringBuilder.Append(keyPart); }
        }

        return jsonStringBuilder.ToString();
    }

    /// <summary>Writes the given encrypted key/values to the JSON oconfiguration file</summary>
    /// <param name="encryptedKeyValues">Encrypted key/values to write</param>
    private void writeValues(IDictionary<string, string> encryptedKeyValues)
    {
        try
        {
            if (encryptedKeyValues == null || encryptedKeyValues.Count == 0) { return; }

            using (var stream = new FileStream(this.protectedSource.Path, FileMode.Open, FileAccess.ReadWrite))
            {
                JObject json;

                using (var streamReader = new StreamReader(stream, Encoding.UTF8, true, 4096, true))
                {
                    using (var jsonTextReader = new JsonTextReader(streamReader))
                    {
                        json = JObject.Load(jsonTextReader);

                        foreach (var encryptedKeyValue in encryptedKeyValues)
                        {
                            var tokenPath = this.convertToTokenPath(encryptedKeyValue.Key);
                            var value = json.SelectToken(tokenPath) as JValue;
                            if (value.Value != null) { value.Value = encryptedKeyValue.Value; }
                        }
                    }
                }

                stream.Seek(0, SeekOrigin.Begin);
                using (var streamWriter = new StreamWriter(stream))
                {
                    using (var jsonTextWriter = new JsonTextWriter(streamWriter) { Formatting = Formatting.Indented })
                    {
                        json.WriteTo(jsonTextWriter);
                    }
                }
            }
        }
        catch (Exception exception)
        {
            throw new Exception(string.Format(Localization.ProtectedJsonConfigurationWriteEncryptedValues, this.protectedSource.Path), exception);
        }
    }

    /// <summary>Represents a provider that protects a JSON configuration file</summary>
    /// <param name="source">Settings of the source</param>
    /// <see cref="ArgumentNullException"/>
    public ProtectedJsonConfigurationProvider(ProtectedJsonConfigurationSource source) : base(source)
    {
        this.protectedSource = source as ProtectedJsonConfigurationSource;
    }

    /// <summary>Loads the JSON data from the given <see cref="Stream"/></summary>
    /// <param name="stream"><see cref="Stream"/> to load</param>
    public override void Load(Stream stream)
    {
        //Call the base method first to ensure the data to be available
        base.Load(stream);

        var expressions = protectedSource.EncryptedKeyExpressions;
        if (expressions != null)
        {
            //Dictionary that contains the keys (and their encrypted value) that must be written to the JSON file
            var encryptedKeyValuesToWrite = new Dictionary<string, string>();

            //Iterate through the data in order to verify whether the keys that require to be encrypted, as indeed encrypted.
            //Copy the keys to a new string array in order to avoid a collection modified exception
            var keys = new string[this.Data.Keys.Count];
            this.Data.Keys.CopyTo(keys, 0);

            foreach (var key in keys)
            {
                //Iterate through each expression in order to check whether the current key must be encrypted and is encrypted.
                //If not then encrypt the value and overwrite the key
                var value = this.Data[key];
                if (!string.IsNullOrEmpty(value) && expressions.Any(e => e.IsMatch(key)))
                {
                    this.encryptedKeys.Add(key);

                    //Verify whether the value is encrypted
                    if (!this.isEncrypted(value))
                    {
                        var protectedValue = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), protectedSource.Entropy, protectedSource.Scope);
                        var protectedValueWithPrefix = new List<byte>(encryptedPrefixBytes);
                        protectedValueWithPrefix.AddRange(protectedValue);

                        //Convert the protected value to a base-64 string in order to mask the prefix (for cosmetic purposes)
                        //and overwrite the key with the encrypted value
                        var protectedBase64Value = Convert.ToBase64String(protectedValueWithPrefix.ToArray());
                        encryptedKeyValuesToWrite.Add(key, protectedBase64Value);
                        this.Data[key] = protectedBase64Value;
                    }
                }
            }

            //Write the encrypted key/values to the JSON configuration file
            this.writeValues(encryptedKeyValuesToWrite);
        }
    }

    /// <summary>Attempts to get the value of the given key</summary>
    /// <param name="key">Key to get</param>
    /// <param name="value">Value of the key</param>
    /// <returns>Returns true in case the key has been found</returns>
    public override bool TryGet(string key, out string value)
    {
        if (!base.TryGet(key, out value)) { return false; }
        else if (!this.encryptedKeys.Contains(key)) { return true; }

        //Key is encrypted and must therefore be decrypted in order to return.
        //Note that the decoded base-64 bytes contains the encrypted prefix which must be excluded when unprotection
        var protectedValueWithPrefix = Convert.FromBase64String(value);
        var protectedValue = new byte[protectedValueWithPrefix.Length - encryptedPrefixBytes.Length];
        Buffer.BlockCopy(protectedValueWithPrefix, encryptedPrefixBytes.Length, protectedValue, 0, protectedValue.Length);

        var unprotectedValue = ProtectedData.Unprotect(protectedValue, this.protectedSource.Entropy, this.protectedSource.Scope);
        value = Encoding.UTF8.GetString(unprotectedValue);
        return true;
    }

/// <summary>Provides extensions concerning <see cref="ProtectedJsonConfigurationProvider"/></summary>
public static class ProtectedJsonConfigurationProviderExtensions
{
    /// <summary>Adds a protected JSON file</summary>
    /// <param name="configurationBuilder"><see cref="IConfigurationBuilder"/> in which to apply the JSON file</param>
    /// <param name="path">Path to the JSON file</param>
    /// <param name="optional">Specifies whether the JSON file is optional</param>
    /// <param name="entropy">Byte array to increase protection</param>
    /// <returns>Returns the <see cref="IConfigurationBuilder"/></returns>
    /// <exception cref="ArgumentNullException"/>
    public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder configurationBuilder, string path, bool optional, byte[] entropy, params Regex[] encryptedKeyExpressions)
    {
        var source = new ProtectedJsonConfigurationSource(entropy)
        {
            Path = path,
            Optional = optional,
            EncryptedKeyExpressions = encryptedKeyExpressions
        };

        return configurationBuilder.Add(source);
    }
}