11

I'd like to use my appsettings.json to store a "master password".

This master password would then be used to open up a private key (and its subsequent password store) generated by this excellent password store package: https://github.com/neosmart/SecureStore

The problem is, I can't think of any way to encrypt the master password. I know in .NET 4.5, it was possible to do the following:

1) Place your password into the web.config file

2) Run this script: aspnet_regiis.exe -pef appSettings "C:\myfolder"

3) Your password would end up being encrypted - but read securely by your program.

https://www.codeproject.com/Articles/599416/Encrypting-ASP-NET-Application-Settings

Am I going about this the right way or is there a better practice?

Ryan Battistone
  • 621
  • 1
  • 9
  • 22
  • 1
    That used to work because Internet Information System hosted, now it acts as a reverse proxy or you utilize HttpSys. You might be able to encrypt the file, then when a request is made, the application would decrypt. I feel like that might have some major performance issues though. – Greg Jan 08 '18 at 23:07
  • 1
    Just use bouncy castle and PKCS the password with a cert, and store the cert in the environment. For example. – zaitsman Jan 09 '18 at 00:01
  • 1
    [see if my answer here helps](https://stackoverflow.com/questions/46207293/add-admin-page-without-full-fledged-user-management/46258324#46258324) – jamesSampica Jan 09 '18 at 04:27
  • Related reading: https://gaevoy.com/2021/01/08/secure-your-appsettings-json.html – HackSlash Jun 13 '23 at 18:35

4 Answers4

12

Remember do not store secrets in the main appsettings.json that is in the web site and usually held in source control. Use a file provider to locate the file in some other location elsewhere on the server.

If you have access to Azure, you could store the secret in Azure Key Vault instead of appsettings.json.

With that in mind, if your want to use a JSON file, you can use a bridge or a proxy class to handle the decryption of values.

First you will need a class to decrypt the values. For brevity, I won't go into the details of the decryption class here and will just assume that a class called SettingsDecryptor has been written and implements an interface called ISettingsDecryptor with a single method Decrypt which decrypts a string value.

The bridge class takes two constructor parameters.

  • The first is an IOptions<T> or IOptionsSnapshot<T> where T is that class that the section in appsettings.json is bound to via the services.Configure method (E.g. MyAppSettings). Alternatively, if you do not want to bind to a class, you could use IConfiguration instead and read directly from the configuration.
  • The second is the decryption class that implements ISettingsDecryptor.

In the bridge class, each property that requires decrypting should use the decryption class to decrypt the encrypted value in the configuration.

public class MyAppSettingsBridge : IAppSettings
{
    private readonly IOptions<MyAppSettings> _appSettings;

    private readonly ISettingsDecrypt _decryptor;

    public MyAppSettingsBridge(IOptionsSnapshot<MyAppSettings> appSettings, ISettingsDecrypt decryptor) {
        _appSettings = appSettings ?? throw new ArgumentNullException(nameof(appSettings));
        _decryptor = decryptor ?? throw new ArgumentException(nameof(decryptor));
    }

    public string ApplicationName => _appSettings.Value.ApplicationName;

    public string SqlConnectionSting => _decryptor.Decrypt(_appSettings.Value.Sql);

    public string OracleConnectionSting => _decryptor.Decrypt(_appSettings.Value.Oracle);
}

The DI container should be set up something like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddOptions();            
    services.Configure<MyAppSettings>(Configuration.GetSection("MyAppSettings"));
    services.AddSingleton(Configuration);        
    services.AddSingleton<ISettingsDecrypt, SettingsDecryptor>();
    services.AddScoped<IAppSettings, MyAppSettingsBridge>();
}

The controller can then have a constructor that takes the bridge as an IAppSettings to access the decrypted settings.

The above answer is a brief summary of the overall solution as there is quite a bit of code required.

The full detailed explanation can be seen at my blog post Hiding Secrets in appsettings.json – Using a Bridge in your ASP.Net Core Configuration (Part 4) where I describe using a bridge pattern in detail. There is also a full example (including a decryption class) on Github at https://github.com/configureappio/ConfiguarationBridgeCrypto

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Steve Collins
  • 144
  • 1
  • 4
6

The JSON configuration provider does not support encryption. Currently, the only out of the box provider that does support encrypted configuration is Azure KeyVault. You can use KeyVault whether or not your application is actually hosted on Azure, and although it's not free, the allowances are such that it would likely only cost pennies in most scenarios.

That said, part of the beauty of Core is that it's completely modular. You can always create your own configuration provider(s) and implement whatever you want. For example, you could write a JSON provider that actually does support encryption, if that's how you want to go.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • If I wanted to roll my own configuration provider, where would I store the password? I'm thinking I'd store it as an environment variable, but there's got to be a better way, no? – Ryan Battistone Jan 09 '18 at 00:12
  • 1
    That's sort of the point: you'd store it wherever you please. A configuration provider is inherently just something that knows how to read from a particular source. For example, the JSON provider knows how to read values from JSON. The only piece of the puzzle you're missing is "knows how to read encrypted values", which the default JSON provider does not. This is just logic you'd have to add to *your* provider, but the source could be JSON, environment variables, Web.config, etc. Whatever works best for you. – Chris Pratt Jan 09 '18 at 20:48
  • 2
    Where would you store the ClientSecret of the Azure KeyVault? The [documentation](https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-2.1) states that this ClientSecret is stored in the appsettings.json. I presume that this is also quite undesirable, and done to keep the example understandable. – Fluous Nov 23 '18 at 14:47
  • 3
    @Chris Pratt I would like to access Azure KeyVault from a .NET core application not hosted on Azure, what would be the best way to approach this? All MS documentation implies that the application is hosted in Azure. – Greg Quinn Jan 17 '19 at 03:08
2

For ASP.NET Core the best solution is to do any transformations of the configuration values, like decryption or string replacements, when the application starts. This is why configuration provider exists.

The configuration providers can be chained. In the source code of the Microsoft.Extensions.Configuration there is class called ChainedConfigurationProvider that can be used as an example.

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return new HostBuilder()
    .ConfigureAppConfiguration((host, config) => {

        var jsonFile = new ConfigurationBuilder();
        jsonFile.AddJsonFile("appsettings.json");
        // the json file is the source for the new configuration provider.
        config.AddConfiguration(jsonFile.Build());
    });
}

If you are using Docker Swarm or Kubernetes you don't have to encrypt the password in the appsettings.json file. You can use the build-in Key-per-file Configuration Provider or custom configuration provider to read the password from a docker secret and map it to a configuration value.

On my blog post How to manage passwords in ASP.NET Core configuration files I explain in detail how to create a custom configuration provider that allows you to keep only the password as a secret and update the configuration string at runtime. Also the the full source code of this article is hosted on github.com/gabihodoroaga/blog-app-secrets.

1

You could take a look at my Moonrise.StandardUtils.NetStd NuGet package. The Settings class has transparent en/decryption and is a much simpler way to access a JSON settings file - whether Application or User - in any .Net application.

Settings.Application.SettingsEncryptor = new DpApiSettingsEncryptor(DpApiSettingsEncryptor.ProtectionScope.Machine);
MyConfigClass config;
Settings.Application.Read("Configuration", ref config)

Anything individual or group of settings that has previously been encrypted will be decrypted. You can encrypt a setting with;

Settings.Application.Write("ContainerStartup:FileProviders:BrandingConfig", secretValue, true);

Or use the EncryptAppSettings.exe that is bundled into Moonrise.Microsoft.EncryptedJsonConfiguration or the Moonrise.Samples NuGet packages - you'll find it in the package file/directory - well it's a way to get an .exe to folk.

The Moonrise.Microsoft.EncryptedJsonConfiguration allows you to use the IConfigurationBuilder with .AddEncyptedJsonFile(...) which will then transparently decrypt any settings encrypted with the EncryptAppSettings.exe.

Here's a fragment of a partially encrypted appSettings.json

  "ContainerStartup": {
    "FileProviders": {
      "BrandingConfig": "[{ENC]{AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAsevacb5DdkaxvzOPPkLrdwQAAAACAAAAAAAQZgAAAAEAACAAAADITnvKp+Lnb5n6kPK7WyYuWFQLnbvbkOvgHBLBdIw2MAAAAAAOgAAAAAIAACAAAADqJZ0YUGC+jOEr4/6hgQ+8UdZ1ssbiEXXCjdhSV3teZ3AAAAAW4d8Z38JYNM1Dw45KquZYK+bTszYp/1wXt+LiYpiy2q88sOpQr5VpDFatgWar1aOePXA52RC6eZH1HFrYijqWTSEiffBqWzWZPPTXw1wkUVB5MLIjOq4bu33h+4Z23Vy+XaFsf6IFVl4ccM4fHpsRQAAAAAG5OP+nJQxzH3A7n3gnh8d2eAOFgLWzYCDgQon7NXHeEJcZezgxT+0npvIQ/kcYb1Xpwt7FiNtyJ2HZswL8MSg=]{ENC[{",
      "SearchingNotification": true,
      "UseBranding": false,
      "UseLocalModule": true,
      "UseLocalModuleEmbedded": true,
      "UseParentModules": true,
      "UseParentModulesEmbedded": false
    },